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