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 <deque>
21 #include <memory>
22 
23 #include <hintids.hxx>
24 
25 #include <editeng/twolinesitem.hxx>
26 #include <editeng/charrotateitem.hxx>
27 #include <vcl/outdev.hxx>
28 #include <txatbase.hxx>
29 #include <fmtruby.hxx>
30 #include <txtatr.hxx>
31 #include <charfmt.hxx>
32 #include <layfrm.hxx>
33 #include <SwPortionHandler.hxx>
34 #include "pormulti.hxx"
35 #include "inftxt.hxx"
36 #include "itrpaint.hxx"
37 #include <viewopt.hxx>
38 #include "itrform2.hxx"
39 #include "porfld.hxx"
40 #include "porglue.hxx"
41 #include "porrst.hxx"
42 #include <pagefrm.hxx>
43 #include <rowfrm.hxx>
44 #include <tgrditem.hxx>
45 #include <swtable.hxx>
46 #include <fmtfsize.hxx>
47 #include <doc.hxx>
48 
49 using namespace ::com::sun::star;
50 
51 // A SwMultiPortion is not a simple portion,
52 // it's a container, which contains almost a SwLineLayoutPortion.
53 // This SwLineLayout could be followed by other textportions via pPortion
54 // and by another SwLineLayout via pNext to realize a doubleline portion.
~SwMultiPortion()55 SwMultiPortion::~SwMultiPortion()
56 {
57 }
58 
Paint(const SwTextPaintInfo &) const59 void SwMultiPortion::Paint( const SwTextPaintInfo & ) const
60 {
61     OSL_FAIL( "Don't try SwMultiPortion::Paint, try SwTextPainter::PaintMultiPortion" );
62 }
63 
64 // Summarize the internal lines to calculate the (external) size.
65 // The internal line has to calculate first.
CalcSize(SwTextFormatter & rLine,SwTextFormatInfo & rInf)66 void SwMultiPortion::CalcSize( SwTextFormatter& rLine, SwTextFormatInfo &rInf )
67 {
68     Width( 0 );
69     Height( 0 );
70     SetAscent( 0 );
71     SetFlyInContent( false );
72     SwLineLayout *pLay = &GetRoot();
73     do
74     {
75         pLay->CalcLine( rLine, rInf );
76         if( rLine.IsFlyInCntBase() )
77             SetFlyInContent( true );
78         if( IsRuby() && ( OnTop() == ( pLay == &GetRoot() ) ) )
79         {
80             // An empty phonetic line don't need an ascent or a height.
81             if( !pLay->Width() )
82             {
83                 pLay->SetAscent( 0 );
84                 pLay->Height( 0 );
85             }
86             if( OnTop() )
87                 SetAscent( GetAscent() + pLay->Height() );
88         }
89         else
90             SetAscent( GetAscent() + pLay->GetAscent() );
91 
92         // Increase the line height, except for ruby text on the right.
93         if ( !IsRuby() || !OnRight() || pLay == &GetRoot() )
94             Height( Height() + pLay->Height() );
95         else
96         {
97             // We already added the width after building the portion,
98             // so no need to add it twice.
99             break;
100         }
101 
102         if( Width() < pLay->Width() )
103             Width( pLay->Width() );
104         pLay = pLay->GetNext();
105     } while ( pLay );
106     if( !HasBrackets() )
107         return;
108 
109     sal_uInt16 nTmp = static_cast<SwDoubleLinePortion*>(this)->GetBrackets()->nHeight;
110     if( nTmp > Height() )
111     {
112         const sal_uInt16 nAdd = ( nTmp - Height() ) / 2;
113         GetRoot().SetAscent( GetRoot().GetAscent() + nAdd );
114         GetRoot().Height( GetRoot().Height() + nAdd );
115         Height( nTmp );
116     }
117     nTmp = static_cast<SwDoubleLinePortion*>(this)->GetBrackets()->nAscent;
118     if( nTmp > GetAscent() )
119         SetAscent( nTmp );
120 }
121 
CalcSpacing(tools::Long,const SwTextSizeInfo &) const122 tools::Long SwMultiPortion::CalcSpacing( tools::Long , const SwTextSizeInfo & ) const
123 {
124     return 0;
125 }
126 
ChgSpaceAdd(SwLineLayout *,tools::Long) const127 bool SwMultiPortion::ChgSpaceAdd( SwLineLayout*, tools::Long ) const
128 {
129     return false;
130 }
131 
HandlePortion(SwPortionHandler & rPH) const132 void SwMultiPortion::HandlePortion( SwPortionHandler& rPH ) const
133 {
134     rPH.Text( GetLen(), GetWhichPor() );
135 }
136 
137 // sets the tabulator-flag, if there's any tabulator-portion inside.
ActualizeTabulator()138 void SwMultiPortion::ActualizeTabulator()
139 {
140     SwLinePortion* pPor = GetRoot().GetFirstPortion();
141     // First line
142     for( m_bTab1 = m_bTab2 = false; pPor; pPor = pPor->GetNextPortion() )
143         if( pPor->InTabGrp() )
144             SetTab1( true );
145     if( GetRoot().GetNext() )
146     {
147         // Second line
148         pPor = GetRoot().GetNext()->GetFirstPortion();
149         do
150         {
151             if( pPor->InTabGrp() )
152                 SetTab2( true );
153             pPor = pPor->GetNextPortion();
154         } while ( pPor );
155     }
156 }
157 
SwRotatedPortion(const SwMultiCreator & rCreate,TextFrameIndex const nEnd,bool bRTL)158 SwRotatedPortion::SwRotatedPortion( const SwMultiCreator& rCreate,
159         TextFrameIndex const nEnd, bool bRTL )
160     : SwMultiPortion( nEnd )
161 {
162     const SvxCharRotateItem* pRot = static_cast<const SvxCharRotateItem*>(rCreate.pItem);
163     if( !pRot )
164     {
165         const SwTextAttr& rAttr = *rCreate.pAttr;
166         const SfxPoolItem *const pItem =
167                 CharFormat::GetItem(rAttr, RES_CHRATR_ROTATE);
168         if ( pItem )
169         {
170             pRot = static_cast<const SvxCharRotateItem*>(pItem);
171         }
172     }
173     if( pRot )
174     {
175         sal_uInt8 nDir;
176         if ( bRTL )
177             nDir = pRot->IsBottomToTop() ? 3 : 1;
178         else
179             nDir = pRot->IsBottomToTop() ? 1 : 3;
180 
181         SetDirection( nDir );
182     }
183 }
184 
SwBidiPortion(TextFrameIndex const nEnd,sal_uInt8 nLv)185 SwBidiPortion::SwBidiPortion(TextFrameIndex const nEnd, sal_uInt8 nLv)
186     : SwMultiPortion( nEnd ), m_nLevel( nLv )
187 {
188     SetBidi();
189 
190     if ( m_nLevel % 2 )
191         SetDirection( DIR_RIGHT2LEFT );
192     else
193         SetDirection( DIR_LEFT2RIGHT );
194 }
195 
CalcSpacing(tools::Long nSpaceAdd,const SwTextSizeInfo & rInf) const196 tools::Long SwBidiPortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo& rInf ) const
197 {
198     return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt(rInf)) * nSpaceAdd / SPACING_PRECISION_FACTOR;
199 }
200 
ChgSpaceAdd(SwLineLayout * pCurr,tools::Long nSpaceAdd) const201 bool SwBidiPortion::ChgSpaceAdd( SwLineLayout* pCurr, tools::Long nSpaceAdd ) const
202 {
203     if( !HasTabulator() && nSpaceAdd > 0 && !pCurr->IsSpaceAdd() )
204     {
205         pCurr->CreateSpaceAdd();
206         pCurr->SetLLSpaceAdd( nSpaceAdd, 0 );
207         return true;
208     }
209 
210     return false;
211 }
212 
GetSpaceCnt(const SwTextSizeInfo & rInf) const213 TextFrameIndex SwBidiPortion::GetSpaceCnt(const SwTextSizeInfo &rInf) const
214 {
215     // Calculate number of blanks for justified alignment
216     TextFrameIndex nTmpStart = rInf.GetIdx();
217     TextFrameIndex nNull(0);
218     TextFrameIndex nBlanks(0);
219 
220     for (SwLinePortion* pPor = GetRoot().GetFirstPortion(); pPor; pPor = pPor->GetNextPortion())
221     {
222         if( pPor->InTextGrp() )
223             nBlanks = nBlanks + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
224         else if ( pPor->IsMultiPortion() &&
225                  static_cast<SwMultiPortion*>(pPor)->IsBidi() )
226             nBlanks = nBlanks + static_cast<SwBidiPortion*>(pPor)->GetSpaceCnt( rInf );
227 
228         const_cast<SwTextSizeInfo &>(rInf).SetIdx( rInf.GetIdx() + pPor->GetLen() );
229     }
230     const_cast<SwTextSizeInfo &>(rInf).SetIdx( nTmpStart );
231     return nBlanks;
232 }
233 
234 // This constructor is for the continuation of a doubleline portion
235 // in the next line.
236 // It takes the same brackets and if the original has no content except
237 // brackets, these will be deleted.
SwDoubleLinePortion(SwDoubleLinePortion & rDouble,TextFrameIndex const nEnd)238 SwDoubleLinePortion::SwDoubleLinePortion(
239         SwDoubleLinePortion& rDouble, TextFrameIndex const nEnd)
240     : SwMultiPortion(nEnd)
241     , m_nLineDiff(0)
242     , m_nBlank1(0)
243     , m_nBlank2(0)
244 {
245     SetDirection( rDouble.GetDirection() );
246     SetDouble();
247     if( rDouble.GetBrackets() )
248     {
249         SetBrackets( rDouble );
250         // An empty multiportion needs no brackets.
251         // Notice: GetLen() might be zero, if the multiportion contains
252         // the second part of a field and the width might be zero, if
253         // it contains a note only. In this cases the brackets are okay.
254         // But if the length and the width are both zero, the portion
255         // is really empty.
256         if( rDouble.Width() ==  rDouble.BracketWidth() )
257             rDouble.ClearBrackets();
258     }
259 }
260 
261 // This constructor uses the textattribute to get the right brackets.
262 // The textattribute could be a 2-line-attribute or a character- or
263 // internet style, which contains the 2-line-attribute.
SwDoubleLinePortion(const SwMultiCreator & rCreate,TextFrameIndex const nEnd)264 SwDoubleLinePortion::SwDoubleLinePortion(
265         const SwMultiCreator& rCreate, TextFrameIndex const nEnd)
266     : SwMultiPortion(nEnd)
267     , m_pBracket(new SwBracket)
268     , m_nLineDiff(0)
269     , m_nBlank1(0)
270     , m_nBlank2(0)
271 {
272     m_pBracket->nAscent = 0;
273     m_pBracket->nHeight = 0;
274     m_pBracket->nPreWidth = 0;
275     m_pBracket->nPostWidth = 0;
276 
277     SetDouble();
278     const SvxTwoLinesItem* pTwo = static_cast<const SvxTwoLinesItem*>(rCreate.pItem);
279     if( pTwo )
280         m_pBracket->nStart = TextFrameIndex(0);
281     else
282     {
283         const SwTextAttr& rAttr = *rCreate.pAttr;
284         m_pBracket->nStart = rCreate.nStartOfAttr;
285 
286         const SfxPoolItem * const pItem =
287             CharFormat::GetItem( rAttr, RES_CHRATR_TWO_LINES );
288         if ( pItem )
289         {
290             pTwo = static_cast<const SvxTwoLinesItem*>(pItem);
291         }
292     }
293     if( pTwo )
294     {
295         m_pBracket->cPre = pTwo->GetStartBracket();
296         m_pBracket->cPost = pTwo->GetEndBracket();
297     }
298     else
299     {
300         m_pBracket->cPre = 0;
301         m_pBracket->cPost = 0;
302     }
303     SwFontScript nTmp = SW_SCRIPTS;
304     if( m_pBracket->cPre > 255 )
305     {
306         OUString aText(m_pBracket->cPre);
307         nTmp = SwScriptInfo::WhichFont(0, aText);
308     }
309     m_pBracket->nPreScript = nTmp;
310     nTmp = SW_SCRIPTS;
311     if( m_pBracket->cPost > 255 )
312     {
313         OUString aText(m_pBracket->cPost);
314         nTmp = SwScriptInfo::WhichFont(0, aText);
315     }
316     m_pBracket->nPostScript = nTmp;
317 
318     if( !m_pBracket->cPre && !m_pBracket->cPost )
319     {
320         m_pBracket.reset();
321     }
322 
323     // double line portions have the same direction as the frame directions
324     if ( rCreate.nLevel % 2 )
325         SetDirection( DIR_RIGHT2LEFT );
326     else
327         SetDirection( DIR_LEFT2RIGHT );
328 }
329 
330 // paints the wished bracket,
331 // if the multiportion has surrounding brackets.
332 // The X-position of the SwTextPaintInfo will be modified:
333 // the open bracket sets position behind itself,
334 // the close bracket in front of itself.
PaintBracket(SwTextPaintInfo & rInf,tools::Long nSpaceAdd,bool bOpen) const335 void SwDoubleLinePortion::PaintBracket( SwTextPaintInfo &rInf,
336                                         tools::Long nSpaceAdd,
337                                         bool bOpen ) const
338 {
339     sal_Unicode cCh = bOpen ? m_pBracket->cPre : m_pBracket->cPost;
340     if( !cCh )
341         return;
342     const sal_uInt16 nChWidth = bOpen ? PreWidth() : PostWidth();
343     if( !nChWidth )
344         return;
345     if( !bOpen )
346         rInf.X( rInf.X() + Width() - PostWidth() +
347             ( nSpaceAdd > 0 ? CalcSpacing( nSpaceAdd, rInf ) : 0 ) );
348 
349     SwBlankPortion aBlank( cCh, true );
350     aBlank.SetAscent( m_pBracket->nAscent );
351     aBlank.Width( nChWidth );
352     aBlank.Height( m_pBracket->nHeight );
353     {
354         SwFont aTmpFnt( *rInf.GetFont() );
355         SwFontScript nAct = bOpen ? m_pBracket->nPreScript : m_pBracket->nPostScript;
356         if( SW_SCRIPTS > nAct )
357             aTmpFnt.SetActual( nAct );
358         aTmpFnt.SetProportion( 100 );
359         SwFontSave aSave( rInf, &aTmpFnt );
360         aBlank.Paint( rInf );
361     }
362     if( bOpen )
363         rInf.X( rInf.X() + PreWidth() );
364 }
365 
366 // creates the bracket-structure
367 // and fills it, if not both characters are 0x00.
SetBrackets(const SwDoubleLinePortion & rDouble)368 void SwDoubleLinePortion::SetBrackets( const SwDoubleLinePortion& rDouble )
369 {
370     if( rDouble.m_pBracket )
371     {
372         m_pBracket.reset( new SwBracket );
373         m_pBracket->cPre = rDouble.m_pBracket->cPre;
374         m_pBracket->cPost = rDouble.m_pBracket->cPost;
375         m_pBracket->nPreScript = rDouble.m_pBracket->nPreScript;
376         m_pBracket->nPostScript = rDouble.m_pBracket->nPostScript;
377         m_pBracket->nStart = rDouble.m_pBracket->nStart;
378     }
379 }
380 
381 // calculates the size of the brackets => pBracket,
382 // reduces the nMaxWidth-parameter ( minus bracket-width )
383 // and moves the rInf-x-position behind the opening bracket.
FormatBrackets(SwTextFormatInfo & rInf,SwTwips & nMaxWidth)384 void SwDoubleLinePortion::FormatBrackets( SwTextFormatInfo &rInf, SwTwips& nMaxWidth )
385 {
386     nMaxWidth -= rInf.X();
387     SwFont aTmpFnt( *rInf.GetFont() );
388     aTmpFnt.SetProportion( 100 );
389     m_pBracket->nAscent = 0;
390     m_pBracket->nHeight = 0;
391     if( m_pBracket->cPre )
392     {
393         OUString aStr( m_pBracket->cPre );
394         SwFontScript nActualScr = aTmpFnt.GetActual();
395         if( SW_SCRIPTS > m_pBracket->nPreScript )
396             aTmpFnt.SetActual( m_pBracket->nPreScript );
397         SwFontSave aSave( rInf, &aTmpFnt );
398         SwPosSize aSize = rInf.GetTextSize( aStr );
399         m_pBracket->nAscent = rInf.GetAscent();
400         m_pBracket->nHeight = aSize.Height();
401         aTmpFnt.SetActual( nActualScr );
402         if( nMaxWidth > aSize.Width() )
403         {
404             m_pBracket->nPreWidth = aSize.Width();
405             nMaxWidth -= aSize.Width();
406             rInf.X( rInf.X() + aSize.Width() );
407         }
408         else
409         {
410             m_pBracket->nPreWidth = 0;
411             nMaxWidth = 0;
412         }
413     }
414     else
415         m_pBracket->nPreWidth = 0;
416     if( m_pBracket->cPost )
417     {
418         OUString aStr( m_pBracket->cPost );
419         if( SW_SCRIPTS > m_pBracket->nPostScript )
420             aTmpFnt.SetActual( m_pBracket->nPostScript );
421         SwFontSave aSave( rInf, &aTmpFnt );
422         SwPosSize aSize = rInf.GetTextSize( aStr );
423         const sal_uInt16 nTmpAsc = rInf.GetAscent();
424         if( nTmpAsc > m_pBracket->nAscent )
425         {
426             m_pBracket->nHeight += nTmpAsc - m_pBracket->nAscent;
427             m_pBracket->nAscent = nTmpAsc;
428         }
429         if( aSize.Height() > m_pBracket->nHeight )
430             m_pBracket->nHeight = aSize.Height();
431         if( nMaxWidth > aSize.Width() )
432         {
433             m_pBracket->nPostWidth = aSize.Width();
434             nMaxWidth -= aSize.Width();
435         }
436         else
437         {
438             m_pBracket->nPostWidth = 0;
439             nMaxWidth = 0;
440         }
441     }
442     else
443         m_pBracket->nPostWidth = 0;
444     nMaxWidth += rInf.X();
445 }
446 
447 // calculates the number of blanks in each line and
448 // the difference of the width of the two lines.
449 // These results are used from the text adjustment.
CalcBlanks(SwTextFormatInfo & rInf)450 void SwDoubleLinePortion::CalcBlanks( SwTextFormatInfo &rInf )
451 {
452     SwLinePortion* pPor = GetRoot().GetFirstPortion();
453     TextFrameIndex nNull(0);
454     TextFrameIndex nStart = rInf.GetIdx();
455     SetTab1( false );
456     SetTab2( false );
457     for (m_nBlank1 = TextFrameIndex(0); pPor; pPor = pPor->GetNextPortion())
458     {
459         if( pPor->InTextGrp() )
460             m_nBlank1 = m_nBlank1 + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
461         rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
462         if( pPor->InTabGrp() )
463             SetTab1( true );
464     }
465     m_nLineDiff = GetRoot().Width();
466     if( GetRoot().GetNext() )
467     {
468         pPor = GetRoot().GetNext()->GetFirstPortion();
469         m_nLineDiff -= GetRoot().GetNext()->Width();
470     }
471     for (m_nBlank2 = TextFrameIndex(0); pPor; pPor = pPor->GetNextPortion())
472     {
473         if( pPor->InTextGrp() )
474             m_nBlank2 = m_nBlank2 + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
475         rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
476         if( pPor->InTabGrp() )
477             SetTab2( true );
478     }
479     rInf.SetIdx( nStart );
480 }
481 
CalcSpacing(tools::Long nSpaceAdd,const SwTextSizeInfo &) const482 tools::Long SwDoubleLinePortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo & ) const
483 {
484     return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt()) * nSpaceAdd / SPACING_PRECISION_FACTOR;
485 }
486 
487 // Merges the spaces for text adjustment from the inner and outer part.
488 // Inside the doubleline portion the wider line has no spaceadd-array, the
489 // smaller line has such an array to reach width of the wider line.
490 // If the surrounding line has text adjustment and the doubleline portion
491 // contains no tabulator, it is necessary to create/manipulate the inner
492 // space arrays.
ChgSpaceAdd(SwLineLayout * pCurr,tools::Long nSpaceAdd) const493 bool SwDoubleLinePortion::ChgSpaceAdd( SwLineLayout* pCurr,
494                                            tools::Long nSpaceAdd ) const
495 {
496     bool bRet = false;
497     if( !HasTabulator() && nSpaceAdd > 0 )
498     {
499         if( !pCurr->IsSpaceAdd() )
500         {
501             // The wider line gets the spaceadd from the surrounding line direct
502             pCurr->CreateSpaceAdd();
503             pCurr->SetLLSpaceAdd( nSpaceAdd, 0 );
504             bRet = true;
505         }
506         else
507         {
508             sal_Int32 const nMyBlank = sal_Int32(GetSmallerSpaceCnt());
509             sal_Int32 const nOther = sal_Int32(GetSpaceCnt());
510             SwTwips nMultiSpace = pCurr->GetLLSpaceAdd( 0 ) * nMyBlank + nOther * nSpaceAdd;
511 
512             if( nMyBlank )
513                 nMultiSpace /= sal_Int32(nMyBlank);
514 
515 //            pCurr->SetLLSpaceAdd( nMultiSpace, 0 );
516             // #i65711# SetLLSpaceAdd replaces the first value,
517             // instead we want to insert a new first value:
518             std::vector<tools::Long>* pVec = pCurr->GetpLLSpaceAdd();
519             pVec->insert( pVec->begin(), nMultiSpace );
520             bRet = true;
521         }
522     }
523     return bRet;
524 }
525 // cancels the manipulation from SwDoubleLinePortion::ChangeSpaceAdd(..)
ResetSpaceAdd(SwLineLayout * pCurr)526 void SwDoubleLinePortion::ResetSpaceAdd( SwLineLayout* pCurr )
527 {
528     pCurr->RemoveFirstLLSpaceAdd();
529     if( !pCurr->GetLLSpaceAddCount() )
530         pCurr->FinishSpaceAdd();
531 }
532 
~SwDoubleLinePortion()533 SwDoubleLinePortion::~SwDoubleLinePortion()
534 {
535 }
536 
537 // constructs a ruby portion, i.e. an additional text is displayed
538 // beside the main text, e.g. phonetic characters.
SwRubyPortion(const SwRubyPortion & rRuby,TextFrameIndex const nEnd)539 SwRubyPortion::SwRubyPortion(const SwRubyPortion& rRuby, TextFrameIndex const nEnd)
540     : SwMultiPortion( nEnd )
541     , m_nRubyOffset( rRuby.GetRubyOffset() )
542     , m_nAdjustment( rRuby.GetAdjustment() )
543 {
544     SetDirection( rRuby.GetDirection() );
545     SetRubyPosition( rRuby.GetRubyPosition() );
546     SetRuby();
547 }
548 
549 // constructs a ruby portion, i.e. an additional text is displayed
550 // beside the main text, e.g. phonetic characters.
SwRubyPortion(const SwMultiCreator & rCreate,const SwFont & rFnt,const IDocumentSettingAccess & rIDocumentSettingAccess,TextFrameIndex const nEnd,TextFrameIndex const nOffs,const SwTextSizeInfo & rInf)551 SwRubyPortion::SwRubyPortion( const SwMultiCreator& rCreate, const SwFont& rFnt,
552                               const IDocumentSettingAccess& rIDocumentSettingAccess,
553                               TextFrameIndex const nEnd, TextFrameIndex const nOffs,
554                               const SwTextSizeInfo &rInf )
555      : SwMultiPortion( nEnd )
556 {
557     SetRuby();
558     OSL_ENSURE( SwMultiCreatorId::Ruby == rCreate.nId, "Ruby expected" );
559     OSL_ENSURE( RES_TXTATR_CJK_RUBY == rCreate.pAttr->Which(), "Wrong attribute" );
560     const SwFormatRuby& rRuby = rCreate.pAttr->GetRuby();
561     m_nAdjustment = rRuby.GetAdjustment();
562     m_nRubyOffset = nOffs;
563 
564     const SwTextFrame *pFrame = rInf.GetTextFrame();
565     RubyPosition ePos = static_cast<RubyPosition>( rRuby.GetPosition() );
566 
567     // RIGHT is designed for horizontal writing mode only.
568     if ( ePos == RubyPosition::RIGHT && pFrame->IsVertical() )
569         ePos = RubyPosition::ABOVE;
570 
571     // In grid mode we force the ruby text to the upper or lower line
572     if ( rInf.SnapToGrid() )
573     {
574         SwTextGridItem const*const pGrid( GetGridItem(pFrame->FindPageFrame()) );
575         if ( pGrid )
576             ePos = pGrid->GetRubyTextBelow() ? RubyPosition::BELOW : RubyPosition::ABOVE;
577     }
578 
579     SetRubyPosition( ePos );
580 
581     const SwCharFormat *const pFormat =
582         static_txtattr_cast<SwTextRuby const*>(rCreate.pAttr)->GetCharFormat();
583     std::unique_ptr<SwFont> pRubyFont;
584     if( pFormat )
585     {
586         const SwAttrSet& rSet = pFormat->GetAttrSet();
587         pRubyFont.reset(new SwFont( rFnt ));
588         pRubyFont->SetDiffFnt( &rSet, &rIDocumentSettingAccess );
589 
590         // we do not allow a vertical font for the ruby text
591         pRubyFont->SetVertical( rFnt.GetOrientation() , OnRight() );
592     }
593 
594     OUString aStr = rRuby.GetText().copy( sal_Int32(nOffs) );
595     SwFieldPortion *pField = new SwFieldPortion( aStr, std::move(pRubyFont) );
596     pField->SetNextOffset( nOffs );
597     pField->SetFollow( true );
598 
599     if( OnTop() )
600         GetRoot().SetNextPortion( pField );
601     else
602     {
603         GetRoot().SetNext( new SwLineLayout() );
604         GetRoot().GetNext()->SetNextPortion( pField );
605     }
606 
607     // ruby portions have the same direction as the frame directions
608     if ( rCreate.nLevel % 2 )
609     {
610         // switch right and left ruby adjustment in rtl environment
611         if ( css::text::RubyAdjust_LEFT == m_nAdjustment )
612             m_nAdjustment = css::text::RubyAdjust_RIGHT;
613         else if ( css::text::RubyAdjust_RIGHT == m_nAdjustment )
614             m_nAdjustment = css::text::RubyAdjust_LEFT;
615 
616         SetDirection( DIR_RIGHT2LEFT );
617     }
618     else
619         SetDirection( DIR_LEFT2RIGHT );
620 }
621 
622 // In ruby portion there are different alignments for
623 // the ruby text and the main text.
624 // Left, right, centered and two possibilities of block adjustment
625 // The block adjustment is realized by spacing between the characters,
626 // either with a half space or no space in front of the first letter and
627 // a half space at the end of the last letter.
628 // Notice: the smaller line will be manipulated, normally it's the ruby line,
629 // but it could be the main text, too.
630 // If there is a tabulator in smaller line, no adjustment is possible.
Adjust_(SwTextFormatInfo & rInf)631 void SwRubyPortion::Adjust_( SwTextFormatInfo &rInf )
632 {
633     SwTwips nLineDiff = GetRoot().Width() - GetRoot().GetNext()->Width();
634     TextFrameIndex const nOldIdx = rInf.GetIdx();
635     if( !nLineDiff )
636         return;
637     SwLineLayout *pCurr;
638     if( nLineDiff < 0 )
639     {   // The first line has to be adjusted.
640         if( GetTab1() )
641             return;
642         pCurr = &GetRoot();
643         nLineDiff = -nLineDiff;
644     }
645     else
646     {   // The second line has to be adjusted.
647         if( GetTab2() )
648             return;
649         pCurr = GetRoot().GetNext();
650         rInf.SetIdx( nOldIdx + GetRoot().GetLen() );
651     }
652     sal_uInt16 nLeft = 0;   // the space in front of the first letter
653     sal_uInt16 nRight = 0;  // the space at the end of the last letter
654     TextFrameIndex nSub(0);
655     switch ( m_nAdjustment )
656     {
657         case css::text::RubyAdjust_CENTER: nRight = static_cast<sal_uInt16>(nLineDiff / 2);
658             [[fallthrough]];
659         case css::text::RubyAdjust_RIGHT: nLeft  = static_cast<sal_uInt16>(nLineDiff - nRight); break;
660         case css::text::RubyAdjust_BLOCK: nSub   = TextFrameIndex(1);
661             [[fallthrough]];
662         case css::text::RubyAdjust_INDENT_BLOCK:
663         {
664             TextFrameIndex nCharCnt(0);
665             SwLinePortion *pPor;
666             for( pPor = pCurr->GetFirstPortion(); pPor; pPor = pPor->GetNextPortion() )
667             {
668                 if( pPor->InTextGrp() )
669                     static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nCharCnt );
670                 rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
671             }
672             if( nCharCnt > nSub )
673             {
674                 SwTwips nCalc = nLineDiff / sal_Int32(nCharCnt - nSub);
675                 short nTmp;
676                 if( nCalc < SHRT_MAX )
677                     nTmp = -short(nCalc);
678                 else
679                     nTmp = SHRT_MIN;
680 
681                 pCurr->CreateSpaceAdd( SPACING_PRECISION_FACTOR * nTmp );
682                 nLineDiff -= nCalc * (sal_Int32(nCharCnt) - 1);
683             }
684             if( nLineDiff > 1 )
685             {
686                 nRight = static_cast<sal_uInt16>(nLineDiff / 2);
687                 nLeft  = static_cast<sal_uInt16>(nLineDiff - nRight);
688             }
689             break;
690         }
691         default: OSL_FAIL( "New ruby adjustment" );
692     }
693     if( nLeft || nRight )
694     {
695         if( !pCurr->GetNextPortion() )
696             pCurr->SetNextPortion(SwTextPortion::CopyLinePortion(*pCurr));
697         if( nLeft )
698         {
699             SwMarginPortion *pMarg = new SwMarginPortion;
700             pMarg->AddPrtWidth( nLeft );
701             pMarg->SetNextPortion( pCurr->GetNextPortion() );
702             pCurr->SetNextPortion( pMarg );
703         }
704         if( nRight )
705         {
706             SwMarginPortion *pMarg = new SwMarginPortion;
707             pMarg->AddPrtWidth( nRight );
708             pCurr->FindLastPortion()->Append( pMarg );
709         }
710     }
711 
712     pCurr->Width( Width() );
713     rInf.SetIdx( nOldIdx );
714 }
715 
716 // has to change the nRubyOffset, if there's a fieldportion
717 // in the phonetic line.
718 // The nRubyOffset is the position in the rubystring, where the
719 // next SwRubyPortion has start the displaying of the phonetics.
CalcRubyOffset()720 void SwRubyPortion::CalcRubyOffset()
721 {
722     const SwLineLayout *pCurr = &GetRoot();
723     if( !OnTop() )
724     {
725         pCurr = pCurr->GetNext();
726         if( !pCurr )
727             return;
728     }
729     const SwLinePortion *pPor = pCurr->GetFirstPortion();
730     const SwFieldPortion *pField = nullptr;
731     while( pPor )
732     {
733         if( pPor->InFieldGrp() )
734             pField = static_cast<const SwFieldPortion*>(pPor);
735         pPor = pPor->GetNextPortion();
736     }
737     if( pField )
738     {
739         if( pField->HasFollow() )
740             m_nRubyOffset = pField->GetNextOffset();
741         else
742             m_nRubyOffset = TextFrameIndex(COMPLETE_STRING);
743     }
744 }
745 
746 // A little helper function for GetMultiCreator(..)
747 // It extracts the 2-line-format from a 2-line-attribute or a character style.
748 // The rValue is set to true, if the 2-line-attribute's value is set and
749 // no 2-line-format reference is passed. If there is a 2-line-format reference,
750 // then the rValue is set only, if the 2-line-attribute's value is set _and_
751 // the 2-line-formats has the same brackets.
lcl_Check2Lines(const SfxPoolItem * const pItem,const SvxTwoLinesItem * & rpRef,bool & rValue)752 static bool lcl_Check2Lines(const SfxPoolItem *const pItem,
753         const SvxTwoLinesItem* &rpRef, bool &rValue)
754 {
755     if( pItem )
756     {
757         rValue = static_cast<const SvxTwoLinesItem*>(pItem)->GetValue();
758         if( !rpRef )
759             rpRef = static_cast<const SvxTwoLinesItem*>(pItem);
760         else if( static_cast<const SvxTwoLinesItem*>(pItem)->GetEndBracket() !=
761                     rpRef->GetEndBracket() ||
762                     static_cast<const SvxTwoLinesItem*>(pItem)->GetStartBracket() !=
763                     rpRef->GetStartBracket() )
764             rValue = false;
765         return true;
766     }
767     return false;
768 }
769 
lcl_Has2Lines(const SwTextAttr & rAttr,const SvxTwoLinesItem * & rpRef,bool & rValue)770 static bool lcl_Has2Lines(const SwTextAttr& rAttr,
771         const SvxTwoLinesItem* &rpRef, bool &rValue)
772 {
773     const SfxPoolItem* pItem = CharFormat::GetItem(rAttr, RES_CHRATR_TWO_LINES);
774     return lcl_Check2Lines(pItem, rpRef, rValue);
775 }
776 
777 // is a little help function for GetMultiCreator(..)
778 // It extracts the charrotation from a charrotate-attribute or a character style.
779 // The rValue is set to true, if the charrotate-attribute's value is set and
780 // no charrotate-format reference is passed.
781 // If there is a charrotate-format reference, then the rValue is set only,
782 // if the charrotate-attribute's value is set _and_ identical
783 // to the charrotate-format's value.
lcl_CheckRotation(const SfxPoolItem * const pItem,const SvxCharRotateItem * & rpRef,bool & rValue)784 static bool lcl_CheckRotation(const SfxPoolItem *const pItem,
785         const SvxCharRotateItem* &rpRef, bool &rValue)
786 {
787     if ( pItem )
788     {
789         rValue = static_cast<const SvxCharRotateItem*>(pItem)->GetValue() != 0_deg10;
790         if( !rpRef )
791             rpRef = static_cast<const SvxCharRotateItem*>(pItem);
792         else if( static_cast<const SvxCharRotateItem*>(pItem)->GetValue() !=
793                     rpRef->GetValue() )
794             rValue = false;
795         return true;
796     }
797 
798     return false;
799 }
800 
lcl_HasRotation(const SwTextAttr & rAttr,const SvxCharRotateItem * & rpRef,bool & rValue)801 static bool lcl_HasRotation(const SwTextAttr& rAttr,
802         const SvxCharRotateItem* &rpRef, bool &rValue)
803 {
804     const SfxPoolItem* pItem = CharFormat::GetItem( rAttr, RES_CHRATR_ROTATE );
805     return lcl_CheckRotation(pItem, rpRef, rValue);
806 }
807 
808 namespace sw {
809     namespace {
810 
811     // need to use a very special attribute iterator here that returns
812     // both the hints and the nodes, so that GetMultiCreator() can handle
813     // items in the nodes' set properly
814     class MergedAttrIterMulti
815         : public MergedAttrIterBase
816     {
817     private:
818         bool m_First = true;
819     public:
MergedAttrIterMulti(SwTextFrame const & rFrame)820         MergedAttrIterMulti(SwTextFrame const& rFrame) : MergedAttrIterBase(rFrame) {}
821         SwTextAttr const* NextAttr(SwTextNode const*& rpNode);
822         // can't have operator= because m_pMerged/m_pNode const
Assign(MergedAttrIterMulti const & rOther)823         void Assign(MergedAttrIterMulti const& rOther)
824         {
825             assert(m_pMerged == rOther.m_pMerged);
826             assert(m_pNode == rOther.m_pNode);
827             m_CurrentExtent = rOther.m_CurrentExtent;
828             m_CurrentHint = rOther.m_CurrentHint;
829             m_First = rOther.m_First;
830         }
831     };
832 
833     }
834 
NextAttr(SwTextNode const * & rpNode)835     SwTextAttr const* MergedAttrIterMulti::NextAttr(SwTextNode const*& rpNode)
836     {
837         if (m_First)
838         {
839             m_First = false;
840             rpNode = m_pMerged
841                 ? !m_pMerged->extents.empty()
842                     ? m_pMerged->extents[0].pNode
843                     : m_pMerged->pFirstNode
844                 : m_pNode;
845             return nullptr;
846         }
847         if (m_pMerged)
848         {
849             while (m_CurrentExtent < m_pMerged->extents.size())
850             {
851                 sw::Extent const& rExtent(m_pMerged->extents[m_CurrentExtent]);
852                 if (SwpHints const*const pHints = rExtent.pNode->GetpSwpHints())
853                 {
854                     while (m_CurrentHint < pHints->Count())
855                     {
856                         SwTextAttr const*const pHint(pHints->Get(m_CurrentHint));
857                         if (rExtent.nEnd < pHint->GetStart())
858                         {
859                             break;
860                         }
861                         ++m_CurrentHint;
862                         if (rExtent.nStart <= pHint->GetStart())
863                         {
864                             rpNode = rExtent.pNode;
865                             return pHint;
866                         }
867                     }
868                 }
869                 ++m_CurrentExtent;
870                 if (m_CurrentExtent < m_pMerged->extents.size() &&
871                     rExtent.pNode != m_pMerged->extents[m_CurrentExtent].pNode)
872                 {
873                     m_CurrentHint = 0; // reset
874                     rpNode = m_pMerged->extents[m_CurrentExtent].pNode;
875                     return nullptr;
876                 }
877             }
878             return nullptr;
879         }
880         else
881         {
882             SwpHints const*const pHints(m_pNode->GetpSwpHints());
883             if (pHints)
884             {
885                 if (m_CurrentHint < pHints->Count())
886                 {
887                     SwTextAttr const*const pHint(pHints->Get(m_CurrentHint));
888                     ++m_CurrentHint;
889                     rpNode = m_pNode;
890                     return pHint;
891                 }
892             }
893             return nullptr;
894         }
895     }
896 }
897 
898 // If we (e.g. the position rPos) are inside a two-line-attribute or
899 // a ruby-attribute, the attribute will be returned in a SwMultiCreator-struct,
900 // otherwise the function returns zero.
901 // The rPos parameter is set to the end of the multiportion,
902 // normally this is the end of the attribute,
903 // but sometimes it is the start of another attribute, which finished or
904 // interrupts the first attribute.
905 // E.g. a ruby portion interrupts a 2-line-attribute, a 2-line-attribute
906 // with different brackets interrupts another 2-line-attribute.
GetMultiCreator(TextFrameIndex & rPos,SwMultiPortion const * pMulti) const907 std::optional<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex &rPos,
908                                                 SwMultiPortion const * pMulti ) const
909 {
910     SwScriptInfo& rSI = const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo();
911 
912     // get the last embedding level
913     sal_uInt8 nCurrLevel;
914     if ( pMulti )
915     {
916         OSL_ENSURE( pMulti->IsBidi(), "Nested MultiPortion is not BidiPortion" );
917         // level associated with bidi-portion;
918         nCurrLevel = static_cast<SwBidiPortion const *>(pMulti)->GetLevel();
919     }
920     else
921         // no nested bidi portion required
922         nCurrLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
923 
924     // check if there is a field at rPos:
925     sal_uInt8 nNextLevel = nCurrLevel;
926     bool bFieldBidi = false;
927 
928     if (rPos < TextFrameIndex(GetText().getLength()) && CH_TXTATR_BREAKWORD == GetChar(rPos))
929     {
930         bFieldBidi = true;
931     }
932     else
933         nNextLevel = rSI.DirType( rPos );
934 
935     if (TextFrameIndex(GetText().getLength()) != rPos && nNextLevel > nCurrLevel)
936     {
937         rPos = bFieldBidi ? rPos + TextFrameIndex(1) : rSI.NextDirChg(rPos, &nCurrLevel);
938         if (TextFrameIndex(COMPLETE_STRING) == rPos)
939             return {};
940         SwMultiCreator aRet;
941         aRet.pItem = nullptr;
942         aRet.pAttr = nullptr;
943         aRet.nStartOfAttr = TextFrameIndex(-1);
944         aRet.nId = SwMultiCreatorId::Bidi;
945         aRet.nLevel = nCurrLevel + 1;
946         return aRet;
947     }
948 
949     // a bidi portion can only contain other bidi portions
950     if ( pMulti )
951         return {};
952 
953     // need the node that contains input rPos
954     std::pair<SwTextNode const*, sal_Int32> startPos(m_pFrame->MapViewToModel(rPos));
955     const SvxCharRotateItem* pActiveRotateItem(nullptr);
956     const SfxPoolItem* pNodeRotateItem(nullptr);
957     const SvxTwoLinesItem* pActiveTwoLinesItem(nullptr);
958     const SfxPoolItem* pNodeTwoLinesItem(nullptr);
959     SwTextAttr const* pActiveTwoLinesHint(nullptr);
960     SwTextAttr const* pActiveRotateHint(nullptr);
961     const SwTextAttr *pRuby = nullptr;
962     sw::MergedAttrIterMulti iterAtStartOfNode(*m_pFrame);
963     bool bTwo = false;
964     bool bRot = false;
965 
966     for (sw::MergedAttrIterMulti iter = *m_pFrame; ; )
967     {
968         SwTextNode const* pNode(nullptr);
969         SwTextAttr const*const pAttr = iter.NextAttr(pNode);
970         if (!pNode)
971         {
972             break;
973         }
974         if (pAttr)
975         {
976             assert(pNode->GetIndex() <= startPos.first->GetIndex()); // should break earlier
977             if (startPos.first->GetIndex() <= pNode->GetIndex())
978             {
979                 if (startPos.first->GetIndex() != pNode->GetIndex()
980                     || startPos.second < pAttr->GetStart())
981                 {
982                     break;
983                 }
984                 if (startPos.second < pAttr->GetAnyEnd())
985                 {
986                     // sw_redlinehide: ruby *always* splits
987                     if (RES_TXTATR_CJK_RUBY == pAttr->Which())
988                         pRuby = pAttr;
989                     else
990                     {
991                         const SvxCharRotateItem* pRoTmp = nullptr;
992                         if (lcl_HasRotation( *pAttr, pRoTmp, bRot ))
993                         {
994                             pActiveRotateHint = bRot ? pAttr : nullptr;
995                             pActiveRotateItem = pRoTmp;
996                         }
997                         const SvxTwoLinesItem* p2Tmp = nullptr;
998                         if (lcl_Has2Lines( *pAttr, p2Tmp, bTwo ))
999                         {
1000                             pActiveTwoLinesHint = bTwo ? pAttr : nullptr;
1001                             pActiveTwoLinesItem = p2Tmp;
1002                         }
1003                     }
1004                 }
1005             }
1006         }
1007         else if (pNode) // !pAttr && pNode means the node changed
1008         {
1009             if (startPos.first->GetIndex() < pNode->GetIndex())
1010             {
1011                 break; // only one node initially
1012             }
1013             if (startPos.first->GetIndex() == pNode->GetIndex())
1014             {
1015                 iterAtStartOfNode.Assign(iter);
1016                 if (SfxItemState::SET == pNode->GetSwAttrSet().GetItemState(
1017                             RES_CHRATR_ROTATE, true, &pNodeRotateItem) &&
1018                     static_cast<const SvxCharRotateItem*>(pNodeRotateItem)->GetValue())
1019                 {
1020                     pActiveRotateItem = static_cast<const SvxCharRotateItem*>(pNodeRotateItem);
1021                 }
1022                 else
1023                 {
1024                     pNodeRotateItem = nullptr;
1025                 }
1026                 if (SfxItemState::SET == startPos.first->GetSwAttrSet().GetItemState(
1027                             RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem) &&
1028                     static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem)->GetValue())
1029                 {
1030                     pActiveTwoLinesItem = static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem);
1031                 }
1032                 else
1033                 {
1034                     pNodeTwoLinesItem = nullptr;
1035                 }
1036             }
1037         }
1038     }
1039     if (!pRuby && !pActiveTwoLinesItem && !pActiveRotateItem)
1040         return {};
1041 
1042     if( pRuby )
1043     {   // The winner is ... a ruby attribute and so
1044         // the end of the multiportion is the end of the ruby attribute.
1045         rPos = m_pFrame->MapModelToView(startPos.first, *pRuby->End());
1046         SwMultiCreator aRet;
1047         aRet.pItem = nullptr;
1048         aRet.pAttr = pRuby;
1049         aRet.nStartOfAttr = m_pFrame->MapModelToView(startPos.first, aRet.pAttr->GetStart());
1050         aRet.nId = SwMultiCreatorId::Ruby;
1051         aRet.nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
1052         return aRet;
1053     }
1054     if (pActiveTwoLinesHint ||
1055         (pNodeTwoLinesItem && pNodeTwoLinesItem == pActiveTwoLinesItem &&
1056          rPos < TextFrameIndex(GetText().getLength())))
1057     {   // The winner is a 2-line-attribute,
1058         // the end of the multiportion depends on the following attributes...
1059         SwMultiCreator aRet;
1060 
1061         // We note the endpositions of the 2-line attributes in aEnd as stack
1062         std::deque<TextFrameIndex> aEnd;
1063 
1064         // The bOn flag signs the state of the last 2-line attribute in the
1065         // aEnd-stack, it is compatible with the winner-attribute or
1066         // it interrupts the other attribute.
1067         bool bOn = true;
1068 
1069         if (pActiveTwoLinesHint)
1070         {
1071             aRet.pItem = nullptr;
1072             aRet.pAttr = pActiveTwoLinesHint;
1073             aRet.nStartOfAttr = m_pFrame->MapModelToView(startPos.first, aRet.pAttr->GetStart());
1074             if (pNodeTwoLinesItem)
1075             {
1076                 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
1077                 bOn = static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem)->GetEndBracket() ==
1078                         pActiveTwoLinesItem->GetEndBracket() &&
1079                       static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem)->GetStartBracket() ==
1080                         pActiveTwoLinesItem->GetStartBracket();
1081             }
1082             else
1083             {
1084                 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *aRet.pAttr->End()));
1085             }
1086         }
1087         else
1088         {
1089             aRet.pItem = pNodeTwoLinesItem;
1090             aRet.pAttr = nullptr;
1091             aRet.nStartOfAttr = TextFrameIndex(-1);
1092             aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
1093         }
1094         aRet.nId = SwMultiCreatorId::Double;
1095         aRet.nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
1096 
1097         // pActiveTwoLinesHint is the last 2-line-attribute, which contains
1098         // the actual position.
1099 
1100         // At this moment we know that at position rPos the "winner"-attribute
1101         // causes a 2-line-portion. The end of the attribute is the end of the
1102         // portion, if there's no interrupting attribute.
1103         // There are two kinds of interrupters:
1104         // - ruby attributes stops the 2-line-attribute, the end of the
1105         //   multiline is the start of the ruby attribute
1106         // - 2-line-attributes with value "Off" or with different brackets,
1107         //   these attributes may interrupt the winner, but they could be
1108         //   neutralized by another 2-line-attribute starting at the same
1109         //   position with the same brackets as the winner-attribute.
1110 
1111         // In the following loop rPos is the critical position and it will be
1112         // evaluated, if at rPos starts an interrupting or a maintaining
1113         // continuity attribute.
1114 
1115         // iterAtStartOfNode is positioned to the first hint of the node
1116         // (if any); the node item itself has already been handled above
1117         for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
1118         {
1119             SwTextNode const* pNode(nullptr);
1120             SwTextAttr const*const pTmp = iter.NextAttr(pNode);
1121             if (!pNode)
1122             {
1123                 break;
1124             }
1125             assert(startPos.first->GetIndex() <= pNode->GetIndex());
1126             TextFrameIndex nTmpStart;
1127             TextFrameIndex nTmpEnd;
1128             if (pTmp)
1129             {
1130                 nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
1131                 if (nTmpEnd <= rPos)
1132                     continue;
1133                 nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
1134             }
1135             else
1136             {
1137                 pNodeTwoLinesItem = nullptr;
1138                 pNode->GetSwAttrSet().GetItemState(
1139                             RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem);
1140                 nTmpStart = m_pFrame->MapModelToView(pNode, 0);
1141                 nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
1142                 assert(rPos <= nTmpEnd); // next node must not have smaller index
1143             }
1144 
1145             if (rPos < nTmpStart)
1146             {
1147                 // If bOn is false and the next attribute starts later than rPos
1148                 // the winner attribute is interrupted at rPos.
1149                 // If the start of the next attribute is behind the end of
1150                 // the last attribute on the aEnd-stack, this is the endposition
1151                 // on the stack is the end of the 2-line portion.
1152                 if (!bOn || aEnd.back() < nTmpStart)
1153                     break;
1154                 // At this moment, bOn is true and the next attribute starts
1155                 // behind rPos, so we could move rPos to the next startpoint
1156                 rPos = nTmpStart;
1157                 // We clean up the aEnd-stack, endpositions equal to rPos are
1158                 // superfluous.
1159                 while( !aEnd.empty() && aEnd.back() <= rPos )
1160                 {
1161                     bOn = !bOn;
1162                     aEnd.pop_back();
1163                 }
1164                 // If the endstack is empty, we simulate an attribute with
1165                 // state true and endposition rPos
1166                 if( aEnd.empty() )
1167                 {
1168                     aEnd.push_front( rPos );
1169                     bOn = true;
1170                 }
1171             }
1172             // A ruby attribute stops the 2-line immediately
1173             if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
1174                 return aRet;
1175             if (pTmp ? lcl_Has2Lines(*pTmp, pActiveTwoLinesItem, bTwo)
1176                      : lcl_Check2Lines(pNodeTwoLinesItem, pActiveTwoLinesItem, bTwo))
1177             {   // We have an interesting attribute...
1178                 if( bTwo == bOn )
1179                 {   // .. with the same state, so the last attribute could
1180                     // be continued.
1181                     if (aEnd.back() < nTmpEnd)
1182                         aEnd.back() = nTmpEnd;
1183                 }
1184                 else
1185                 {   // .. with a different state.
1186                     bOn = bTwo;
1187                     // If this is smaller than the last on the stack, we put
1188                     // it on the stack. If it has the same endposition, the last
1189                     // could be removed.
1190                     if (nTmpEnd < aEnd.back())
1191                         aEnd.push_back( nTmpEnd );
1192                     else if( aEnd.size() > 1 )
1193                         aEnd.pop_back();
1194                     else
1195                         aEnd.back() = nTmpEnd;
1196                 }
1197             }
1198         }
1199         if( bOn && !aEnd.empty() )
1200             rPos = aEnd.back();
1201         return aRet;
1202     }
1203     if (pActiveRotateHint ||
1204         (pNodeRotateItem && pNodeRotateItem == pActiveRotateItem &&
1205          rPos < TextFrameIndex(GetText().getLength())))
1206     {   // The winner is a rotate-attribute,
1207         // the end of the multiportion depends on the following attributes...
1208         SwMultiCreator aRet;
1209         aRet.nId = SwMultiCreatorId::Rotate;
1210 
1211         // We note the endpositions of the 2-line attributes in aEnd as stack
1212         std::deque<TextFrameIndex> aEnd;
1213 
1214         // The bOn flag signs the state of the last 2-line attribute in the
1215         // aEnd-stack, which could interrupts the winning rotation attribute.
1216         bool bOn = pNodeTwoLinesItem != nullptr;
1217         aEnd.push_front(TextFrameIndex(GetText().getLength()));
1218 
1219         // first, search for the start position of the next TWOLINE portion
1220         // because the ROTATE portion must end there at the latest
1221         TextFrameIndex n2Start = rPos;
1222         for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
1223         {
1224             SwTextNode const* pNode(nullptr);
1225             SwTextAttr const*const pTmp = iter.NextAttr(pNode);
1226             if (!pNode)
1227             {
1228                 break;
1229             }
1230             assert(startPos.first->GetIndex() <= pNode->GetIndex());
1231             TextFrameIndex nTmpStart;
1232             TextFrameIndex nTmpEnd;
1233             if (pTmp)
1234             {
1235                 nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
1236                 if (nTmpEnd <= n2Start)
1237                     continue;
1238                 nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
1239             }
1240             else
1241             {
1242                 pNodeTwoLinesItem = nullptr;
1243                 pNode->GetSwAttrSet().GetItemState(
1244                             RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem);
1245                 nTmpStart = m_pFrame->MapModelToView(pNode, 0);
1246                 nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
1247                 assert(n2Start <= nTmpEnd); // next node must not have smaller index
1248             }
1249 
1250             if (n2Start < nTmpStart)
1251             {
1252                 if (bOn || aEnd.back() < nTmpStart)
1253                     break;
1254                 n2Start = nTmpStart;
1255                 while( !aEnd.empty() && aEnd.back() <= n2Start )
1256                 {
1257                     bOn = !bOn;
1258                     aEnd.pop_back();
1259                 }
1260                 if( aEnd.empty() )
1261                 {
1262                     aEnd.push_front( n2Start );
1263                     bOn = false;
1264                 }
1265             }
1266             // A ruby attribute stops immediately
1267             if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
1268             {
1269                 bOn = true;
1270                 break;
1271             }
1272             const SvxTwoLinesItem* p2Lines = nullptr;
1273             if (pTmp ? lcl_Has2Lines(*pTmp, p2Lines, bTwo)
1274                      : lcl_Check2Lines(pNodeTwoLinesItem, p2Lines, bTwo))
1275             {
1276                 if( bTwo == bOn )
1277                 {
1278                     if (aEnd.back() < nTmpEnd)
1279                         aEnd.back() = nTmpEnd;
1280                 }
1281                 else
1282                 {
1283                     bOn = bTwo;
1284                     if (nTmpEnd < aEnd.back())
1285                         aEnd.push_back( nTmpEnd );
1286                     else if( aEnd.size() > 1 )
1287                         aEnd.pop_back();
1288                     else
1289                         aEnd.back() = nTmpEnd;
1290                 }
1291             }
1292         }
1293         if( !bOn && !aEnd.empty() )
1294             n2Start = aEnd.back();
1295 
1296         aEnd.clear();
1297 
1298         // now, search for the end of the ROTATE portion, similar to above
1299         bOn = true;
1300         if (pActiveRotateHint)
1301         {
1302             aRet.pItem = nullptr;
1303             aRet.pAttr = pActiveRotateHint;
1304             aRet.nStartOfAttr = m_pFrame->MapModelToView(startPos.first, aRet.pAttr->GetStart());
1305             if (pNodeRotateItem)
1306             {
1307                 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
1308                 bOn = static_cast<const SvxCharRotateItem*>(pNodeRotateItem)->GetValue() ==
1309                         pActiveRotateItem->GetValue();
1310             }
1311             else
1312             {
1313                 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *aRet.pAttr->End()));
1314             }
1315         }
1316         else
1317         {
1318             aRet.pItem = pNodeRotateItem;
1319             aRet.pAttr = nullptr;
1320             aRet.nStartOfAttr = TextFrameIndex(-1);
1321             aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
1322         }
1323         for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
1324         {
1325             SwTextNode const* pNode(nullptr);
1326             SwTextAttr const*const pTmp = iter.NextAttr(pNode);
1327             if (!pNode)
1328             {
1329                 break;
1330             }
1331             assert(startPos.first->GetIndex() <= pNode->GetIndex());
1332             TextFrameIndex nTmpStart;
1333             TextFrameIndex nTmpEnd;
1334             if (pTmp)
1335             {
1336                 nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
1337                 if (nTmpEnd <= rPos)
1338                     continue;
1339                 nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
1340             }
1341             else
1342             {
1343                 pNodeRotateItem = nullptr;
1344                 pNode->GetSwAttrSet().GetItemState(
1345                             RES_CHRATR_ROTATE, true, &pNodeRotateItem);
1346                 nTmpStart = m_pFrame->MapModelToView(pNode, 0);
1347                 nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
1348                 assert(rPos <= nTmpEnd); // next node must not have smaller index
1349             }
1350 
1351             if (rPos < nTmpStart)
1352             {
1353                 if (!bOn || aEnd.back() < nTmpStart)
1354                     break;
1355                 rPos = nTmpStart;
1356                 while( !aEnd.empty() && aEnd.back() <= rPos )
1357                 {
1358                     bOn = !bOn;
1359                     aEnd.pop_back();
1360                 }
1361                 if( aEnd.empty() )
1362                 {
1363                     aEnd.push_front( rPos );
1364                     bOn = true;
1365                 }
1366             }
1367             if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
1368             {
1369                 bOn = false;
1370                 break;
1371             }
1372             // TODO why does this use bTwo, not bRot ???
1373             if (pTmp ? lcl_HasRotation(*pTmp, pActiveRotateItem, bTwo)
1374                      : lcl_CheckRotation(pNodeRotateItem, pActiveRotateItem, bTwo))
1375             {
1376                 if( bTwo == bOn )
1377                 {
1378                     if (aEnd.back() < nTmpEnd)
1379                         aEnd.back() = nTmpEnd;
1380                 }
1381                 else
1382                 {
1383                     bOn = bTwo;
1384                     if (nTmpEnd < aEnd.back())
1385                         aEnd.push_back( nTmpEnd );
1386                     else if( aEnd.size() > 1 )
1387                         aEnd.pop_back();
1388                     else
1389                         aEnd.back() = nTmpEnd;
1390                 }
1391             }
1392         }
1393         if( bOn && !aEnd.empty() )
1394             rPos = aEnd.back();
1395         if( rPos > n2Start )
1396             rPos = n2Start;
1397         return aRet;
1398     }
1399     return {};
1400 }
1401 
1402 namespace {
1403 
1404 // A little helper class to manage the spaceadd-arrays of the text adjustment
1405 // during a PaintMultiPortion.
1406 // The constructor prepares the array for the first line of multiportion,
1407 // the SecondLine-function restores the values for the first line and prepares
1408 // the second line.
1409 // The destructor restores the values of the last manipulation.
1410 class SwSpaceManipulator
1411 {
1412     SwTextPaintInfo& m_rInfo;
1413     SwMultiPortion& m_rMulti;
1414     std::vector<tools::Long>* m_pOldSpaceAdd;
1415     sal_uInt16 m_nOldSpaceIndex;
1416     tools::Long m_nSpaceAdd;
1417     bool m_bSpaceChg;
1418     sal_uInt8 m_nOldDir;
1419 
1420 public:
1421     SwSpaceManipulator( SwTextPaintInfo& rInf, SwMultiPortion& rMult );
1422     ~SwSpaceManipulator();
1423     void SecondLine();
GetSpaceAdd() const1424     tools::Long GetSpaceAdd() const { return m_nSpaceAdd; }
1425 };
1426 
1427 }
1428 
SwSpaceManipulator(SwTextPaintInfo & rInf,SwMultiPortion & rMult)1429 SwSpaceManipulator::SwSpaceManipulator(SwTextPaintInfo& rInf, SwMultiPortion& rMult)
1430     : m_rInfo(rInf)
1431     , m_rMulti(rMult)
1432     , m_nSpaceAdd(0)
1433 {
1434     m_pOldSpaceAdd = m_rInfo.GetpSpaceAdd();
1435     m_nOldSpaceIndex = m_rInfo.GetSpaceIdx();
1436     m_nOldDir = m_rInfo.GetDirection();
1437     m_rInfo.SetDirection(m_rMulti.GetDirection());
1438     m_bSpaceChg = false;
1439 
1440     if (m_rMulti.IsDouble())
1441     {
1442         m_nSpaceAdd = (m_pOldSpaceAdd && !m_rMulti.HasTabulator()) ? m_rInfo.GetSpaceAdd() : 0;
1443         if (m_rMulti.GetRoot().IsSpaceAdd())
1444         {
1445             m_rInfo.SetpSpaceAdd(m_rMulti.GetRoot().GetpLLSpaceAdd());
1446             m_rInfo.ResetSpaceIdx();
1447             m_bSpaceChg = m_rMulti.ChgSpaceAdd(&m_rMulti.GetRoot(), m_nSpaceAdd);
1448         }
1449         else if (m_rMulti.HasTabulator())
1450             m_rInfo.SetpSpaceAdd(nullptr);
1451     }
1452     else if (!m_rMulti.IsBidi())
1453     {
1454         m_rInfo.SetpSpaceAdd(m_rMulti.GetRoot().GetpLLSpaceAdd());
1455         m_rInfo.ResetSpaceIdx();
1456     }
1457 }
1458 
SecondLine()1459 void SwSpaceManipulator::SecondLine()
1460 {
1461     if (m_bSpaceChg)
1462     {
1463         m_rInfo.RemoveFirstSpaceAdd();
1464         m_bSpaceChg = false;
1465     }
1466     SwLineLayout* pLay = m_rMulti.GetRoot().GetNext();
1467     if( pLay->IsSpaceAdd() )
1468     {
1469         m_rInfo.SetpSpaceAdd(pLay->GetpLLSpaceAdd());
1470         m_rInfo.ResetSpaceIdx();
1471         m_bSpaceChg = m_rMulti.ChgSpaceAdd(pLay, m_nSpaceAdd);
1472     }
1473     else
1474     {
1475         m_rInfo.SetpSpaceAdd((!m_rMulti.IsDouble() || m_rMulti.HasTabulator()) ? nullptr
1476                                                                                : m_pOldSpaceAdd);
1477         m_rInfo.SetSpaceIdx(m_nOldSpaceIndex);
1478     }
1479 }
1480 
~SwSpaceManipulator()1481 SwSpaceManipulator::~SwSpaceManipulator()
1482 {
1483     if (m_bSpaceChg)
1484     {
1485         m_rInfo.RemoveFirstSpaceAdd();
1486         m_bSpaceChg = false;
1487     }
1488     m_rInfo.SetpSpaceAdd(m_pOldSpaceAdd);
1489     m_rInfo.SetSpaceIdx(m_nOldSpaceIndex);
1490     m_rInfo.SetDirection(m_nOldDir);
1491 }
1492 
1493 // Manages the paint for a SwMultiPortion.
1494 // External, for the calling function, it seems to be a normal Paint-function,
1495 // internal it is like a SwTextFrame::PaintSwFrame with multiple DrawTextLines
PaintMultiPortion(const SwRect & rPaint,SwMultiPortion & rMulti,const SwMultiPortion * pEnvPor)1496 void SwTextPainter::PaintMultiPortion( const SwRect &rPaint,
1497     SwMultiPortion& rMulti, const SwMultiPortion* pEnvPor )
1498 {
1499     SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
1500     const bool bHasGrid = pGrid && GetInfo().SnapToGrid();
1501     sal_uInt16 nRubyHeight = 0;
1502     bool bRubyTop = true;
1503 
1504     if ( bHasGrid && pGrid->IsSquaredMode() )
1505     {
1506         nRubyHeight = pGrid->GetRubyHeight();
1507         bRubyTop = ! pGrid->GetRubyTextBelow();
1508     }
1509 
1510     // do not allow grid mode for first line in ruby portion
1511     const bool bRubyInGrid = bHasGrid && rMulti.IsRuby();
1512 
1513     const sal_uInt16 nOldHeight = rMulti.Height();
1514     const bool bOldGridModeAllowed = GetInfo().SnapToGrid();
1515 
1516     if ( bRubyInGrid )
1517     {
1518         GetInfo().SetSnapToGrid( ! bRubyTop );
1519         if (pGrid->IsSquaredMode())
1520             rMulti.Height( m_pCurr->Height() );
1521     }
1522 
1523     SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() );
1524     bool bEnvDir = false;
1525     bool bThisDir = false;
1526     bool bFrameDir = false;
1527     if ( rMulti.IsBidi() )
1528     {
1529         // these values are needed for the calculation of the x coordinate
1530         // and the layout mode
1531         OSL_ENSURE( ! pEnvPor || pEnvPor->IsBidi(),
1532                 "Oh no, I expected a BidiPortion" );
1533         bFrameDir = GetInfo().GetTextFrame()->IsRightToLeft();
1534         bEnvDir = pEnvPor ? ((static_cast<const SwBidiPortion*>(pEnvPor)->GetLevel() % 2) != 0) : bFrameDir;
1535         bThisDir = (static_cast<SwBidiPortion&>(rMulti).GetLevel() % 2) != 0;
1536     }
1537 
1538 #if OSL_DEBUG_LEVEL > 1
1539     // only paint first level bidi portions
1540     if( rMulti.Width() > 1 && ! pEnvPor )
1541         GetInfo().DrawViewOpt( rMulti, PortionType::Field );
1542 #endif
1543 
1544     if ( bRubyInGrid && pGrid->IsSquaredMode() )
1545         rMulti.Height( nOldHeight );
1546 
1547     // do we have to repaint a post it portion?
1548     if( GetInfo().OnWin() && rMulti.GetNextPortion() &&
1549         ! rMulti.GetNextPortion()->Width() )
1550         rMulti.GetNextPortion()->PrePaint( GetInfo(), &rMulti );
1551 
1552     // old values must be saved and restored at the end
1553     TextFrameIndex const nOldLen = GetInfo().GetLen();
1554     const SwTwips nOldX = GetInfo().X();
1555     const SwTwips nOldY = GetInfo().Y();
1556     TextFrameIndex const nOldIdx = GetInfo().GetIdx();
1557 
1558     SwSpaceManipulator aManip( GetInfo(), rMulti );
1559 
1560     std::unique_ptr<SwFontSave> pFontSave;
1561     std::unique_ptr<SwFont> pTmpFnt;
1562 
1563     if( rMulti.IsDouble() )
1564     {
1565         pTmpFnt.reset(new SwFont( *GetInfo().GetFont() ));
1566         if( rMulti.IsDouble() )
1567         {
1568             SetPropFont( 50 );
1569             pTmpFnt->SetProportion( GetPropFont() );
1570         }
1571         pFontSave.reset(new SwFontSave( GetInfo(), pTmpFnt.get(), this ));
1572     }
1573     else
1574     {
1575         pFontSave = nullptr;
1576         pTmpFnt = nullptr;
1577     }
1578 
1579     if( rMulti.HasBrackets() )
1580     {
1581         TextFrameIndex const nTmpOldIdx = GetInfo().GetIdx();
1582         GetInfo().SetIdx(static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart);
1583         SeekAndChg( GetInfo() );
1584         static_cast<SwDoubleLinePortion&>(rMulti).PaintBracket( GetInfo(), 0, true );
1585         GetInfo().SetIdx( nTmpOldIdx );
1586     }
1587 
1588     const SwTwips nTmpX = GetInfo().X();
1589 
1590     SwLineLayout* pLay = &rMulti.GetRoot();// the first line of the multiportion
1591     SwLinePortion* pPor = pLay->GetFirstPortion();//first portion of these line
1592     SwTwips nOfst = 0;
1593 
1594     // GetInfo().Y() is the baseline from the surrounding line. We must switch
1595     // this temporary to the baseline of the inner lines of the multiportion.
1596     if( rMulti.HasRotation() )
1597     {
1598         if( rMulti.IsRevers() )
1599         {
1600             GetInfo().Y( nOldY - rMulti.GetAscent() );
1601             nOfst = nTmpX + rMulti.Width();
1602         }
1603         else
1604         {
1605             GetInfo().Y( nOldY - rMulti.GetAscent() + rMulti.Height() );
1606             nOfst = nTmpX;
1607         }
1608     }
1609     else if ( rMulti.IsBidi() )
1610     {
1611         // does the current bidi portion has the same direction
1612         // as its environment?
1613         if ( bEnvDir != bThisDir )
1614         {
1615             // different directions, we have to adjust the x coordinate
1616             SwTwips nMultiWidth = rMulti.Width() +
1617                     rMulti.CalcSpacing( GetInfo().GetSpaceAdd(), GetInfo() );
1618 
1619             if ( bFrameDir == bThisDir )
1620                 GetInfo().X( GetInfo().X() - nMultiWidth );
1621             else
1622                 GetInfo().X( GetInfo().X() + nMultiWidth );
1623         }
1624 
1625         nOfst = nOldY - rMulti.GetAscent();
1626 
1627         // set layout mode
1628         aLayoutModeModifier.Modify( bThisDir );
1629     }
1630     else
1631         nOfst = nOldY - rMulti.GetAscent();
1632 
1633     bool bRest = pLay->IsRest();
1634     bool bFirst = true;
1635 
1636     OSL_ENSURE( nullptr == GetInfo().GetUnderFnt() || rMulti.IsBidi(),
1637             " Only BiDi portions are allowed to use the common underlining font" );
1638 
1639     if ( rMulti.IsRuby() )
1640         GetInfo().SetRuby( rMulti.OnTop() );
1641 
1642     do
1643     {
1644         if ( bHasGrid && pGrid->IsSquaredMode() )
1645         {
1646             if( rMulti.HasRotation() )
1647             {
1648                 const sal_uInt16 nAdjustment = ( pLay->Height() - pPor->Height() ) / 2 +
1649                                             pPor->GetAscent();
1650                 if( rMulti.IsRevers() )
1651                     GetInfo().X( nOfst - nAdjustment );
1652                 else
1653                     GetInfo().X( nOfst + nAdjustment );
1654             }
1655             else
1656             {
1657                 // special treatment for ruby portions in grid mode
1658                 SwTwips nAdjustment = 0;
1659                 if ( rMulti.IsRuby() )
1660                 {
1661                     if ( bRubyTop != ( pLay == &rMulti.GetRoot() ) )
1662                         // adjust base text
1663                         nAdjustment = ( m_pCurr->Height() - nRubyHeight - pPor->Height() ) / 2;
1664                     else if ( bRubyTop )
1665                         // adjust upper ruby text
1666                         nAdjustment = nRubyHeight - pPor->Height();
1667                     // else adjust lower ruby text
1668                 }
1669 
1670                 GetInfo().Y( nOfst + nAdjustment + pPor->GetAscent() );
1671             }
1672         }
1673         else if( rMulti.HasRotation() )
1674         {
1675             if( rMulti.IsRevers() )
1676                 GetInfo().X( nOfst - AdjustBaseLine( *pLay, pPor, 0, 0, true ) );
1677             else
1678                 GetInfo().X( nOfst + AdjustBaseLine( *pLay, pPor ) );
1679         }
1680         else if ( rMulti.IsRuby() && rMulti.OnRight() && GetInfo().IsRuby() )
1681         {
1682             SwTwips nLineDiff = std::max(( rMulti.GetRoot().Height() - pPor->Width() ) / 2, 0 );
1683             GetInfo().Y( nOfst + nLineDiff );
1684             // Draw the ruby text on top of the preserved space.
1685             GetInfo().X( GetInfo().X() - pPor->Height() );
1686         }
1687         else
1688             GetInfo().Y( nOfst + AdjustBaseLine( *pLay, pPor ) );
1689 
1690         bool bSeeked = true;
1691         GetInfo().SetLen( pPor->GetLen() );
1692 
1693         if( bRest && pPor->InFieldGrp() && !pPor->GetLen() )
1694         {
1695             if( static_cast<SwFieldPortion*>(pPor)->HasFont() )
1696                  bSeeked = false;
1697             else
1698                 SeekAndChgBefore( GetInfo() );
1699         }
1700         else if( pPor->InTextGrp() || pPor->InFieldGrp() || pPor->InTabGrp() )
1701             SeekAndChg( GetInfo() );
1702         else if ( !bFirst && pPor->IsBreakPortion() && GetInfo().GetOpt().IsParagraph() )
1703         {
1704             if( GetRedln() )
1705                 SeekAndChg( GetInfo() );
1706             else
1707                 SeekAndChgBefore( GetInfo() );
1708         }
1709         else
1710             bSeeked = false;
1711 
1712         SwLinePortion *pNext = pPor->GetNextPortion();
1713         if(GetInfo().OnWin() && pNext && !pNext->Width() )
1714         {
1715             if ( !bSeeked )
1716                 SeekAndChg( GetInfo() );
1717             pNext->PrePaint( GetInfo(), pPor );
1718         }
1719 
1720         CheckSpecialUnderline( pPor );
1721         SwUnderlineFont* pUnderLineFnt = GetInfo().GetUnderFnt();
1722         if ( pUnderLineFnt )
1723         {
1724             if ( rMulti.IsDouble() )
1725                 pUnderLineFnt->GetFont().SetProportion( 50 );
1726             pUnderLineFnt->SetPos( GetInfo().GetPos() );
1727         }
1728 
1729         if ( rMulti.IsBidi() )
1730         {
1731             // we do not allow any rotation inside a bidi portion
1732             SwFont* pTmpFont = GetInfo().GetFont();
1733             pTmpFont->SetVertical( 0_deg10, GetInfo().GetTextFrame()->IsVertical() );
1734         }
1735 
1736         if( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() )
1737         {
1738             // but we do allow nested bidi portions
1739             OSL_ENSURE( rMulti.IsBidi(), "Only nesting of bidi portions is allowed" );
1740             PaintMultiPortion( rPaint, static_cast<SwMultiPortion&>(*pPor), &rMulti );
1741         }
1742         else
1743             pPor->Paint( GetInfo() );
1744 
1745         bFirst &= !pPor->GetLen();
1746         if( pNext || !pPor->IsMarginPortion() )
1747             pPor->Move( GetInfo() );
1748 
1749         pPor = pNext;
1750 
1751         // If there's no portion left, we go to the next line
1752         if( !pPor && pLay->GetNext() )
1753         {
1754             pLay = pLay->GetNext();
1755             pPor = pLay->GetFirstPortion();
1756             bRest = pLay->IsRest();
1757             aManip.SecondLine();
1758 
1759             // delete underline font
1760             delete GetInfo().GetUnderFnt();
1761             GetInfo().SetUnderFnt( nullptr );
1762 
1763             if( rMulti.HasRotation() )
1764             {
1765                 if( rMulti.IsRevers() )
1766                 {
1767                     nOfst += pLay->Height();
1768                     GetInfo().Y( nOldY - rMulti.GetAscent() );
1769                 }
1770                 else
1771                 {
1772                     nOfst -= pLay->Height();
1773                     GetInfo().Y( nOldY - rMulti.GetAscent() + rMulti.Height() );
1774                 }
1775             }
1776             else if ( bHasGrid && rMulti.IsRuby() )
1777             {
1778                 GetInfo().SetSnapToGrid( bRubyTop );
1779                 GetInfo().X( nTmpX );
1780                 if (pGrid->IsSquaredMode() )
1781                 {
1782                     if ( bRubyTop )
1783                         nOfst += nRubyHeight;
1784                     else
1785                         nOfst += m_pCurr->Height() - nRubyHeight;
1786                 }
1787                 else
1788                 {
1789                     nOfst += rMulti.GetRoot().Height();
1790                 }
1791             }
1792             else if ( rMulti.IsRuby() && rMulti.OnRight() )
1793             {
1794                 GetInfo().SetDirection( DIR_TOP2BOTTOM );
1795                 GetInfo().SetRuby( true );
1796             } else
1797             {
1798                 GetInfo().X( nTmpX );
1799                 // We switch to the baseline of the next inner line
1800                 nOfst += rMulti.GetRoot().Height();
1801             }
1802         }
1803     } while( pPor );
1804 
1805     if ( bRubyInGrid )
1806         GetInfo().SetSnapToGrid( bOldGridModeAllowed );
1807 
1808     // delete underline font
1809     if ( ! rMulti.IsBidi() )
1810     {
1811         delete GetInfo().GetUnderFnt();
1812         GetInfo().SetUnderFnt( nullptr );
1813     }
1814 
1815     GetInfo().SetIdx( nOldIdx );
1816     GetInfo().Y( nOldY );
1817 
1818     if( rMulti.HasBrackets() )
1819     {
1820         TextFrameIndex const nTmpOldIdx = GetInfo().GetIdx();
1821         GetInfo().SetIdx(static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart);
1822         SeekAndChg( GetInfo() );
1823         GetInfo().X( nOldX );
1824         static_cast<SwDoubleLinePortion&>(rMulti).PaintBracket( GetInfo(),
1825             aManip.GetSpaceAdd(), false );
1826         GetInfo().SetIdx( nTmpOldIdx );
1827     }
1828     // Restore the saved values
1829     GetInfo().X( nOldX );
1830     GetInfo().SetLen( nOldLen );
1831     pFontSave.reset();
1832     pTmpFnt.reset();
1833     SetPropFont( 0 );
1834 }
1835 
lcl_ExtractFieldFollow(SwLineLayout * pLine,SwLinePortion * & rpField)1836 static bool lcl_ExtractFieldFollow( SwLineLayout* pLine, SwLinePortion* &rpField )
1837 {
1838     SwLinePortion* pLast = pLine;
1839     rpField = pLine->GetNextPortion();
1840     while( rpField && !rpField->InFieldGrp() )
1841     {
1842         pLast = rpField;
1843         rpField = rpField->GetNextPortion();
1844     }
1845     bool bRet = rpField != nullptr;
1846     if( bRet )
1847     {
1848         if( static_cast<SwFieldPortion*>(rpField)->IsFollow() )
1849         {
1850             rpField->Truncate();
1851             pLast->SetNextPortion( nullptr );
1852         }
1853         else
1854             rpField = nullptr;
1855     }
1856     pLine->Truncate();
1857     return bRet;
1858 }
1859 
1860 // If a multi portion completely has to go to the
1861 // next line, this function is called to truncate
1862 // the rest of the remaining multi portion
lcl_TruncateMultiPortion(SwMultiPortion & rMulti,SwTextFormatInfo & rInf,TextFrameIndex const nStartIdx)1863 static void lcl_TruncateMultiPortion( SwMultiPortion& rMulti, SwTextFormatInfo& rInf,
1864            TextFrameIndex const nStartIdx)
1865 {
1866     rMulti.GetRoot().Truncate();
1867     rMulti.GetRoot().SetLen(TextFrameIndex(0));
1868     rMulti.GetRoot().Width(0);
1869 //  rMulti.CalcSize( *this, aInf );
1870     if ( rMulti.GetRoot().GetNext() )
1871     {
1872         rMulti.GetRoot().GetNext()->Truncate();
1873         rMulti.GetRoot().GetNext()->SetLen(TextFrameIndex(0));
1874         rMulti.GetRoot().GetNext()->Width( 0 );
1875     }
1876     rMulti.Width( 0 );
1877     rMulti.SetLen(TextFrameIndex(0));
1878     rInf.SetIdx( nStartIdx );
1879 }
1880 
1881 // Manages the formatting of a SwMultiPortion. External, for the calling
1882 // function, it seems to be a normal Format-function, internal it is like a
1883 // SwTextFrame::Format_ with multiple BuildPortions
BuildMultiPortion(SwTextFormatInfo & rInf,SwMultiPortion & rMulti)1884 bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf,
1885     SwMultiPortion& rMulti )
1886 {
1887     SwTwips nMaxWidth = rInf.Width();
1888     SwTwips nOldX = 0;
1889 
1890     if( rMulti.HasBrackets() )
1891     {
1892         TextFrameIndex const nOldIdx = rInf.GetIdx();
1893         rInf.SetIdx( static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart );
1894         SeekAndChg( rInf );
1895         nOldX = GetInfo().X();
1896         static_cast<SwDoubleLinePortion&>(rMulti).FormatBrackets( rInf, nMaxWidth );
1897         rInf.SetIdx( nOldIdx );
1898     }
1899 
1900     SeekAndChg( rInf );
1901     std::unique_ptr<SwFontSave> xFontSave;
1902     std::unique_ptr<SwFont> xTmpFont;
1903     if( rMulti.IsDouble() )
1904     {
1905         xTmpFont.reset(new SwFont( *rInf.GetFont() ));
1906         if( rMulti.IsDouble() )
1907         {
1908             SetPropFont( 50 );
1909             xTmpFont->SetProportion( GetPropFont() );
1910         }
1911         xFontSave.reset(new SwFontSave(rInf, xTmpFont.get(), this));
1912     }
1913 
1914     SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() );
1915     if ( rMulti.IsBidi() )
1916     {
1917         // set layout mode
1918         aLayoutModeModifier.Modify( ! rInf.GetTextFrame()->IsRightToLeft() );
1919     }
1920 
1921     SwTwips nTmpX = 0;
1922 
1923     if( rMulti.HasRotation() )
1924     {
1925         // For nMaxWidth we take the height of the body frame.
1926         // #i25067#: If the current frame is inside a table, we restrict
1927         // nMaxWidth to the current frame height, unless the frame size
1928         // attribute is set to variable size:
1929 
1930         // We set nTmpX (which is used for portion calculating) to the
1931         // current Y value
1932         const SwPageFrame* pPage = m_pFrame->FindPageFrame();
1933         OSL_ENSURE( pPage, "No page in frame!");
1934         const SwLayoutFrame* pUpperFrame = pPage;
1935 
1936         if ( m_pFrame->IsInTab() )
1937         {
1938             pUpperFrame = m_pFrame->GetUpper();
1939             while ( pUpperFrame && !pUpperFrame->IsCellFrame() )
1940                 pUpperFrame = pUpperFrame->GetUpper();
1941             assert(pUpperFrame); //pFrame is in table but does not have an upper cell frame
1942             if (!pUpperFrame)
1943                 return false;
1944             const SwTableLine* pLine = static_cast<const SwRowFrame*>(pUpperFrame->GetUpper())->GetTabLine();
1945             const SwFormatFrameSize& rFrameFormatSize = pLine->GetFrameFormat()->GetFrameSize();
1946             if ( SwFrameSize::Variable == rFrameFormatSize.GetHeightSizeType() )
1947                 pUpperFrame = pPage;
1948         }
1949         if ( pUpperFrame == pPage && !m_pFrame->IsInFootnote() )
1950             pUpperFrame = pPage->FindBodyCont();
1951 
1952         nMaxWidth = pUpperFrame ?
1953                     ( rInf.GetTextFrame()->IsVertical() ?
1954                       pUpperFrame->getFramePrintArea().Width() :
1955                       pUpperFrame->getFramePrintArea().Height() ) :
1956                     USHRT_MAX;
1957     }
1958     else
1959         nTmpX = rInf.X();
1960 
1961     SwMultiPortion* pOldMulti = m_pMulti;
1962 
1963     m_pMulti = &rMulti;
1964     SwLineLayout *pOldCurr = m_pCurr;
1965     TextFrameIndex const nOldStart = GetStart();
1966     SwTwips nMinWidth = nTmpX + 1;
1967     SwTwips nActWidth = nMaxWidth;
1968     const TextFrameIndex nStartIdx = rInf.GetIdx();
1969     TextFrameIndex nMultiLen = rMulti.GetLen();
1970 
1971     SwLinePortion *pFirstRest;
1972     SwLinePortion *pSecondRest;
1973     if( rMulti.IsFormatted() )
1974     {
1975         if( !lcl_ExtractFieldFollow( &rMulti.GetRoot(), pFirstRest )
1976             && rMulti.IsDouble() && rMulti.GetRoot().GetNext() )
1977             lcl_ExtractFieldFollow( rMulti.GetRoot().GetNext(), pFirstRest );
1978         if( !rMulti.IsDouble() && rMulti.GetRoot().GetNext() )
1979             lcl_ExtractFieldFollow( rMulti.GetRoot().GetNext(), pSecondRest );
1980         else
1981             pSecondRest = nullptr;
1982     }
1983     else
1984     {
1985         pFirstRest = rMulti.GetRoot().GetNextPortion();
1986         pSecondRest = rMulti.GetRoot().GetNext() ?
1987                       rMulti.GetRoot().GetNext()->GetNextPortion() : nullptr;
1988         if( pFirstRest )
1989             rMulti.GetRoot().SetNextPortion( nullptr );
1990         if( pSecondRest )
1991             rMulti.GetRoot().GetNext()->SetNextPortion( nullptr );
1992         rMulti.SetFormatted();
1993         nMultiLen = nMultiLen - rInf.GetIdx();
1994     }
1995 
1996     // save some values
1997     const OUString* pOldText = &(rInf.GetText());
1998     const SwTwips nOldPaintOfst = rInf.GetPaintOfst();
1999     std::shared_ptr<vcl::TextLayoutCache> const pOldCachedVclData(rInf.GetCachedVclData());
2000     rInf.SetCachedVclData(nullptr);
2001 
2002     OUString const aMultiStr( rInf.GetText().copy(0, sal_Int32(nMultiLen + rInf.GetIdx())) );
2003     rInf.SetText( aMultiStr );
2004     SwTextFormatInfo aInf( rInf, rMulti.GetRoot(), nActWidth );
2005     // Do we allow break cuts? The FirstMulti-Flag is evaluated during
2006     // line break determination.
2007     bool bFirstMulti = rInf.GetIdx() != rInf.GetLineStart();
2008 
2009     SwLinePortion *pNextFirst = nullptr;
2010     SwLinePortion *pNextSecond = nullptr;
2011     bool bRet = false;
2012 
2013     SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
2014     const bool bHasGrid = pGrid && GRID_LINES_CHARS == pGrid->GetGridType();
2015 
2016     bool bRubyTop = false;
2017 
2018     if ( bHasGrid )
2019         bRubyTop = ! pGrid->GetRubyTextBelow();
2020 
2021     do
2022     {
2023         m_pCurr = &rMulti.GetRoot();
2024         m_nStart = nStartIdx;
2025         bRet = false;
2026         FormatReset( aInf );
2027         aInf.X( nTmpX );
2028         aInf.Width( sal_uInt16(nActWidth) );
2029         aInf.RealWidth( sal_uInt16(nActWidth) );
2030         aInf.SetFirstMulti( bFirstMulti );
2031         aInf.SetNumDone( rInf.IsNumDone() );
2032         aInf.SetFootnoteDone( rInf.IsFootnoteDone() );
2033 
2034         // if there's a bookmark at the start of the MultiPortion, it will be
2035         // painted with the rotation etc. of the MultiPortion; move it *inside*
2036         // so it gets positioned correctly; currently there's no other portion
2037         // inserted between the end of WhichFirstPortion() and
2038         // BuildMultiPortion()
2039         if (rInf.GetLast()->GetWhichPor() == PortionType::Bookmark)
2040         {
2041             auto const pBookmark(static_cast<SwBookmarkPortion*>(rInf.GetLast()));
2042             auto *const pPrevious = pBookmark->FindPrevPortion(rInf.GetRoot());
2043             assert(!pPrevious || pPrevious->GetNextPortion() == pBookmark);
2044             if (pPrevious)
2045             {
2046                 pPrevious->SetNextPortion(nullptr);
2047             }
2048             rInf.SetLast(pPrevious);
2049             assert(m_pCurr->GetNextPortion() == nullptr);
2050             m_pCurr->SetNextPortion(pBookmark);
2051         }
2052 
2053         if( pFirstRest )
2054         {
2055             OSL_ENSURE( pFirstRest->InFieldGrp(), "BuildMulti: Fieldrest expected");
2056             SwFieldPortion *pField =
2057                 static_cast<SwFieldPortion*>(pFirstRest)->Clone(
2058                     static_cast<SwFieldPortion*>(pFirstRest)->GetExp() );
2059             pField->SetFollow( true );
2060             aInf.SetRest( pField );
2061         }
2062         aInf.SetRuby( rMulti.IsRuby() && rMulti.OnTop() );
2063 
2064         // in grid mode we temporarily have to disable the grid for the ruby line
2065         const bool bOldGridModeAllowed = GetInfo().SnapToGrid();
2066         if ( bHasGrid && aInf.IsRuby() && bRubyTop )
2067             aInf.SetSnapToGrid( false );
2068 
2069         // If there's no more rubytext, then buildportion is forbidden
2070         if( pFirstRest || !aInf.IsRuby() )
2071             BuildPortions( aInf );
2072 
2073         aInf.SetSnapToGrid( bOldGridModeAllowed );
2074 
2075         rMulti.CalcSize( *this, aInf );
2076         m_pCurr->SetRealHeight( m_pCurr->Height() );
2077 
2078         if( rMulti.IsBidi() )
2079         {
2080             pNextFirst = aInf.GetRest();
2081             break;
2082         }
2083 
2084         if( rMulti.HasRotation() && !rMulti.IsDouble() )
2085             break;
2086         // second line has to be formatted
2087         else if( m_pCurr->GetLen()<nMultiLen || rMulti.IsRuby() || aInf.GetRest())
2088         {
2089             TextFrameIndex const nFirstLen = m_pCurr->GetLen();
2090             delete m_pCurr->GetNext();
2091             m_pCurr->SetNext( new SwLineLayout() );
2092             m_pCurr = m_pCurr->GetNext();
2093             m_nStart = aInf.GetIdx();
2094             aInf.X( nTmpX );
2095             SwTextFormatInfo aTmp( aInf, *m_pCurr, nActWidth );
2096             if( rMulti.IsRuby() )
2097             {
2098                 aTmp.SetRuby( !rMulti.OnTop() );
2099                 pNextFirst = aInf.GetRest();
2100                 if( pSecondRest )
2101                 {
2102                     OSL_ENSURE( pSecondRest->InFieldGrp(), "Fieldrest expected");
2103                     SwFieldPortion *pField = static_cast<SwFieldPortion*>(pSecondRest)->Clone(
2104                                     static_cast<SwFieldPortion*>(pSecondRest)->GetExp() );
2105                     pField->SetFollow( true );
2106                     aTmp.SetRest( pField );
2107                 }
2108                 if( !rMulti.OnTop() && nFirstLen < nMultiLen )
2109                     bRet = true;
2110             }
2111             else
2112                 aTmp.SetRest( aInf.GetRest() );
2113             aInf.SetRest( nullptr );
2114 
2115             // in grid mode we temporarily have to disable the grid for the ruby line
2116             if ( bHasGrid && aTmp.IsRuby() && ! bRubyTop )
2117                 aTmp.SetSnapToGrid( false );
2118 
2119             BuildPortions( aTmp );
2120 
2121             const SwLinePortion *pRightPortion = rMulti.OnRight() ?
2122                                                  rMulti.GetRoot().GetNext()->GetNextPortion() : nullptr;
2123             if (pRightPortion)
2124             {
2125                 // The ruby text on the right is vertical.
2126                 // The width and the height are swapped.
2127                 SwTwips nHeight = pRightPortion->Height();
2128                 // Keep room for the ruby text.
2129                 rMulti.GetRoot().FindLastPortion()->AddPrtWidth( nHeight );
2130             }
2131 
2132             aTmp.SetSnapToGrid( bOldGridModeAllowed );
2133 
2134             rMulti.CalcSize( *this, aInf );
2135             rMulti.GetRoot().SetRealHeight( rMulti.GetRoot().Height() );
2136             m_pCurr->SetRealHeight( m_pCurr->Height() );
2137             if( rMulti.IsRuby() )
2138             {
2139                 pNextSecond = aTmp.GetRest();
2140                 if( pNextFirst )
2141                     bRet = true;
2142             }
2143             else
2144                 pNextFirst = aTmp.GetRest();
2145             if( ( !aTmp.IsRuby() && nFirstLen + m_pCurr->GetLen() < nMultiLen )
2146                 || aTmp.GetRest() )
2147                 // our guess for width of multiportion was too small,
2148                 // text did not fit into multiportion
2149                 bRet = true;
2150         }
2151         if( rMulti.IsRuby() )
2152             break;
2153         if( bRet )
2154         {
2155             // our guess for multiportion width was too small,
2156             // we set min to act
2157             nMinWidth = nActWidth;
2158             nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4;
2159             if ( nActWidth == nMaxWidth && rInf.GetLineStart() == rInf.GetIdx() )
2160             // we have too less space, we must allow break cuts
2161             // ( the first multi flag is considered during TextPortion::Format_() )
2162                 bFirstMulti = false;
2163             if( nActWidth <= nMinWidth )
2164                 break;
2165         }
2166         else
2167         {
2168             // For Solaris, this optimization can causes trouble:
2169             // Setting this to the portion width ( = rMulti.Width() )
2170             // can make GetTextBreak inside SwTextGuess::Guess return too small
2171             // values. Therefore we add some extra twips.
2172             if( nActWidth > nTmpX + rMulti.Width() + 6 )
2173                 nActWidth = nTmpX + rMulti.Width() + 6;
2174             nMaxWidth = nActWidth;
2175             nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4;
2176             if( nActWidth >= nMaxWidth )
2177                 break;
2178             // we do not allow break cuts during formatting
2179             bFirstMulti = true;
2180         }
2181         delete pNextFirst;
2182         pNextFirst = nullptr;
2183     } while ( true );
2184 
2185     m_pMulti = pOldMulti;
2186 
2187     m_pCurr = pOldCurr;
2188     m_nStart = nOldStart;
2189     SetPropFont( 0 );
2190 
2191     rMulti.SetLen( rMulti.GetRoot().GetLen() + ( rMulti.GetRoot().GetNext() ?
2192         rMulti.GetRoot().GetNext()->GetLen() : TextFrameIndex(0) ) );
2193 
2194     if( rMulti.IsDouble() )
2195     {
2196         static_cast<SwDoubleLinePortion&>(rMulti).CalcBlanks( rInf );
2197         if( static_cast<SwDoubleLinePortion&>(rMulti).GetLineDiff() )
2198         {
2199             SwLineLayout* pLine = &rMulti.GetRoot();
2200             if( static_cast<SwDoubleLinePortion&>(rMulti).GetLineDiff() > 0 )
2201             {
2202                 rInf.SetIdx( nStartIdx + pLine->GetLen() );
2203                 pLine = pLine->GetNext();
2204             }
2205             if( pLine )
2206             {
2207                 GetInfo().SetMulti( true );
2208 
2209                 // If the fourth element bSkipKashida of function CalcNewBlock is true, multiportion will be showed in justification.
2210                 // Kashida (Persian) is a type of justification used in some cursive scripts, particularly Arabic.
2211                 // In contrast to white-space justification, which increases the length of a line of text by expanding spaces between words or individual letters,
2212                 // kashida justification is accomplished by elongating characters at certain chosen points.
2213                 // Kashida justification can be combined with white-space justification to various extents.
2214                 // The default value of bSkipKashida (the 4th parameter passed to 'CalcNewBlock') is false.
2215                 // Only when Adjust is SvxAdjust::Block ( alignment is justify ), multiportion will be showed in justification in new code.
2216                 CalcNewBlock( pLine, nullptr, rMulti.Width(), GetAdjust() != SvxAdjust::Block );
2217 
2218                 GetInfo().SetMulti( false );
2219             }
2220             rInf.SetIdx( nStartIdx );
2221         }
2222         if( static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets() )
2223         {
2224             rMulti.Width( rMulti.Width() +
2225                     static_cast<SwDoubleLinePortion&>(rMulti).BracketWidth() );
2226             GetInfo().X( nOldX );
2227         }
2228     }
2229     else
2230     {
2231         rMulti.ActualizeTabulator();
2232         if( rMulti.IsRuby() )
2233         {
2234             static_cast<SwRubyPortion&>(rMulti).Adjust( rInf );
2235             static_cast<SwRubyPortion&>(rMulti).CalcRubyOffset();
2236         }
2237     }
2238     if( rMulti.HasRotation() )
2239     {
2240         SwTwips nH = rMulti.Width();
2241         SwTwips nAsc = rMulti.GetAscent() + ( nH - rMulti.Height() )/2;
2242         if( nAsc > nH )
2243             nAsc = nH;
2244         else if( nAsc < 0 )
2245             nAsc = 0;
2246         rMulti.Width( rMulti.Height() );
2247         rMulti.Height( sal_uInt16(nH) );
2248         rMulti.SetAscent( sal_uInt16(nAsc) );
2249         bRet = ( rInf.GetPos().X() + rMulti.Width() > rInf.Width() ) &&
2250                  nStartIdx != rInf.GetLineStart();
2251     }
2252     else if ( rMulti.IsBidi() )
2253     {
2254         bRet = rMulti.GetLen() < nMultiLen || pNextFirst;
2255     }
2256 
2257     // line break has to be performed!
2258     if( bRet )
2259     {
2260         OSL_ENSURE( !pNextFirst || pNextFirst->InFieldGrp(),
2261             "BuildMultiPortion: Surprising restportion, field expected" );
2262         SwMultiPortion *pTmp;
2263         if( rMulti.IsDouble() )
2264             pTmp = new SwDoubleLinePortion( static_cast<SwDoubleLinePortion&>(rMulti),
2265                                             nMultiLen + rInf.GetIdx() );
2266         else if( rMulti.IsRuby() )
2267         {
2268             OSL_ENSURE( !pNextSecond || pNextSecond->InFieldGrp(),
2269                 "BuildMultiPortion: Surprising restportion, field expected" );
2270 
2271             if ( rInf.GetIdx() == rInf.GetLineStart() )
2272             {
2273                 // the ruby portion has to be split in two portions
2274                 pTmp = new SwRubyPortion( static_cast<SwRubyPortion&>(rMulti),
2275                                           nMultiLen + rInf.GetIdx() );
2276 
2277                 if( pNextSecond )
2278                 {
2279                     pTmp->GetRoot().SetNext( new SwLineLayout() );
2280                     pTmp->GetRoot().GetNext()->SetNextPortion( pNextSecond );
2281                 }
2282                 pTmp->SetFollowField();
2283             }
2284             else
2285             {
2286                 // we try to keep our ruby portion together
2287                 lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
2288                 pTmp = nullptr;
2289             }
2290         }
2291         else if( rMulti.HasRotation() )
2292         {
2293             // we try to keep our rotated portion together
2294             lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
2295             pTmp = new SwRotatedPortion( nMultiLen + rInf.GetIdx(),
2296                                          rMulti.GetDirection() );
2297         }
2298         // during a recursion of BuildMultiPortions we may not build
2299         // a new SwBidiPortion, this would cause a memory leak
2300         else if( rMulti.IsBidi() && ! m_pMulti )
2301         {
2302             if ( ! rMulti.GetLen() )
2303                 lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
2304 
2305             // If there is a HolePortion at the end of the bidi portion,
2306             // it has to be moved behind the bidi portion. Otherwise
2307             // the visual cursor travelling gets into trouble.
2308             SwLineLayout& aRoot = rMulti.GetRoot();
2309             SwLinePortion* pPor = aRoot.GetFirstPortion();
2310             while ( pPor )
2311             {
2312                 if ( pPor->GetNextPortion() && pPor->GetNextPortion()->IsHolePortion() )
2313                 {
2314                     SwLinePortion* pHolePor = pPor->GetNextPortion();
2315                     pPor->SetNextPortion( nullptr );
2316                     aRoot.SetLen( aRoot.GetLen() - pHolePor->GetLen() );
2317                     rMulti.SetLen( rMulti.GetLen() - pHolePor->GetLen() );
2318                     rMulti.SetNextPortion( pHolePor );
2319                     break;
2320                 }
2321                 pPor = pPor->GetNextPortion();
2322             }
2323 
2324             pTmp = new SwBidiPortion( nMultiLen + rInf.GetIdx(),
2325                                     static_cast<SwBidiPortion&>(rMulti).GetLevel() );
2326         }
2327         else
2328             pTmp = nullptr;
2329 
2330         if ( ! rMulti.GetLen() && rInf.GetLast() )
2331         {
2332             SeekAndChgBefore( rInf );
2333             rInf.GetLast()->FormatEOL( rInf );
2334         }
2335 
2336         if( pNextFirst && pTmp )
2337         {
2338             pTmp->SetFollowField();
2339             pTmp->GetRoot().SetNextPortion( pNextFirst );
2340         }
2341         else
2342             // A follow field portion is still waiting. If nobody wants it,
2343             // we delete it.
2344             delete pNextFirst;
2345 
2346         rInf.SetRest( pTmp );
2347     }
2348 
2349     rInf.SetCachedVclData(pOldCachedVclData);
2350     rInf.SetText( *pOldText );
2351     rInf.SetPaintOfst( nOldPaintOfst );
2352     rInf.SetStop( aInf.IsStop() );
2353     rInf.SetNumDone( true );
2354     rInf.SetFootnoteDone( true );
2355     SeekAndChg( rInf );
2356     delete pFirstRest;
2357     delete pSecondRest;
2358     xFontSave.reset();
2359     return bRet;
2360 }
2361 
2362 // When a fieldportion at the end of line breaks and needs a following
2363 // fieldportion in the next line, then the "restportion" of the formatinfo
2364 // has to be set. Normally this happens during the formatting of the first
2365 // part of the fieldportion.
2366 // But sometimes the formatting starts at the line with the following part,
2367 // especially when the following part is on the next page.
2368 // In this case the MakeRestPortion-function has to create the following part.
2369 // The first parameter is the line that contains possibly a first part
2370 // of a field. When the function finds such field part, it creates the right
2371 // restportion. This may be a multiportion, e.g. if the field is surrounded by
2372 // a doubleline- or ruby-portion.
2373 // The second parameter is the start index of the line.
MakeRestPortion(const SwLineLayout * pLine,TextFrameIndex nPosition)2374 SwLinePortion* SwTextFormatter::MakeRestPortion( const SwLineLayout* pLine,
2375     TextFrameIndex nPosition)
2376 {
2377     if( !nPosition )
2378         return nullptr;
2379     TextFrameIndex nMultiPos = nPosition - pLine->GetLen();
2380     const SwMultiPortion *pTmpMulti = nullptr;
2381     const SwMultiPortion *pHelpMulti = nullptr;
2382     const SwLinePortion* pPor = pLine->GetFirstPortion();
2383     SwFieldPortion *pField = nullptr;
2384     while( pPor )
2385     {
2386         if( pPor->GetLen() && !pHelpMulti )
2387         {
2388             nMultiPos = nMultiPos + pPor->GetLen();
2389             pTmpMulti = nullptr;
2390         }
2391         if( pPor->InFieldGrp() )
2392         {
2393             if( !pHelpMulti )
2394                 pTmpMulti = nullptr;
2395             pField = const_cast<SwFieldPortion*>(static_cast<const SwFieldPortion*>(pPor));
2396         }
2397         else if( pPor->IsMultiPortion() )
2398         {
2399             OSL_ENSURE( !pHelpMulti || pHelpMulti->IsBidi(),
2400                     "Nested multiportions are forbidden." );
2401 
2402             pField = nullptr;
2403             pTmpMulti = static_cast<const SwMultiPortion*>(pPor);
2404         }
2405         pPor = pPor->GetNextPortion();
2406         // If the last portion is a multi-portion, we enter it
2407         // and look for a field portion inside.
2408         // If we are already in a multiportion, we could change to the
2409         // next line
2410         if( !pPor && pTmpMulti )
2411         {
2412             if( pHelpMulti )
2413             {   // We're already inside the multiportion, let's take the second
2414                 // line, if we are in a double line portion
2415                 if( !pHelpMulti->IsRuby() )
2416                     pPor = pHelpMulti->GetRoot().GetNext();
2417                 pTmpMulti = nullptr;
2418             }
2419             else
2420             {   // Now we enter a multiportion, in a ruby portion we take the
2421                 // main line, not the phonetic line, in a doublelineportion we
2422                 // starts with the first line.
2423                 pHelpMulti = pTmpMulti;
2424                 nMultiPos = nMultiPos - pHelpMulti->GetLen();
2425                 if( pHelpMulti->IsRuby() && pHelpMulti->OnTop() )
2426                     pPor = pHelpMulti->GetRoot().GetNext();
2427                 else
2428                     pPor = pHelpMulti->GetRoot().GetFirstPortion();
2429             }
2430         }
2431     }
2432     if( pField && !pField->HasFollow() )
2433         pField = nullptr;
2434 
2435     SwLinePortion *pRest = nullptr;
2436     if( pField )
2437     {
2438         const SwTextAttr *pHint = GetAttr(nPosition - TextFrameIndex(1));
2439         if ( pHint
2440              && ( pHint->Which() == RES_TXTATR_FIELD
2441                   || pHint->Which() == RES_TXTATR_ANNOTATION ) )
2442         {
2443             pRest = NewFieldPortion( GetInfo(), pHint );
2444             if( pRest->InFieldGrp() )
2445                 static_cast<SwFieldPortion*>(pRest)->TakeNextOffset( pField );
2446             else
2447             {
2448                 delete pRest;
2449                 pRest = nullptr;
2450             }
2451         }
2452     }
2453     if( !pHelpMulti )
2454         return pRest;
2455 
2456     nPosition = nMultiPos + pHelpMulti->GetLen();
2457     std::optional<SwMultiCreator> pCreate = GetInfo().GetMultiCreator( nMultiPos, nullptr );
2458 
2459     if ( !pCreate )
2460     {
2461         OSL_ENSURE( !pHelpMulti->GetLen(), "Multiportion without attribute?" );
2462         if ( nMultiPos )
2463             --nMultiPos;
2464         pCreate = GetInfo().GetMultiCreator( --nMultiPos, nullptr );
2465     }
2466 
2467     if (!pCreate)
2468         return pRest;
2469 
2470     if( pRest || nMultiPos > nPosition || ( pHelpMulti->IsRuby() &&
2471         static_cast<const SwRubyPortion*>(pHelpMulti)->GetRubyOffset() < TextFrameIndex(COMPLETE_STRING)))
2472     {
2473         SwMultiPortion* pTmp;
2474         if( pHelpMulti->IsDouble() )
2475             pTmp = new SwDoubleLinePortion( *pCreate, nMultiPos );
2476         else if( pHelpMulti->IsBidi() )
2477             pTmp = new SwBidiPortion( nMultiPos, pCreate->nLevel );
2478         else if( pHelpMulti->IsRuby() )
2479         {
2480             pTmp = new SwRubyPortion( *pCreate, *GetInfo().GetFont(),
2481                                        m_pFrame->GetDoc().getIDocumentSettingAccess(),
2482                                        nMultiPos, static_cast<const SwRubyPortion*>(pHelpMulti)->GetRubyOffset(),
2483                                        GetInfo() );
2484         }
2485         else if( pHelpMulti->HasRotation() )
2486             pTmp = new SwRotatedPortion( nMultiPos, pHelpMulti->GetDirection() );
2487         else
2488         {
2489             return pRest;
2490         }
2491         pCreate.reset();
2492         pTmp->SetFollowField();
2493         if( pRest )
2494         {
2495             SwLineLayout *pLay = &pTmp->GetRoot();
2496             if( pTmp->IsRuby() && pTmp->OnTop() )
2497             {
2498                 pLay->SetNext( new SwLineLayout() );
2499                 pLay = pLay->GetNext();
2500             }
2501             pLay->SetNextPortion( pRest );
2502         }
2503         return pTmp;
2504     }
2505     return pRest;
2506 }
2507 
2508 // SwTextCursorSave notes the start and current line of a SwTextCursor,
2509 // sets them to the values for GetModelPositionForViewPoint inside a multiportion
2510 // and restores them in the destructor.
SwTextCursorSave(SwTextCursor * pCursor,SwMultiPortion * pMulti,SwTwips nY,sal_uInt16 & nX,TextFrameIndex const nCurrStart,tools::Long nSpaceAdd)2511 SwTextCursorSave::SwTextCursorSave( SwTextCursor* pCursor,
2512                                   SwMultiPortion* pMulti,
2513                                   SwTwips nY,
2514                                   sal_uInt16& nX,
2515                                   TextFrameIndex const nCurrStart,
2516                                   tools::Long nSpaceAdd )
2517   : pTextCursor(pCursor),
2518     pCurr(pCursor->m_pCurr),
2519     nStart(pCursor->m_nStart)
2520 {
2521     pCursor->m_nStart = nCurrStart;
2522     pCursor->m_pCurr = &pMulti->GetRoot();
2523     while( pCursor->Y() + pCursor->GetLineHeight() < nY &&
2524         pCursor->Next() )
2525         ; // nothing
2526     nWidth = pCursor->m_pCurr->Width();
2527     nOldProp = pCursor->GetPropFont();
2528 
2529     if ( pMulti->IsDouble() || pMulti->IsBidi() )
2530     {
2531         bSpaceChg = pMulti->ChgSpaceAdd( pCursor->m_pCurr, nSpaceAdd );
2532 
2533         TextFrameIndex nSpaceCnt;
2534         if ( pMulti->IsDouble() )
2535         {
2536             pCursor->SetPropFont( 50 );
2537             nSpaceCnt = static_cast<SwDoubleLinePortion*>(pMulti)->GetSpaceCnt();
2538         }
2539         else
2540         {
2541             TextFrameIndex const nOldIdx = pCursor->GetInfo().GetIdx();
2542             pCursor->GetInfo().SetIdx ( nCurrStart );
2543             nSpaceCnt = static_cast<SwBidiPortion*>(pMulti)->GetSpaceCnt(pCursor->GetInfo());
2544             pCursor->GetInfo().SetIdx ( nOldIdx );
2545         }
2546 
2547         if( nSpaceAdd > 0 && !pMulti->HasTabulator() )
2548             pCursor->m_pCurr->Width( static_cast<sal_uInt16>(nWidth + nSpaceAdd * sal_Int32(nSpaceCnt) / SPACING_PRECISION_FACTOR) );
2549 
2550         // For a BidiPortion we have to calculate the offset from the
2551         // end of the portion
2552         if ( nX && pMulti->IsBidi() )
2553             nX = pCursor->m_pCurr->Width() - nX;
2554     }
2555     else
2556         bSpaceChg = false;
2557 }
2558 
~SwTextCursorSave()2559 SwTextCursorSave::~SwTextCursorSave()
2560 {
2561     if( bSpaceChg )
2562         SwDoubleLinePortion::ResetSpaceAdd( pTextCursor->m_pCurr );
2563     pTextCursor->m_pCurr->Width( nWidth );
2564     pTextCursor->m_pCurr = pCurr;
2565     pTextCursor->m_nStart = nStart;
2566     pTextCursor->SetPropFont( nOldProp );
2567 }
2568 
2569 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2570