sc: factor out some more code
[LibreOffice.git] / sw / source / core / text / pormulti.cxx
blob50e9a13ebd4a23a1f2e2667d90c937fdd17302e7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <deque>
21 #include <memory>
23 #include <hintids.hxx>
25 #include <editeng/twolinesitem.hxx>
26 #include <editeng/charrotateitem.hxx>
27 #include <vcl/outdev.hxx>
28 #include <txatbase.hxx>
29 #include <fmtruby.hxx>
30 #include <txtatr.hxx>
31 #include <charfmt.hxx>
32 #include <layfrm.hxx>
33 #include <SwPortionHandler.hxx>
34 #include <EnhancedPDFExportHelper.hxx>
35 #include <com/sun/star/i18n/BreakType.hpp>
36 #include <com/sun/star/i18n/XBreakIterator.hpp>
37 #include <breakit.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 "porrst.hxx"
46 #include <pagefrm.hxx>
47 #include <rowfrm.hxx>
48 #include <tgrditem.hxx>
49 #include <swtable.hxx>
50 #include <fmtfsize.hxx>
51 #include <doc.hxx>
53 using namespace ::com::sun::star;
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.
59 SwMultiPortion::~SwMultiPortion()
63 void SwMultiPortion::Paint( const SwTextPaintInfo & ) const
65 OSL_FAIL( "Don't try SwMultiPortion::Paint, try SwTextPainter::PaintMultiPortion" );
68 // Summarize the internal lines to calculate the (external) size.
69 // The internal line has to calculate first.
70 void SwMultiPortion::CalcSize( SwTextFormatter& rLine, SwTextFormatInfo &rInf )
72 Width( 0 );
73 Height( 0 );
74 SetAscent( 0 );
75 SetFlyInContent( false );
76 SwLineLayout *pLay = &GetRoot();
79 pLay->CalcLine( rLine, rInf );
80 if( rLine.IsFlyInCntBase() )
81 SetFlyInContent( true );
82 if( IsRuby() && ( OnTop() == ( pLay == &GetRoot() ) ) )
84 // An empty phonetic line don't need an ascent or a height.
85 if( !pLay->Width() )
87 pLay->SetAscent( 0 );
88 pLay->Height( 0 );
90 if( OnTop() )
91 SetAscent( GetAscent() + pLay->Height() );
93 else
94 SetAscent( GetAscent() + pLay->GetAscent() );
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
101 // We already added the width after building the portion,
102 // so no need to add it twice.
103 break;
106 if( Width() < pLay->Width() )
107 Width( pLay->Width() );
108 pLay = pLay->GetNext();
109 } while ( pLay );
110 if( !HasBrackets() )
111 return;
113 sal_uInt16 nTmp = static_cast<SwDoubleLinePortion*>(this)->GetBrackets()->nHeight;
114 if( nTmp > Height() )
116 const sal_uInt16 nAdd = ( nTmp - Height() ) / 2;
117 GetRoot().SetAscent( GetRoot().GetAscent() + nAdd );
118 GetRoot().Height( GetRoot().Height() + nAdd );
119 Height( nTmp );
121 nTmp = static_cast<SwDoubleLinePortion*>(this)->GetBrackets()->nAscent;
122 if( nTmp > GetAscent() )
123 SetAscent( nTmp );
126 SwTwips SwMultiPortion::CalcSpacing( tools::Long , const SwTextSizeInfo & ) const
128 return 0;
131 bool SwMultiPortion::ChgSpaceAdd( SwLineLayout*, tools::Long ) const
133 return false;
136 void SwMultiPortion::HandlePortion( SwPortionHandler& rPH ) const
138 rPH.Text( GetLen(), GetWhichPor() );
141 void SwMultiPortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
142 TextFrameIndex& nOffset) const
144 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwMultiPortion"));
145 dumpAsXmlAttributes(pWriter, rText, nOffset);
146 // Intentionally not incrementing nOffset here, one of the child portions will do that.
148 const SwLineLayout* pLine = &GetRoot();
149 while (pLine)
151 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwLineLayout"));
152 pLine->dumpAsXmlAttributes(pWriter, rText, nOffset);
153 const SwLinePortion* pPor = pLine->GetFirstPortion();
154 while (pPor)
156 pPor->dumpAsXml(pWriter, rText, nOffset);
157 pPor = pPor->GetNextPortion();
159 (void)xmlTextWriterEndElement(pWriter);
160 pLine = pLine->GetNext();
163 (void)xmlTextWriterEndElement(pWriter);
166 // sets the tabulator-flag, if there's any tabulator-portion inside.
167 void SwMultiPortion::ActualizeTabulator()
169 SwLinePortion* pPor = GetRoot().GetFirstPortion();
170 // First line
171 for( m_bTab1 = m_bTab2 = false; pPor; pPor = pPor->GetNextPortion() )
172 if( pPor->InTabGrp() )
173 SetTab1( true );
174 if( GetRoot().GetNext() )
176 // Second line
177 pPor = GetRoot().GetNext()->GetFirstPortion();
180 if( pPor->InTabGrp() )
181 SetTab2( true );
182 pPor = pPor->GetNextPortion();
183 } while ( pPor );
187 SwRotatedPortion::SwRotatedPortion( const SwMultiCreator& rCreate,
188 TextFrameIndex const nEnd, bool bRTL )
189 : SwMultiPortion( nEnd )
191 const SvxCharRotateItem* pRot = static_cast<const SvxCharRotateItem*>(rCreate.pItem);
192 if( !pRot )
194 const SwTextAttr& rAttr = *rCreate.pAttr;
195 const SfxPoolItem *const pItem =
196 CharFormat::GetItem(rAttr, RES_CHRATR_ROTATE);
197 if ( pItem )
199 pRot = static_cast<const SvxCharRotateItem*>(pItem);
202 if( pRot )
204 sal_uInt8 nDir;
205 if ( bRTL )
206 nDir = pRot->IsBottomToTop() ? 3 : 1;
207 else
208 nDir = pRot->IsBottomToTop() ? 1 : 3;
210 SetDirection( nDir );
214 SwBidiPortion::SwBidiPortion(TextFrameIndex const nEnd, sal_uInt8 nLv)
215 : SwMultiPortion( nEnd ), m_nLevel( nLv )
217 SetBidi();
219 if ( m_nLevel % 2 )
220 SetDirection( DIR_RIGHT2LEFT );
221 else
222 SetDirection( DIR_LEFT2RIGHT );
225 SwTwips SwBidiPortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo& rInf ) const
227 nSpaceAdd = nSpaceAdd > LONG_MAX/2 ? LONG_MAX/2 - nSpaceAdd : nSpaceAdd;
228 return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt(rInf)) * nSpaceAdd / SPACING_PRECISION_FACTOR;
231 bool SwBidiPortion::ChgSpaceAdd( SwLineLayout* pCurr, tools::Long nSpaceAdd ) const
233 if( !HasTabulator() && nSpaceAdd > 0 && !pCurr->IsSpaceAdd() )
235 pCurr->CreateSpaceAdd();
236 pCurr->SetLLSpaceAdd( nSpaceAdd, 0 );
237 return true;
240 return false;
243 TextFrameIndex SwBidiPortion::GetSpaceCnt(const SwTextSizeInfo &rInf) const
245 // Calculate number of blanks for justified alignment
246 TextFrameIndex nTmpStart = rInf.GetIdx();
247 TextFrameIndex nNull(0);
248 TextFrameIndex nBlanks(0);
250 for (SwLinePortion* pPor = GetRoot().GetFirstPortion(); pPor; pPor = pPor->GetNextPortion())
252 if( pPor->InTextGrp() )
253 nBlanks = nBlanks + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
254 else if ( pPor->IsMultiPortion() &&
255 static_cast<SwMultiPortion*>(pPor)->IsBidi() )
256 nBlanks = nBlanks + static_cast<SwBidiPortion*>(pPor)->GetSpaceCnt( rInf );
258 const_cast<SwTextSizeInfo &>(rInf).SetIdx( rInf.GetIdx() + pPor->GetLen() );
260 const_cast<SwTextSizeInfo &>(rInf).SetIdx( nTmpStart );
261 return nBlanks;
264 // This constructor is for the continuation of a doubleline portion
265 // in the next line.
266 // It takes the same brackets and if the original has no content except
267 // brackets, these will be deleted.
268 SwDoubleLinePortion::SwDoubleLinePortion(
269 SwDoubleLinePortion& rDouble, TextFrameIndex const nEnd)
270 : SwMultiPortion(nEnd)
271 , m_nLineDiff(0)
272 , m_nBlank1(0)
273 , m_nBlank2(0)
275 SetDirection( rDouble.GetDirection() );
276 SetDouble();
277 if( rDouble.GetBrackets() )
279 SetBrackets( rDouble );
280 // An empty multiportion needs no brackets.
281 // Notice: GetLen() might be zero, if the multiportion contains
282 // the second part of a field and the width might be zero, if
283 // it contains a note only. In this cases the brackets are okay.
284 // But if the length and the width are both zero, the portion
285 // is really empty.
286 if( rDouble.Width() == rDouble.BracketWidth() )
287 rDouble.ClearBrackets();
291 // This constructor uses the textattribute to get the right brackets.
292 // The textattribute could be a 2-line-attribute or a character- or
293 // internet style, which contains the 2-line-attribute.
294 SwDoubleLinePortion::SwDoubleLinePortion(
295 const SwMultiCreator& rCreate, TextFrameIndex const nEnd)
296 : SwMultiPortion(nEnd)
297 , m_pBracket(new SwBracket)
298 , m_nLineDiff(0)
299 , m_nBlank1(0)
300 , m_nBlank2(0)
302 m_pBracket->nAscent = 0;
303 m_pBracket->nHeight = 0;
304 m_pBracket->nPreWidth = 0;
305 m_pBracket->nPostWidth = 0;
307 SetDouble();
308 const SvxTwoLinesItem* pTwo = static_cast<const SvxTwoLinesItem*>(rCreate.pItem);
309 if( pTwo )
310 m_pBracket->nStart = TextFrameIndex(0);
311 else
313 const SwTextAttr& rAttr = *rCreate.pAttr;
314 m_pBracket->nStart = rCreate.nStartOfAttr;
316 const SfxPoolItem * const pItem =
317 CharFormat::GetItem( rAttr, RES_CHRATR_TWO_LINES );
318 if ( pItem )
320 pTwo = static_cast<const SvxTwoLinesItem*>(pItem);
323 if( pTwo )
325 m_pBracket->cPre = pTwo->GetStartBracket();
326 m_pBracket->cPost = pTwo->GetEndBracket();
328 else
330 m_pBracket->cPre = 0;
331 m_pBracket->cPost = 0;
333 SwFontScript nTmp = SW_SCRIPTS;
334 if( m_pBracket->cPre > 255 )
336 OUString aText(m_pBracket->cPre);
337 nTmp = SwScriptInfo::WhichFont(0, aText);
339 m_pBracket->nPreScript = nTmp;
340 nTmp = SW_SCRIPTS;
341 if( m_pBracket->cPost > 255 )
343 OUString aText(m_pBracket->cPost);
344 nTmp = SwScriptInfo::WhichFont(0, aText);
346 m_pBracket->nPostScript = nTmp;
348 if( !m_pBracket->cPre && !m_pBracket->cPost )
350 m_pBracket.reset();
353 // double line portions have the same direction as the frame directions
354 if ( rCreate.nLevel % 2 )
355 SetDirection( DIR_RIGHT2LEFT );
356 else
357 SetDirection( DIR_LEFT2RIGHT );
360 // paints the wished bracket,
361 // if the multiportion has surrounding brackets.
362 // The X-position of the SwTextPaintInfo will be modified:
363 // the open bracket sets position behind itself,
364 // the close bracket in front of itself.
365 void SwDoubleLinePortion::PaintBracket( SwTextPaintInfo &rInf,
366 tools::Long nSpaceAdd,
367 bool bOpen ) const
369 sal_Unicode cCh = bOpen ? m_pBracket->cPre : m_pBracket->cPost;
370 if( !cCh )
371 return;
372 const sal_uInt16 nChWidth = bOpen ? PreWidth() : PostWidth();
373 if( !nChWidth )
374 return;
375 if( !bOpen )
376 rInf.X( rInf.X() + Width() - PostWidth() +
377 ( nSpaceAdd > 0 ? CalcSpacing( nSpaceAdd, rInf ) : 0 ) );
379 SwBlankPortion aBlank( cCh, true );
380 aBlank.SetAscent( m_pBracket->nAscent );
381 aBlank.Width( nChWidth );
382 aBlank.Height( m_pBracket->nHeight );
384 SwFont aTmpFnt( *rInf.GetFont() );
385 SwFontScript nAct = bOpen ? m_pBracket->nPreScript : m_pBracket->nPostScript;
386 if( SW_SCRIPTS > nAct )
387 aTmpFnt.SetActual( nAct );
388 aTmpFnt.SetProportion( 100 );
389 SwFontSave aSave( rInf, &aTmpFnt );
390 aBlank.Paint( rInf );
392 if( bOpen )
393 rInf.X( rInf.X() + PreWidth() );
396 // creates the bracket-structure
397 // and fills it, if not both characters are 0x00.
398 void SwDoubleLinePortion::SetBrackets( const SwDoubleLinePortion& rDouble )
400 if( rDouble.m_pBracket )
402 m_pBracket.reset( new SwBracket );
403 m_pBracket->cPre = rDouble.m_pBracket->cPre;
404 m_pBracket->cPost = rDouble.m_pBracket->cPost;
405 m_pBracket->nPreScript = rDouble.m_pBracket->nPreScript;
406 m_pBracket->nPostScript = rDouble.m_pBracket->nPostScript;
407 m_pBracket->nStart = rDouble.m_pBracket->nStart;
411 // calculates the size of the brackets => pBracket,
412 // reduces the nMaxWidth-parameter ( minus bracket-width )
413 // and moves the rInf-x-position behind the opening bracket.
414 void SwDoubleLinePortion::FormatBrackets( SwTextFormatInfo &rInf, SwTwips& nMaxWidth )
416 nMaxWidth -= rInf.X();
417 SwFont aTmpFnt( *rInf.GetFont() );
418 aTmpFnt.SetProportion( 100 );
419 m_pBracket->nAscent = 0;
420 m_pBracket->nHeight = 0;
421 if( m_pBracket->cPre )
423 OUString aStr( m_pBracket->cPre );
424 SwFontScript nActualScr = aTmpFnt.GetActual();
425 if( SW_SCRIPTS > m_pBracket->nPreScript )
426 aTmpFnt.SetActual( m_pBracket->nPreScript );
427 SwFontSave aSave( rInf, &aTmpFnt );
428 SwPosSize aSize = rInf.GetTextSize( aStr );
429 m_pBracket->nAscent = rInf.GetAscent();
430 m_pBracket->nHeight = aSize.Height();
431 aTmpFnt.SetActual( nActualScr );
432 if( nMaxWidth > aSize.Width() )
434 m_pBracket->nPreWidth = aSize.Width();
435 nMaxWidth -= aSize.Width();
436 rInf.X( rInf.X() + aSize.Width() );
438 else
440 m_pBracket->nPreWidth = 0;
441 nMaxWidth = 0;
444 else
445 m_pBracket->nPreWidth = 0;
446 if( m_pBracket->cPost )
448 OUString aStr( m_pBracket->cPost );
449 if( SW_SCRIPTS > m_pBracket->nPostScript )
450 aTmpFnt.SetActual( m_pBracket->nPostScript );
451 SwFontSave aSave( rInf, &aTmpFnt );
452 SwPosSize aSize = rInf.GetTextSize( aStr );
453 const sal_uInt16 nTmpAsc = rInf.GetAscent();
454 if( nTmpAsc > m_pBracket->nAscent )
456 m_pBracket->nHeight += nTmpAsc - m_pBracket->nAscent;
457 m_pBracket->nAscent = nTmpAsc;
459 if( aSize.Height() > m_pBracket->nHeight )
460 m_pBracket->nHeight = aSize.Height();
461 if( nMaxWidth > aSize.Width() )
463 m_pBracket->nPostWidth = aSize.Width();
464 nMaxWidth -= aSize.Width();
466 else
468 m_pBracket->nPostWidth = 0;
469 nMaxWidth = 0;
472 else
473 m_pBracket->nPostWidth = 0;
474 nMaxWidth += rInf.X();
477 // calculates the number of blanks in each line and
478 // the difference of the width of the two lines.
479 // These results are used from the text adjustment.
480 void SwDoubleLinePortion::CalcBlanks( SwTextFormatInfo &rInf )
482 SwLinePortion* pPor = GetRoot().GetFirstPortion();
483 TextFrameIndex nNull(0);
484 TextFrameIndex nStart = rInf.GetIdx();
485 SetTab1( false );
486 SetTab2( false );
487 for (m_nBlank1 = TextFrameIndex(0); pPor; pPor = pPor->GetNextPortion())
489 if( pPor->InTextGrp() )
490 m_nBlank1 = m_nBlank1 + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
491 rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
492 if( pPor->InTabGrp() )
493 SetTab1( true );
495 m_nLineDiff = GetRoot().Width();
496 if( GetRoot().GetNext() )
498 pPor = GetRoot().GetNext()->GetFirstPortion();
499 m_nLineDiff -= GetRoot().GetNext()->Width();
501 for (m_nBlank2 = TextFrameIndex(0); pPor; pPor = pPor->GetNextPortion())
503 if( pPor->InTextGrp() )
504 m_nBlank2 = m_nBlank2 + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
505 rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
506 if( pPor->InTabGrp() )
507 SetTab2( true );
509 rInf.SetIdx( nStart );
512 SwTwips SwDoubleLinePortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo & ) const
514 nSpaceAdd = nSpaceAdd > LONG_MAX/2 ? LONG_MAX/2 - nSpaceAdd : nSpaceAdd;
515 return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt()) * nSpaceAdd / SPACING_PRECISION_FACTOR;
518 // Merges the spaces for text adjustment from the inner and outer part.
519 // Inside the doubleline portion the wider line has no spaceadd-array, the
520 // smaller line has such an array to reach width of the wider line.
521 // If the surrounding line has text adjustment and the doubleline portion
522 // contains no tabulator, it is necessary to create/manipulate the inner
523 // space arrays.
524 bool SwDoubleLinePortion::ChgSpaceAdd( SwLineLayout* pCurr,
525 tools::Long nSpaceAdd ) const
527 bool bRet = false;
528 if( !HasTabulator() && nSpaceAdd > 0 )
530 if( !pCurr->IsSpaceAdd() )
532 // The wider line gets the spaceadd from the surrounding line direct
533 pCurr->CreateSpaceAdd();
534 pCurr->SetLLSpaceAdd( nSpaceAdd, 0 );
535 bRet = true;
537 else
539 sal_Int32 const nMyBlank = sal_Int32(GetSmallerSpaceCnt());
540 sal_Int32 const nOther = sal_Int32(GetSpaceCnt());
541 SwTwips nMultiSpace = pCurr->GetLLSpaceAdd( 0 ) * nMyBlank + nOther * nSpaceAdd;
543 if( nMyBlank )
544 nMultiSpace /= sal_Int32(nMyBlank);
546 // pCurr->SetLLSpaceAdd( nMultiSpace, 0 );
547 // #i65711# SetLLSpaceAdd replaces the first value,
548 // instead we want to insert a new first value:
549 std::vector<tools::Long>* pVec = pCurr->GetpLLSpaceAdd();
550 pVec->insert( pVec->begin(), nMultiSpace );
551 bRet = true;
554 return bRet;
556 // cancels the manipulation from SwDoubleLinePortion::ChangeSpaceAdd(..)
557 void SwDoubleLinePortion::ResetSpaceAdd( SwLineLayout* pCurr )
559 pCurr->RemoveFirstLLSpaceAdd();
560 if( !pCurr->GetLLSpaceAddCount() )
561 pCurr->FinishSpaceAdd();
564 SwDoubleLinePortion::~SwDoubleLinePortion()
568 // constructs a ruby portion, i.e. an additional text is displayed
569 // beside the main text, e.g. phonetic characters.
570 SwRubyPortion::SwRubyPortion(const SwRubyPortion& rRuby, TextFrameIndex const nEnd)
571 : SwMultiPortion( nEnd )
572 , m_nRubyOffset( rRuby.GetRubyOffset() )
573 , m_nAdjustment( rRuby.GetAdjustment() )
575 SetDirection( rRuby.GetDirection() );
576 SetRubyPosition( rRuby.GetRubyPosition() );
577 SetRuby();
580 // constructs a ruby portion, i.e. an additional text is displayed
581 // beside the main text, e.g. phonetic characters.
582 SwRubyPortion::SwRubyPortion( const SwMultiCreator& rCreate, const SwFont& rFnt,
583 const IDocumentSettingAccess& rIDocumentSettingAccess,
584 TextFrameIndex const nEnd, TextFrameIndex const nOffs,
585 const SwTextSizeInfo &rInf )
586 : SwMultiPortion( nEnd )
588 SetRuby();
589 OSL_ENSURE( SwMultiCreatorId::Ruby == rCreate.nId, "Ruby expected" );
590 OSL_ENSURE( RES_TXTATR_CJK_RUBY == rCreate.pAttr->Which(), "Wrong attribute" );
591 const SwFormatRuby& rRuby = rCreate.pAttr->GetRuby();
592 m_nAdjustment = rRuby.GetAdjustment();
593 m_nRubyOffset = nOffs;
595 const SwTextFrame *pFrame = rInf.GetTextFrame();
596 RubyPosition ePos = static_cast<RubyPosition>( rRuby.GetPosition() );
598 // RIGHT is designed for horizontal writing mode only.
599 if ( ePos == RubyPosition::RIGHT && pFrame->IsVertical() )
600 ePos = RubyPosition::ABOVE;
602 // In grid mode we force the ruby text to the upper or lower line
603 if ( rInf.SnapToGrid() )
605 SwTextGridItem const*const pGrid( GetGridItem(pFrame->FindPageFrame()) );
606 if ( pGrid )
607 ePos = pGrid->GetRubyTextBelow() ? RubyPosition::BELOW : RubyPosition::ABOVE;
610 SetRubyPosition( ePos );
612 const SwCharFormat *const pFormat =
613 static_txtattr_cast<SwTextRuby const*>(rCreate.pAttr)->GetCharFormat();
614 std::unique_ptr<SwFont> pRubyFont;
615 if( pFormat )
617 const SwAttrSet& rSet = pFormat->GetAttrSet();
618 pRubyFont.reset(new SwFont( rFnt ));
619 pRubyFont->SetDiffFnt( &rSet, &rIDocumentSettingAccess );
621 // we do not allow a vertical font for the ruby text
622 pRubyFont->SetVertical( rFnt.GetOrientation() , OnRight() );
625 OUString aStr = rRuby.GetText().copy( sal_Int32(nOffs) );
626 SwFieldPortion *pField = new SwFieldPortion( std::move(aStr), std::move(pRubyFont) );
627 pField->SetNextOffset( nOffs );
628 pField->SetFollow( true );
630 if( OnTop() )
631 GetRoot().SetNextPortion( pField );
632 else
634 GetRoot().SetNext( new SwLineLayout() );
635 GetRoot().GetNext()->SetNextPortion( pField );
638 // ruby portions have the same direction as the frame directions
639 if ( rCreate.nLevel % 2 )
641 // switch right and left ruby adjustment in rtl environment
642 if ( css::text::RubyAdjust_LEFT == m_nAdjustment )
643 m_nAdjustment = css::text::RubyAdjust_RIGHT;
644 else if ( css::text::RubyAdjust_RIGHT == m_nAdjustment )
645 m_nAdjustment = css::text::RubyAdjust_LEFT;
647 SetDirection( DIR_RIGHT2LEFT );
649 else
650 SetDirection( DIR_LEFT2RIGHT );
653 // In ruby portion there are different alignments for
654 // the ruby text and the main text.
655 // Left, right, centered and two possibilities of block adjustment
656 // The block adjustment is realized by spacing between the characters,
657 // either with a half space or no space in front of the first letter and
658 // a half space at the end of the last letter.
659 // Notice: the smaller line will be manipulated, normally it's the ruby line,
660 // but it could be the main text, too.
661 // If there is a tabulator in smaller line, no adjustment is possible.
662 void SwRubyPortion::Adjust_( SwTextFormatInfo &rInf )
664 SwTwips nLineDiff = GetRoot().Width() - GetRoot().GetNext()->Width();
665 TextFrameIndex const nOldIdx = rInf.GetIdx();
666 if( !nLineDiff )
667 return;
668 SwLineLayout *pCurr;
669 if( nLineDiff < 0 )
670 { // The first line has to be adjusted.
671 if( GetTab1() )
672 return;
673 pCurr = &GetRoot();
674 nLineDiff = -nLineDiff;
676 else
677 { // The second line has to be adjusted.
678 if( GetTab2() )
679 return;
680 pCurr = GetRoot().GetNext();
681 rInf.SetIdx( nOldIdx + GetRoot().GetLen() );
683 sal_uInt16 nLeft = 0; // the space in front of the first letter
684 sal_uInt16 nRight = 0; // the space at the end of the last letter
685 TextFrameIndex nSub(0);
686 switch ( m_nAdjustment )
688 case css::text::RubyAdjust_CENTER: nRight = o3tl::narrowing<sal_uInt16>(nLineDiff / 2);
689 [[fallthrough]];
690 case css::text::RubyAdjust_RIGHT: nLeft = o3tl::narrowing<sal_uInt16>(nLineDiff - nRight); break;
691 case css::text::RubyAdjust_BLOCK: nSub = TextFrameIndex(1);
692 [[fallthrough]];
693 case css::text::RubyAdjust_INDENT_BLOCK:
695 TextFrameIndex nCharCnt(0);
696 SwLinePortion *pPor;
697 for( pPor = pCurr->GetFirstPortion(); pPor; pPor = pPor->GetNextPortion() )
699 if( pPor->InTextGrp() )
700 static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nCharCnt );
701 rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
703 if( nCharCnt > nSub )
705 SwTwips nCalc = nLineDiff / sal_Int32(nCharCnt - nSub);
706 short nTmp;
707 if( nCalc < SHRT_MAX )
708 nTmp = -short(nCalc);
709 else
710 nTmp = SHRT_MIN;
712 pCurr->CreateSpaceAdd( SPACING_PRECISION_FACTOR * nTmp );
713 nLineDiff -= nCalc * (sal_Int32(nCharCnt) - 1);
715 if( nLineDiff > 1 )
717 nRight = o3tl::narrowing<sal_uInt16>(nLineDiff / 2);
718 nLeft = o3tl::narrowing<sal_uInt16>(nLineDiff - nRight);
720 break;
722 default: OSL_FAIL( "New ruby adjustment" );
724 if( nLeft || nRight )
726 if( !pCurr->GetNextPortion() )
727 pCurr->SetNextPortion(SwTextPortion::CopyLinePortion(*pCurr));
728 if( nLeft )
730 SwMarginPortion *pMarg = new SwMarginPortion;
731 pMarg->AddPrtWidth( nLeft );
732 pMarg->SetNextPortion( pCurr->GetNextPortion() );
733 pCurr->SetNextPortion( pMarg );
735 if( nRight )
737 SwMarginPortion *pMarg = new SwMarginPortion;
738 pMarg->AddPrtWidth( nRight );
739 pCurr->FindLastPortion()->Append( pMarg );
743 pCurr->Width( Width() );
744 rInf.SetIdx( nOldIdx );
747 // has to change the nRubyOffset, if there's a fieldportion
748 // in the phonetic line.
749 // The nRubyOffset is the position in the rubystring, where the
750 // next SwRubyPortion has start the displaying of the phonetics.
751 void SwRubyPortion::CalcRubyOffset()
753 const SwLineLayout *pCurr = &GetRoot();
754 if( !OnTop() )
756 pCurr = pCurr->GetNext();
757 if( !pCurr )
758 return;
760 const SwLinePortion *pPor = pCurr->GetFirstPortion();
761 const SwFieldPortion *pField = nullptr;
762 while( pPor )
764 if( pPor->InFieldGrp() )
765 pField = static_cast<const SwFieldPortion*>(pPor);
766 pPor = pPor->GetNextPortion();
768 if( pField )
770 if( pField->HasFollow() )
771 m_nRubyOffset = pField->GetNextOffset();
772 else
773 m_nRubyOffset = TextFrameIndex(COMPLETE_STRING);
777 // A little helper function for GetMultiCreator(..)
778 // It extracts the 2-line-format from a 2-line-attribute or a character style.
779 // The rValue is set to true, if the 2-line-attribute's value is set and
780 // no 2-line-format reference is passed. If there is a 2-line-format reference,
781 // then the rValue is set only, if the 2-line-attribute's value is set _and_
782 // the 2-line-formats has the same brackets.
783 static bool lcl_Check2Lines(const SfxPoolItem *const pItem,
784 const SvxTwoLinesItem* &rpRef, bool &rValue)
786 if( pItem )
788 rValue = static_cast<const SvxTwoLinesItem*>(pItem)->GetValue();
789 if( !rpRef )
790 rpRef = static_cast<const SvxTwoLinesItem*>(pItem);
791 else if( static_cast<const SvxTwoLinesItem*>(pItem)->GetEndBracket() !=
792 rpRef->GetEndBracket() ||
793 static_cast<const SvxTwoLinesItem*>(pItem)->GetStartBracket() !=
794 rpRef->GetStartBracket() )
795 rValue = false;
796 return true;
798 return false;
801 static bool lcl_Has2Lines(const SwTextAttr& rAttr,
802 const SvxTwoLinesItem* &rpRef, bool &rValue)
804 const SfxPoolItem* pItem = CharFormat::GetItem(rAttr, RES_CHRATR_TWO_LINES);
805 return lcl_Check2Lines(pItem, rpRef, rValue);
808 // is a little help function for GetMultiCreator(..)
809 // It extracts the charrotation from a charrotate-attribute or a character style.
810 // The rValue is set to true, if the charrotate-attribute's value is set and
811 // no charrotate-format reference is passed.
812 // If there is a charrotate-format reference, then the rValue is set only,
813 // if the charrotate-attribute's value is set _and_ identical
814 // to the charrotate-format's value.
815 static bool lcl_CheckRotation(const SfxPoolItem *const pItem,
816 const SvxCharRotateItem* &rpRef, bool &rValue)
818 if ( pItem )
820 rValue = static_cast<const SvxCharRotateItem*>(pItem)->GetValue() != 0_deg10;
821 if( !rpRef )
822 rpRef = static_cast<const SvxCharRotateItem*>(pItem);
823 else if( static_cast<const SvxCharRotateItem*>(pItem)->GetValue() !=
824 rpRef->GetValue() )
825 rValue = false;
826 return true;
829 return false;
832 static bool lcl_HasRotation(const SwTextAttr& rAttr,
833 const SvxCharRotateItem* &rpRef, bool &rValue)
835 const SfxPoolItem* pItem = CharFormat::GetItem( rAttr, RES_CHRATR_ROTATE );
836 return lcl_CheckRotation(pItem, rpRef, rValue);
839 namespace sw {
840 namespace {
842 // need to use a very special attribute iterator here that returns
843 // both the hints and the nodes, so that GetMultiCreator() can handle
844 // items in the nodes' set properly
845 class MergedAttrIterMulti
846 : public MergedAttrIterBase
848 private:
849 bool m_First = true;
850 public:
851 MergedAttrIterMulti(SwTextFrame const& rFrame) : MergedAttrIterBase(rFrame) {}
852 SwTextAttr const* NextAttr(SwTextNode const*& rpNode);
853 // can't have operator= because m_pMerged/m_pNode const
854 void Assign(MergedAttrIterMulti const& rOther)
856 assert(m_pMerged == rOther.m_pMerged);
857 assert(m_pNode == rOther.m_pNode);
858 m_CurrentExtent = rOther.m_CurrentExtent;
859 m_CurrentHint = rOther.m_CurrentHint;
860 m_First = rOther.m_First;
866 SwTextAttr const* MergedAttrIterMulti::NextAttr(SwTextNode const*& rpNode)
868 if (m_First)
870 m_First = false;
871 rpNode = m_pMerged
872 ? !m_pMerged->extents.empty()
873 ? m_pMerged->extents[0].pNode
874 : m_pMerged->pFirstNode
875 : m_pNode;
876 return nullptr;
878 if (m_pMerged)
880 const auto nExtentsSize = m_pMerged->extents.size();
881 while (m_CurrentExtent < nExtentsSize)
883 sw::Extent const& rExtent(m_pMerged->extents[m_CurrentExtent]);
884 if (SwpHints const*const pHints = rExtent.pNode->GetpSwpHints())
886 auto nHintsCount = pHints->Count();
887 while (m_CurrentHint < nHintsCount)
889 SwTextAttr const*const pHint(pHints->Get(m_CurrentHint));
890 if (rExtent.nEnd < pHint->GetStart())
892 break;
894 ++m_CurrentHint;
895 if (rExtent.nStart <= pHint->GetStart())
897 rpNode = rExtent.pNode;
898 return pHint;
902 ++m_CurrentExtent;
903 if (m_CurrentExtent < nExtentsSize &&
904 rExtent.pNode != m_pMerged->extents[m_CurrentExtent].pNode)
906 m_CurrentHint = 0; // reset
907 rpNode = m_pMerged->extents[m_CurrentExtent].pNode;
908 return nullptr;
911 return nullptr;
913 else
915 SwpHints const*const pHints(m_pNode->GetpSwpHints());
916 if (pHints)
918 if (m_CurrentHint < pHints->Count())
920 SwTextAttr const*const pHint(pHints->Get(m_CurrentHint));
921 ++m_CurrentHint;
922 rpNode = m_pNode;
923 return pHint;
926 return nullptr;
931 // If we (e.g. the position rPos) are inside a two-line-attribute or
932 // a ruby-attribute, the attribute will be returned in a SwMultiCreator-struct,
933 // otherwise the function returns zero.
934 // The rPos parameter is set to the end of the multiportion,
935 // normally this is the end of the attribute,
936 // but sometimes it is the start of another attribute, which finished or
937 // interrupts the first attribute.
938 // E.g. a ruby portion interrupts a 2-line-attribute, a 2-line-attribute
939 // with different brackets interrupts another 2-line-attribute.
940 std::optional<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex &rPos,
941 SwMultiPortion const * pMulti ) const
943 SwScriptInfo& rSI = const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo();
945 // get the last embedding level
946 sal_uInt8 nCurrLevel;
947 if ( pMulti )
949 OSL_ENSURE( pMulti->IsBidi(), "Nested MultiPortion is not BidiPortion" );
950 // level associated with bidi-portion;
951 nCurrLevel = static_cast<SwBidiPortion const *>(pMulti)->GetLevel();
953 else
954 // no nested bidi portion required
955 nCurrLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
957 // check if there is a field at rPos:
958 sal_uInt8 nNextLevel = nCurrLevel;
959 bool bFieldBidi = false;
961 if (rPos < TextFrameIndex(GetText().getLength()) && CH_TXTATR_BREAKWORD == GetChar(rPos))
963 bFieldBidi = true;
965 else
966 nNextLevel = rSI.DirType( rPos );
968 if (TextFrameIndex(GetText().getLength()) != rPos && nNextLevel > nCurrLevel)
970 rPos = bFieldBidi ? rPos + TextFrameIndex(1) : rSI.NextDirChg(rPos, &nCurrLevel);
971 if (TextFrameIndex(COMPLETE_STRING) == rPos)
972 return {};
973 SwMultiCreator aRet;
974 aRet.pItem = nullptr;
975 aRet.pAttr = nullptr;
976 aRet.nStartOfAttr = TextFrameIndex(-1);
977 aRet.nId = SwMultiCreatorId::Bidi;
978 aRet.nLevel = nCurrLevel + 1;
979 return aRet;
982 // a bidi portion can only contain other bidi portions
983 if ( pMulti )
984 return {};
986 // need the node that contains input rPos
987 std::pair<SwTextNode const*, sal_Int32> startPos(m_pFrame->MapViewToModel(rPos));
988 const SvxCharRotateItem* pActiveRotateItem(nullptr);
989 const SvxCharRotateItem* pNodeRotateItem(nullptr);
990 const SvxTwoLinesItem* pActiveTwoLinesItem(nullptr);
991 const SvxTwoLinesItem* pNodeTwoLinesItem(nullptr);
992 SwTextAttr const* pActiveTwoLinesHint(nullptr);
993 SwTextAttr const* pActiveRotateHint(nullptr);
994 const SwTextAttr *pRuby = nullptr;
995 sw::MergedAttrIterMulti iterAtStartOfNode(*m_pFrame);
996 bool bTwo = false;
997 bool bRot = false;
999 for (sw::MergedAttrIterMulti iter = *m_pFrame; ; )
1001 SwTextNode const* pNode(nullptr);
1002 SwTextAttr const*const pAttr = iter.NextAttr(pNode);
1003 if (!pNode)
1005 break;
1007 if (pAttr)
1009 assert(pNode->GetIndex() <= startPos.first->GetIndex()); // should break earlier
1010 if (startPos.first->GetIndex() <= pNode->GetIndex())
1012 if (startPos.first->GetIndex() != pNode->GetIndex()
1013 || startPos.second < pAttr->GetStart())
1015 break;
1017 if (startPos.second < pAttr->GetAnyEnd())
1019 // sw_redlinehide: ruby *always* splits
1020 if (RES_TXTATR_CJK_RUBY == pAttr->Which())
1021 pRuby = pAttr;
1022 else
1024 const SvxCharRotateItem* pRoTmp = nullptr;
1025 if (lcl_HasRotation( *pAttr, pRoTmp, bRot ))
1027 pActiveRotateHint = bRot ? pAttr : nullptr;
1028 pActiveRotateItem = pRoTmp;
1030 const SvxTwoLinesItem* p2Tmp = nullptr;
1031 if (lcl_Has2Lines( *pAttr, p2Tmp, bTwo ))
1033 pActiveTwoLinesHint = bTwo ? pAttr : nullptr;
1034 pActiveTwoLinesItem = p2Tmp;
1040 // !pAttr && pNode means the node changed
1041 if (startPos.first->GetIndex() < pNode->GetIndex())
1043 break; // only one node initially
1045 if (startPos.first->GetIndex() == pNode->GetIndex())
1047 iterAtStartOfNode.Assign(iter);
1048 if (SfxItemState::SET == pNode->GetSwAttrSet().GetItemState(
1049 RES_CHRATR_ROTATE, true, &pNodeRotateItem) &&
1050 pNodeRotateItem->GetValue())
1052 pActiveRotateItem = pNodeRotateItem;
1054 else
1056 pNodeRotateItem = nullptr;
1058 if (SfxItemState::SET == startPos.first->GetSwAttrSet().GetItemState(
1059 RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem) &&
1060 pNodeTwoLinesItem->GetValue())
1062 pActiveTwoLinesItem = pNodeTwoLinesItem;
1064 else
1066 pNodeTwoLinesItem = nullptr;
1070 if (!pRuby && !pActiveTwoLinesItem && !pActiveRotateItem)
1071 return {};
1073 if( pRuby )
1074 { // The winner is ... a ruby attribute and so
1075 // the end of the multiportion is the end of the ruby attribute.
1076 rPos = m_pFrame->MapModelToView(startPos.first, *pRuby->End());
1077 SwMultiCreator aRet;
1078 aRet.pItem = nullptr;
1079 aRet.pAttr = pRuby;
1080 aRet.nStartOfAttr = m_pFrame->MapModelToView(startPos.first, aRet.pAttr->GetStart());
1081 aRet.nId = SwMultiCreatorId::Ruby;
1082 aRet.nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
1083 return aRet;
1085 if (pActiveTwoLinesHint ||
1086 (pNodeTwoLinesItem && SfxPoolItem::areSame(pNodeTwoLinesItem, pActiveTwoLinesItem) &&
1087 rPos < TextFrameIndex(GetText().getLength())))
1088 { // The winner is a 2-line-attribute,
1089 // the end of the multiportion depends on the following attributes...
1090 SwMultiCreator aRet;
1092 // We note the endpositions of the 2-line attributes in aEnd as stack
1093 std::deque<TextFrameIndex> aEnd;
1095 // The bOn flag signs the state of the last 2-line attribute in the
1096 // aEnd-stack, it is compatible with the winner-attribute or
1097 // it interrupts the other attribute.
1098 bool bOn = true;
1100 if (pActiveTwoLinesHint)
1102 aRet.pItem = nullptr;
1103 aRet.pAttr = pActiveTwoLinesHint;
1104 aRet.nStartOfAttr = m_pFrame->MapModelToView(startPos.first, aRet.pAttr->GetStart());
1105 if (pNodeTwoLinesItem)
1107 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
1108 bOn = pNodeTwoLinesItem->GetEndBracket() ==
1109 pActiveTwoLinesItem->GetEndBracket() &&
1110 pNodeTwoLinesItem->GetStartBracket() ==
1111 pActiveTwoLinesItem->GetStartBracket();
1113 else
1115 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *aRet.pAttr->End()));
1118 else
1120 aRet.pItem = pNodeTwoLinesItem;
1121 aRet.pAttr = nullptr;
1122 aRet.nStartOfAttr = TextFrameIndex(-1);
1123 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
1125 aRet.nId = SwMultiCreatorId::Double;
1126 aRet.nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
1128 // pActiveTwoLinesHint is the last 2-line-attribute, which contains
1129 // the actual position.
1131 // At this moment we know that at position rPos the "winner"-attribute
1132 // causes a 2-line-portion. The end of the attribute is the end of the
1133 // portion, if there's no interrupting attribute.
1134 // There are two kinds of interrupters:
1135 // - ruby attributes stops the 2-line-attribute, the end of the
1136 // multiline is the start of the ruby attribute
1137 // - 2-line-attributes with value "Off" or with different brackets,
1138 // these attributes may interrupt the winner, but they could be
1139 // neutralized by another 2-line-attribute starting at the same
1140 // position with the same brackets as the winner-attribute.
1142 // In the following loop rPos is the critical position and it will be
1143 // evaluated, if at rPos starts an interrupting or a maintaining
1144 // continuity attribute.
1146 // iterAtStartOfNode is positioned to the first hint of the node
1147 // (if any); the node item itself has already been handled above
1148 for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
1150 SwTextNode const* pNode(nullptr);
1151 SwTextAttr const*const pTmp = iter.NextAttr(pNode);
1152 if (!pNode)
1154 break;
1156 assert(startPos.first->GetIndex() <= pNode->GetIndex());
1157 TextFrameIndex nTmpStart;
1158 TextFrameIndex nTmpEnd;
1159 if (pTmp)
1161 nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
1162 if (nTmpEnd <= rPos)
1163 continue;
1164 nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
1166 else
1168 pNodeTwoLinesItem = pNode->GetSwAttrSet().GetItemIfSet(
1169 RES_CHRATR_TWO_LINES);
1170 nTmpStart = m_pFrame->MapModelToView(pNode, 0);
1171 nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
1172 assert(rPos <= nTmpEnd); // next node must not have smaller index
1175 if (rPos < nTmpStart)
1177 // If bOn is false and the next attribute starts later than rPos
1178 // the winner attribute is interrupted at rPos.
1179 // If the start of the next attribute is behind the end of
1180 // the last attribute on the aEnd-stack, this is the endposition
1181 // on the stack is the end of the 2-line portion.
1182 if (!bOn || aEnd.back() < nTmpStart)
1183 break;
1184 // At this moment, bOn is true and the next attribute starts
1185 // behind rPos, so we could move rPos to the next startpoint
1186 rPos = nTmpStart;
1187 // We clean up the aEnd-stack, endpositions equal to rPos are
1188 // superfluous.
1189 while( !aEnd.empty() && aEnd.back() <= rPos )
1191 bOn = !bOn;
1192 aEnd.pop_back();
1194 // If the endstack is empty, we simulate an attribute with
1195 // state true and endposition rPos
1196 if( aEnd.empty() )
1198 aEnd.push_front( rPos );
1199 bOn = true;
1202 // A ruby attribute stops the 2-line immediately
1203 if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
1204 return aRet;
1205 if (pTmp ? lcl_Has2Lines(*pTmp, pActiveTwoLinesItem, bTwo)
1206 : lcl_Check2Lines(pNodeTwoLinesItem, pActiveTwoLinesItem, bTwo))
1207 { // We have an interesting attribute...
1208 if( bTwo == bOn )
1209 { // .. with the same state, so the last attribute could
1210 // be continued.
1211 if (aEnd.back() < nTmpEnd)
1212 aEnd.back() = nTmpEnd;
1214 else
1215 { // .. with a different state.
1216 bOn = bTwo;
1217 // If this is smaller than the last on the stack, we put
1218 // it on the stack. If it has the same endposition, the last
1219 // could be removed.
1220 if (nTmpEnd < aEnd.back())
1221 aEnd.push_back( nTmpEnd );
1222 else if( aEnd.size() > 1 )
1223 aEnd.pop_back();
1224 else
1225 aEnd.back() = nTmpEnd;
1229 if( bOn && !aEnd.empty() )
1230 rPos = aEnd.back();
1231 return aRet;
1233 if (pActiveRotateHint ||
1234 (pNodeRotateItem && SfxPoolItem::areSame(pNodeRotateItem, pActiveRotateItem) &&
1235 rPos < TextFrameIndex(GetText().getLength())))
1236 { // The winner is a rotate-attribute,
1237 // the end of the multiportion depends on the following attributes...
1238 SwMultiCreator aRet;
1239 aRet.nId = SwMultiCreatorId::Rotate;
1241 // We note the endpositions of the 2-line attributes in aEnd as stack
1242 std::deque<TextFrameIndex> aEnd;
1244 // The bOn flag signs the state of the last 2-line attribute in the
1245 // aEnd-stack, which could interrupts the winning rotation attribute.
1246 bool bOn = pNodeTwoLinesItem != nullptr;
1247 aEnd.push_front(TextFrameIndex(GetText().getLength()));
1249 // first, search for the start position of the next TWOLINE portion
1250 // because the ROTATE portion must end there at the latest
1251 TextFrameIndex n2Start = rPos;
1252 for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
1254 SwTextNode const* pNode(nullptr);
1255 SwTextAttr const*const pTmp = iter.NextAttr(pNode);
1256 if (!pNode)
1258 break;
1260 assert(startPos.first->GetIndex() <= pNode->GetIndex());
1261 TextFrameIndex nTmpStart;
1262 TextFrameIndex nTmpEnd;
1263 if (pTmp)
1265 nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
1266 if (nTmpEnd <= n2Start)
1267 continue;
1268 nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
1270 else
1272 pNodeTwoLinesItem = pNode->GetSwAttrSet().GetItemIfSet(
1273 RES_CHRATR_TWO_LINES);
1274 nTmpStart = m_pFrame->MapModelToView(pNode, 0);
1275 nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
1276 assert(n2Start <= nTmpEnd); // next node must not have smaller index
1279 if (n2Start < nTmpStart)
1281 if (bOn || aEnd.back() < nTmpStart)
1282 break;
1283 n2Start = nTmpStart;
1284 while( !aEnd.empty() && aEnd.back() <= n2Start )
1286 bOn = !bOn;
1287 aEnd.pop_back();
1289 if( aEnd.empty() )
1291 aEnd.push_front( n2Start );
1292 bOn = false;
1295 // A ruby attribute stops immediately
1296 if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
1298 bOn = true;
1299 break;
1301 const SvxTwoLinesItem* p2Lines = nullptr;
1302 if (pTmp ? lcl_Has2Lines(*pTmp, p2Lines, bTwo)
1303 : lcl_Check2Lines(pNodeTwoLinesItem, p2Lines, bTwo))
1305 if( bTwo == bOn )
1307 if (aEnd.back() < nTmpEnd)
1308 aEnd.back() = nTmpEnd;
1310 else
1312 bOn = bTwo;
1313 if (nTmpEnd < aEnd.back())
1314 aEnd.push_back( nTmpEnd );
1315 else if( aEnd.size() > 1 )
1316 aEnd.pop_back();
1317 else
1318 aEnd.back() = nTmpEnd;
1322 if( !bOn && !aEnd.empty() )
1323 n2Start = aEnd.back();
1325 aEnd.clear();
1327 // now, search for the end of the ROTATE portion, similar to above
1328 bOn = true;
1329 if (pActiveRotateHint)
1331 aRet.pItem = nullptr;
1332 aRet.pAttr = pActiveRotateHint;
1333 aRet.nStartOfAttr = m_pFrame->MapModelToView(startPos.first, aRet.pAttr->GetStart());
1334 if (pNodeRotateItem)
1336 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
1337 bOn = pNodeRotateItem->GetValue() ==
1338 pActiveRotateItem->GetValue();
1340 else
1342 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *aRet.pAttr->End()));
1345 else
1347 aRet.pItem = pNodeRotateItem;
1348 aRet.pAttr = nullptr;
1349 aRet.nStartOfAttr = TextFrameIndex(-1);
1350 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
1352 for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
1354 SwTextNode const* pNode(nullptr);
1355 SwTextAttr const*const pTmp = iter.NextAttr(pNode);
1356 if (!pNode)
1358 break;
1360 assert(startPos.first->GetIndex() <= pNode->GetIndex());
1361 TextFrameIndex nTmpStart;
1362 TextFrameIndex nTmpEnd;
1363 if (pTmp)
1365 nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
1366 if (nTmpEnd <= rPos)
1367 continue;
1368 nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
1370 else
1372 pNodeRotateItem = pNode->GetSwAttrSet().GetItemIfSet(
1373 RES_CHRATR_ROTATE);
1374 nTmpStart = m_pFrame->MapModelToView(pNode, 0);
1375 nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
1376 assert(rPos <= nTmpEnd); // next node must not have smaller index
1379 if (rPos < nTmpStart)
1381 if (!bOn || aEnd.back() < nTmpStart)
1382 break;
1383 rPos = nTmpStart;
1384 while( !aEnd.empty() && aEnd.back() <= rPos )
1386 bOn = !bOn;
1387 aEnd.pop_back();
1389 if( aEnd.empty() )
1391 aEnd.push_front( rPos );
1392 bOn = true;
1395 if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
1397 bOn = false;
1398 break;
1400 // TODO why does this use bTwo, not bRot ???
1401 if (pTmp ? lcl_HasRotation(*pTmp, pActiveRotateItem, bTwo)
1402 : lcl_CheckRotation(pNodeRotateItem, pActiveRotateItem, bTwo))
1404 if( bTwo == bOn )
1406 if (aEnd.back() < nTmpEnd)
1407 aEnd.back() = nTmpEnd;
1409 else
1411 bOn = bTwo;
1412 if (nTmpEnd < aEnd.back())
1413 aEnd.push_back( nTmpEnd );
1414 else if( aEnd.size() > 1 )
1415 aEnd.pop_back();
1416 else
1417 aEnd.back() = nTmpEnd;
1421 if( bOn && !aEnd.empty() )
1422 rPos = aEnd.back();
1423 if( rPos > n2Start )
1424 rPos = n2Start;
1425 return aRet;
1427 return {};
1430 namespace {
1432 // A little helper class to manage the spaceadd-arrays of the text adjustment
1433 // during a PaintMultiPortion.
1434 // The constructor prepares the array for the first line of multiportion,
1435 // the SecondLine-function restores the values for the first line and prepares
1436 // the second line.
1437 // The destructor restores the values of the last manipulation.
1438 class SwSpaceManipulator
1440 SwTextPaintInfo& m_rInfo;
1441 SwMultiPortion& m_rMulti;
1442 std::vector<tools::Long>* m_pOldSpaceAdd;
1443 sal_uInt16 m_nOldSpaceIndex;
1444 tools::Long m_nSpaceAdd;
1445 bool m_bSpaceChg;
1446 sal_uInt8 m_nOldDir;
1448 public:
1449 SwSpaceManipulator( SwTextPaintInfo& rInf, SwMultiPortion& rMult );
1450 ~SwSpaceManipulator();
1451 void SecondLine();
1452 tools::Long GetSpaceAdd() const { return m_nSpaceAdd; }
1457 SwSpaceManipulator::SwSpaceManipulator(SwTextPaintInfo& rInf, SwMultiPortion& rMult)
1458 : m_rInfo(rInf)
1459 , m_rMulti(rMult)
1460 , m_nSpaceAdd(0)
1462 m_pOldSpaceAdd = m_rInfo.GetpSpaceAdd();
1463 m_nOldSpaceIndex = m_rInfo.GetSpaceIdx();
1464 m_nOldDir = m_rInfo.GetDirection();
1465 m_rInfo.SetDirection(m_rMulti.GetDirection());
1466 m_bSpaceChg = false;
1468 if (m_rMulti.IsDouble())
1470 m_nSpaceAdd = (m_pOldSpaceAdd && !m_rMulti.HasTabulator()) ? m_rInfo.GetSpaceAdd() : 0;
1471 if (m_rMulti.GetRoot().IsSpaceAdd())
1473 m_rInfo.SetpSpaceAdd(m_rMulti.GetRoot().GetpLLSpaceAdd());
1474 m_rInfo.ResetSpaceIdx();
1475 m_bSpaceChg = m_rMulti.ChgSpaceAdd(&m_rMulti.GetRoot(), m_nSpaceAdd);
1477 else if (m_rMulti.HasTabulator())
1478 m_rInfo.SetpSpaceAdd(nullptr);
1480 else if (!m_rMulti.IsBidi())
1482 m_rInfo.SetpSpaceAdd(m_rMulti.GetRoot().GetpLLSpaceAdd());
1483 m_rInfo.ResetSpaceIdx();
1487 void SwSpaceManipulator::SecondLine()
1489 if (m_bSpaceChg)
1491 m_rInfo.RemoveFirstSpaceAdd();
1492 m_bSpaceChg = false;
1494 SwLineLayout* pLay = m_rMulti.GetRoot().GetNext();
1495 if( pLay->IsSpaceAdd() )
1497 m_rInfo.SetpSpaceAdd(pLay->GetpLLSpaceAdd());
1498 m_rInfo.ResetSpaceIdx();
1499 m_bSpaceChg = m_rMulti.ChgSpaceAdd(pLay, m_nSpaceAdd);
1501 else
1503 m_rInfo.SetpSpaceAdd((!m_rMulti.IsDouble() || m_rMulti.HasTabulator()) ? nullptr
1504 : m_pOldSpaceAdd);
1505 m_rInfo.SetSpaceIdx(m_nOldSpaceIndex);
1509 SwSpaceManipulator::~SwSpaceManipulator()
1511 if (m_bSpaceChg)
1513 m_rInfo.RemoveFirstSpaceAdd();
1514 m_bSpaceChg = false;
1516 m_rInfo.SetpSpaceAdd(m_pOldSpaceAdd);
1517 m_rInfo.SetSpaceIdx(m_nOldSpaceIndex);
1518 m_rInfo.SetDirection(m_nOldDir);
1521 // Manages the paint for a SwMultiPortion.
1522 // External, for the calling function, it seems to be a normal Paint-function,
1523 // internal it is like a SwTextFrame::PaintSwFrame with multiple DrawTextLines
1524 void SwTextPainter::PaintMultiPortion( const SwRect &rPaint,
1525 SwMultiPortion& rMulti, const SwMultiPortion* pEnvPor )
1527 SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
1528 const bool bHasGrid = pGrid && GetInfo().SnapToGrid();
1529 sal_uInt16 nRubyHeight = 0;
1530 bool bRubyTop = true;
1532 if ( bHasGrid && pGrid->IsSquaredMode() )
1534 nRubyHeight = pGrid->GetRubyHeight();
1535 bRubyTop = ! pGrid->GetRubyTextBelow();
1538 // do not allow grid mode for first line in ruby portion
1539 const bool bRubyInGrid = bHasGrid && rMulti.IsRuby();
1541 const sal_uInt16 nOldHeight = rMulti.Height();
1542 const bool bOldGridModeAllowed = GetInfo().SnapToGrid();
1544 if ( bRubyInGrid )
1546 GetInfo().SetSnapToGrid( ! bRubyTop );
1547 if (pGrid->IsSquaredMode())
1548 rMulti.Height( m_pCurr->Height() );
1551 SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() );
1552 bool bEnvDir = false;
1553 bool bThisDir = false;
1554 bool bFrameDir = false;
1555 if ( rMulti.IsBidi() )
1557 // these values are needed for the calculation of the x coordinate
1558 // and the layout mode
1559 OSL_ENSURE( ! pEnvPor || pEnvPor->IsBidi(),
1560 "Oh no, I expected a BidiPortion" );
1561 bFrameDir = GetInfo().GetTextFrame()->IsRightToLeft();
1562 bEnvDir = pEnvPor ? ((static_cast<const SwBidiPortion*>(pEnvPor)->GetLevel() % 2) != 0) : bFrameDir;
1563 bThisDir = (static_cast<SwBidiPortion&>(rMulti).GetLevel() % 2) != 0;
1566 #if OSL_DEBUG_LEVEL > 1
1567 // only paint first level bidi portions
1568 if( rMulti.Width() > 1 && ! pEnvPor )
1569 GetInfo().DrawViewOpt( rMulti, PortionType::Field );
1570 #endif
1572 if ( bRubyInGrid && pGrid->IsSquaredMode() )
1573 rMulti.Height( nOldHeight );
1575 // do we have to repaint a post it portion?
1576 if( GetInfo().OnWin() && rMulti.GetNextPortion() &&
1577 ! rMulti.GetNextPortion()->Width() )
1578 rMulti.GetNextPortion()->PrePaint( GetInfo(), &rMulti );
1580 // old values must be saved and restored at the end
1581 TextFrameIndex const nOldLen = GetInfo().GetLen();
1582 const SwTwips nOldX = GetInfo().X();
1583 const SwTwips nOldY = GetInfo().Y();
1584 TextFrameIndex const nOldIdx = GetInfo().GetIdx();
1586 SwSpaceManipulator aManip( GetInfo(), rMulti );
1588 std::optional<SwFontSave> oFontSave;
1589 std::unique_ptr<SwFont> pTmpFnt;
1591 if( rMulti.IsDouble() )
1593 pTmpFnt.reset(new SwFont( *GetInfo().GetFont() ));
1594 if( rMulti.IsDouble() )
1596 SetPropFont( 50 );
1597 pTmpFnt->SetProportion( GetPropFont() );
1599 oFontSave.emplace( GetInfo(), pTmpFnt.get(), this );
1601 else
1603 pTmpFnt = nullptr;
1606 if( rMulti.HasBrackets() )
1608 // WP is mandatory
1609 Por_Info const por(rMulti, *this, 1);
1610 SwTaggedPDFHelper const tag(nullptr, nullptr, &por, *GetInfo().GetOut());
1612 TextFrameIndex const nTmpOldIdx = GetInfo().GetIdx();
1613 GetInfo().SetIdx(static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart);
1614 SeekAndChg( GetInfo() );
1615 static_cast<SwDoubleLinePortion&>(rMulti).PaintBracket( GetInfo(), 0, true );
1616 GetInfo().SetIdx( nTmpOldIdx );
1619 const SwTwips nTmpX = GetInfo().X();
1621 SwLineLayout* pLay = &rMulti.GetRoot();// the first line of the multiportion
1622 SwLinePortion* pPor = pLay->GetFirstPortion();//first portion of these line
1623 SwTwips nOfst = 0;
1625 // GetInfo().Y() is the baseline from the surrounding line. We must switch
1626 // this temporary to the baseline of the inner lines of the multiportion.
1627 if( rMulti.HasRotation() )
1629 if( rMulti.IsRevers() )
1631 GetInfo().Y( nOldY - rMulti.GetAscent() );
1632 nOfst = nTmpX + rMulti.Width();
1634 else
1636 GetInfo().Y( nOldY - rMulti.GetAscent() + rMulti.Height() );
1637 nOfst = nTmpX;
1640 else if ( rMulti.IsBidi() )
1642 // does the current bidi portion has the same direction
1643 // as its environment?
1644 if ( bEnvDir != bThisDir )
1646 // different directions, we have to adjust the x coordinate
1647 SwTwips nMultiWidth = rMulti.Width() +
1648 rMulti.CalcSpacing( GetInfo().GetSpaceAdd(), GetInfo() );
1650 if ( bFrameDir == bThisDir )
1651 GetInfo().X( GetInfo().X() - nMultiWidth );
1652 else
1653 GetInfo().X( GetInfo().X() + nMultiWidth );
1656 nOfst = nOldY - rMulti.GetAscent();
1658 // set layout mode
1659 aLayoutModeModifier.Modify( bThisDir );
1661 else
1662 nOfst = nOldY - rMulti.GetAscent();
1664 bool bRest = pLay->IsRest();
1665 bool bFirst = true;
1667 OSL_ENSURE( nullptr == GetInfo().GetUnderFnt() || rMulti.IsBidi(),
1668 " Only BiDi portions are allowed to use the common underlining font" );
1670 ::std::optional<SwTaggedPDFHelper> oTag;
1671 if (rMulti.IsDouble())
1673 Por_Info const por(rMulti, *this, 2);
1674 oTag.emplace(nullptr, nullptr, &por, *GetInfo().GetOut());
1676 else if (rMulti.IsRuby())
1678 Por_Info const por(rMulti, *this, bRubyTop ? 1 : 2);
1679 oTag.emplace(nullptr, nullptr, &por, *GetInfo().GetOut());
1680 GetInfo().SetRuby( rMulti.OnTop() );
1685 if ( bHasGrid && pGrid->IsSquaredMode() )
1687 if( rMulti.HasRotation() )
1689 const sal_uInt16 nAdjustment = ( pLay->Height() - pPor->Height() ) / 2 +
1690 pPor->GetAscent();
1691 if( rMulti.IsRevers() )
1692 GetInfo().X( nOfst - nAdjustment );
1693 else
1694 GetInfo().X( nOfst + nAdjustment );
1696 else
1698 // special treatment for ruby portions in grid mode
1699 SwTwips nAdjustment = 0;
1700 if ( rMulti.IsRuby() )
1702 if ( bRubyTop != ( pLay == &rMulti.GetRoot() ) )
1703 // adjust base text
1704 nAdjustment = ( m_pCurr->Height() - nRubyHeight - pPor->Height() ) / 2;
1705 else if ( bRubyTop )
1706 // adjust upper ruby text
1707 nAdjustment = nRubyHeight - pPor->Height();
1708 // else adjust lower ruby text
1711 GetInfo().Y( nOfst + nAdjustment + pPor->GetAscent() );
1714 else if( rMulti.HasRotation() )
1716 if( rMulti.IsRevers() )
1717 GetInfo().X( nOfst - AdjustBaseLine( *pLay, pPor, 0, 0, true ) );
1718 else
1719 GetInfo().X( nOfst + AdjustBaseLine( *pLay, pPor ) );
1721 else if ( rMulti.IsRuby() && rMulti.OnRight() && GetInfo().IsRuby() )
1723 SwTwips nLineDiff = std::max(( rMulti.GetRoot().Height() - pPor->Width() ) / 2, static_cast<SwTwips>(0) );
1724 GetInfo().Y( nOfst + nLineDiff );
1725 // Draw the ruby text on top of the preserved space.
1726 GetInfo().X( GetInfo().X() - pPor->Height() );
1728 else if (!rMulti.IsBidi())
1730 GetInfo().Y(nOfst + AdjustBaseLine(*pLay, pPor));
1733 bool bSeeked = true;
1734 GetInfo().SetLen( pPor->GetLen() );
1736 if( bRest && pPor->InFieldGrp() && !pPor->GetLen() )
1738 if( static_cast<SwFieldPortion*>(pPor)->HasFont() )
1739 bSeeked = false;
1740 else
1741 SeekAndChgBefore( GetInfo() );
1743 else if( pPor->InTextGrp() || pPor->InFieldGrp() || pPor->InTabGrp() )
1744 SeekAndChg( GetInfo() );
1745 else if ( !bFirst && pPor->IsBreakPortion() && GetInfo().GetOpt().IsParagraph() )
1747 if( GetRedln() )
1748 SeekAndChg( GetInfo() );
1749 else
1750 SeekAndChgBefore( GetInfo() );
1752 else
1753 bSeeked = false;
1755 SwLinePortion *pNext = pPor->GetNextPortion();
1756 if(GetInfo().OnWin() && pNext && !pNext->Width() )
1758 if ( !bSeeked )
1759 SeekAndChg( GetInfo() );
1760 pNext->PrePaint( GetInfo(), pPor );
1763 CheckSpecialUnderline( pPor );
1764 SwUnderlineFont* pUnderLineFnt = GetInfo().GetUnderFnt();
1765 if ( pUnderLineFnt )
1767 if ( rMulti.IsDouble() )
1768 pUnderLineFnt->GetFont().SetProportion( 50 );
1769 pUnderLineFnt->SetPos( GetInfo().GetPos() );
1772 if ( rMulti.IsBidi() )
1774 // we do not allow any rotation inside a bidi portion
1775 SwFont* pTmpFont = GetInfo().GetFont();
1776 pTmpFont->SetVertical( 0_deg10, GetInfo().GetTextFrame()->IsVertical() );
1779 if( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() )
1781 // but we do allow nested bidi portions
1782 OSL_ENSURE( rMulti.IsBidi(), "Only nesting of bidi portions is allowed" );
1783 PaintMultiPortion( rPaint, static_cast<SwMultiPortion&>(*pPor), &rMulti );
1785 else
1787 Por_Info const por(*pPor, *this, 0);
1788 SwTaggedPDFHelper const tag(nullptr, nullptr, &por, *GetInfo().GetOut());
1790 pPor->Paint( GetInfo() );
1793 if (GetFnt()->IsURL() && pPor->InTextGrp())
1794 GetInfo().NotifyURL(*pPor);
1796 bFirst &= !pPor->GetLen();
1797 if( pNext || !pPor->IsMarginPortion() )
1798 pPor->Move( GetInfo() );
1800 pPor = pNext;
1802 // If there's no portion left, we go to the next line
1803 if( !pPor && pLay->GetNext() )
1805 pLay = pLay->GetNext();
1806 pPor = pLay->GetFirstPortion();
1807 bRest = pLay->IsRest();
1808 aManip.SecondLine();
1810 // delete underline font
1811 delete GetInfo().GetUnderFnt();
1812 GetInfo().SetUnderFnt( nullptr );
1814 if( rMulti.HasRotation() )
1816 if( rMulti.IsRevers() )
1818 nOfst += pLay->Height();
1819 GetInfo().Y( nOldY - rMulti.GetAscent() );
1821 else
1823 nOfst -= pLay->Height();
1824 GetInfo().Y( nOldY - rMulti.GetAscent() + rMulti.Height() );
1827 else if ( bHasGrid && rMulti.IsRuby() )
1829 GetInfo().SetSnapToGrid( bRubyTop );
1830 GetInfo().X( nTmpX );
1831 if (pGrid->IsSquaredMode() )
1833 if ( bRubyTop )
1834 nOfst += nRubyHeight;
1835 else
1836 nOfst += m_pCurr->Height() - nRubyHeight;
1838 else
1840 nOfst += rMulti.GetRoot().Height();
1843 else if ( rMulti.IsRuby() && rMulti.OnRight() )
1845 GetInfo().SetDirection( DIR_TOP2BOTTOM );
1846 GetInfo().SetRuby( true );
1847 } else
1849 GetInfo().X( nTmpX );
1850 // We switch to the baseline of the next inner line
1851 nOfst += rMulti.GetRoot().Height();
1853 if (rMulti.IsRuby())
1855 oTag.reset();
1856 Por_Info const por(rMulti, *this, bRubyTop ? 2 : 1);
1857 oTag.emplace(nullptr, nullptr, &por, *GetInfo().GetOut());
1860 } while( pPor );
1862 if (rMulti.IsDouble())
1864 oTag.reset();
1867 if ( bRubyInGrid )
1868 GetInfo().SetSnapToGrid( bOldGridModeAllowed );
1870 // delete underline font
1871 if ( ! rMulti.IsBidi() )
1873 delete GetInfo().GetUnderFnt();
1874 GetInfo().SetUnderFnt( nullptr );
1877 GetInfo().SetIdx( nOldIdx );
1878 GetInfo().Y( nOldY );
1880 if( rMulti.HasBrackets() )
1882 // WP is mandatory
1883 Por_Info const por(rMulti, *this, 1);
1884 SwTaggedPDFHelper const tag(nullptr, nullptr, &por, *GetInfo().GetOut());
1886 TextFrameIndex const nTmpOldIdx = GetInfo().GetIdx();
1887 GetInfo().SetIdx(static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart);
1888 SeekAndChg( GetInfo() );
1889 GetInfo().X( nOldX );
1890 static_cast<SwDoubleLinePortion&>(rMulti).PaintBracket( GetInfo(),
1891 aManip.GetSpaceAdd(), false );
1892 GetInfo().SetIdx( nTmpOldIdx );
1894 // Restore the saved values
1895 GetInfo().X( nOldX );
1896 GetInfo().SetLen( nOldLen );
1897 oFontSave.reset();
1898 pTmpFnt.reset();
1899 SetPropFont( 0 );
1902 static bool lcl_ExtractFieldFollow( SwLineLayout* pLine, SwLinePortion* &rpField )
1904 SwLinePortion* pLast = pLine;
1905 rpField = pLine->GetNextPortion();
1906 while( rpField && !rpField->InFieldGrp() )
1908 pLast = rpField;
1909 rpField = rpField->GetNextPortion();
1911 bool bRet = rpField != nullptr;
1912 if( bRet )
1914 if( static_cast<SwFieldPortion*>(rpField)->IsFollow() )
1916 rpField->Truncate();
1917 pLast->SetNextPortion( nullptr );
1919 else
1920 rpField = nullptr;
1922 pLine->Truncate();
1923 return bRet;
1926 // Determines if any part of the bidi portion fits on the current line
1927 namespace
1929 enum class BidiTruncationType
1931 None,
1932 Truncate,
1933 Underflow
1936 BidiTruncationType lcl_BidiPortionNeedsTruncation(SwMultiPortion& rMulti,
1937 SwTextFormatInfo& rExternalInf,
1938 SwTextFormatInfo& rLocalInf,
1939 TextFrameIndex const nStartIdx)
1941 if (!rLocalInf.IsUnderflow())
1943 // Some amount of text fits in the bidi portion without triggering underflow,
1944 // so the portion should not be truncated.
1945 return BidiTruncationType::None;
1948 auto nCurrLen = rMulti.GetLen();
1950 css::i18n::LineBreakHyphenationOptions aHyphOptions;
1951 css::i18n::LineBreakUserOptions aUserOptions;
1952 css::lang::Locale aLocale;
1953 auto aResult = g_pBreakIt->GetBreakIter()->getLineBreak(
1954 rExternalInf.GetText(), sal_Int32(nStartIdx + nCurrLen), aLocale,
1955 sal_Int32(rExternalInf.GetLineStart()), aHyphOptions, aUserOptions);
1957 if (aResult.breakIndex < sal_Int32(nStartIdx))
1959 // The bidi portion doesn't fit on the line, and the first break opportunity
1960 // is before the bidi portion. Underflow to the preceding text.
1961 return BidiTruncationType::Underflow;
1964 if (aResult.breakIndex > sal_Int32(nStartIdx)
1965 && aResult.breakIndex <= sal_Int32(nStartIdx + nCurrLen))
1967 // The bidi portion fits on this line, but ended with underflow.
1968 return BidiTruncationType::None;
1971 // The bidi portion doesn't fit on the line, but a break position exists between the bidi
1972 // portion and the preceding text. Truncating is sufficient.
1973 return BidiTruncationType::Truncate;
1977 // If a multi portion completely has to go to the
1978 // next line, this function is called to truncate
1979 // the rest of the remaining multi portion
1980 static void lcl_TruncateMultiPortion(SwMultiPortion& rMulti, SwTextFormatInfo& rInf,
1981 TextFrameIndex const nStartIdx,
1982 BidiTruncationType nBidiTruncType = BidiTruncationType::None)
1984 rMulti.GetRoot().Truncate();
1985 rMulti.GetRoot().SetLen(TextFrameIndex(0));
1986 rMulti.GetRoot().Width(0);
1987 // rMulti.CalcSize( *this, aInf );
1988 if ( rMulti.GetRoot().GetNext() )
1990 rMulti.GetRoot().GetNext()->Truncate();
1991 rMulti.GetRoot().GetNext()->SetLen(TextFrameIndex(0));
1992 rMulti.GetRoot().GetNext()->Width( 0 );
1994 rMulti.Width( 0 );
1995 rMulti.SetLen(TextFrameIndex(0));
1996 rInf.SetIdx( nStartIdx );
1998 if (rMulti.IsBidi())
2000 // The truncated portion is a bidi portion. Bidi portions contain ordinary text, and may
2001 // potentially underflow in the case that none of the text fits on the current line.
2002 if (nBidiTruncType == BidiTruncationType::Underflow)
2004 // The start of the bidi portion is not a valid break. Instead, a break should be
2005 // inserted into a previous text portion on this line.
2006 rInf.SetUnderflow(&rMulti);
2011 // Manages the formatting of a SwMultiPortion. External, for the calling
2012 // function, it seems to be a normal Format-function, internal it is like a
2013 // SwTextFrame::Format_ with multiple BuildPortions
2014 bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf,
2015 SwMultiPortion& rMulti )
2017 SwTwips nMaxWidth = rInf.Width();
2018 SwTwips nOldX = 0;
2020 if( rMulti.HasBrackets() )
2022 TextFrameIndex const nOldIdx = rInf.GetIdx();
2023 rInf.SetIdx( static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart );
2024 SeekAndChg( rInf );
2025 nOldX = GetInfo().X();
2026 static_cast<SwDoubleLinePortion&>(rMulti).FormatBrackets( rInf, nMaxWidth );
2027 rInf.SetIdx( nOldIdx );
2030 SeekAndChg( rInf );
2031 std::optional<SwFontSave> oFontSave;
2032 std::unique_ptr<SwFont> xTmpFont;
2033 if( rMulti.IsDouble() )
2035 xTmpFont.reset(new SwFont( *rInf.GetFont() ));
2036 if( rMulti.IsDouble() )
2038 SetPropFont( 50 );
2039 xTmpFont->SetProportion( GetPropFont() );
2041 oFontSave.emplace(rInf, xTmpFont.get(), this);
2044 SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() );
2045 if ( rMulti.IsBidi() )
2047 // set layout mode
2048 aLayoutModeModifier.Modify( ! rInf.GetTextFrame()->IsRightToLeft() );
2051 SwTwips nTmpX = 0;
2053 if( rMulti.HasRotation() )
2055 // For nMaxWidth we take the height of the body frame.
2056 // #i25067#: If the current frame is inside a table, we restrict
2057 // nMaxWidth to the current frame height, unless the frame size
2058 // attribute is set to variable size:
2060 // We set nTmpX (which is used for portion calculating) to the
2061 // current Y value
2062 const SwPageFrame* pPage = m_pFrame->FindPageFrame();
2063 OSL_ENSURE( pPage, "No page in frame!");
2064 const SwLayoutFrame* pUpperFrame = pPage;
2066 if ( m_pFrame->IsInTab() )
2068 pUpperFrame = m_pFrame->GetUpper();
2069 while ( pUpperFrame && !pUpperFrame->IsCellFrame() )
2070 pUpperFrame = pUpperFrame->GetUpper();
2071 assert(pUpperFrame); //pFrame is in table but does not have an upper cell frame
2072 if (!pUpperFrame)
2073 return false;
2074 const SwTableLine* pLine = static_cast<const SwRowFrame*>(pUpperFrame->GetUpper())->GetTabLine();
2075 const SwFormatFrameSize& rFrameFormatSize = pLine->GetFrameFormat()->GetFrameSize();
2076 if ( SwFrameSize::Variable == rFrameFormatSize.GetHeightSizeType() )
2077 pUpperFrame = pPage;
2079 if ( pUpperFrame == pPage && !m_pFrame->IsInFootnote() )
2080 pUpperFrame = pPage->FindBodyCont();
2082 nMaxWidth = pUpperFrame ?
2083 ( rInf.GetTextFrame()->IsVertical() ?
2084 pUpperFrame->getFramePrintArea().Width() :
2085 pUpperFrame->getFramePrintArea().Height() ) :
2086 std::numeric_limits<SwTwips>::max();
2087 if (nMaxWidth < 0)
2088 nMaxWidth = 0;
2090 else
2091 nTmpX = rInf.X();
2093 SwMultiPortion* pOldMulti = m_pMulti;
2095 m_pMulti = &rMulti;
2096 SwLineLayout *pOldCurr = m_pCurr;
2097 TextFrameIndex const nOldStart = GetStart();
2098 SwTwips nMinWidth = nTmpX + 1;
2099 SwTwips nActWidth = nMaxWidth;
2100 const TextFrameIndex nStartIdx = rInf.GetIdx();
2101 TextFrameIndex nMultiLen = rMulti.GetLen();
2103 SwLinePortion *pFirstRest;
2104 SwLinePortion *pSecondRest;
2105 if( rMulti.IsFormatted() )
2107 if( !lcl_ExtractFieldFollow( &rMulti.GetRoot(), pFirstRest )
2108 && rMulti.IsDouble() && rMulti.GetRoot().GetNext() )
2109 lcl_ExtractFieldFollow( rMulti.GetRoot().GetNext(), pFirstRest );
2110 if( !rMulti.IsDouble() && rMulti.GetRoot().GetNext() )
2111 lcl_ExtractFieldFollow( rMulti.GetRoot().GetNext(), pSecondRest );
2112 else
2113 pSecondRest = nullptr;
2115 else
2117 pFirstRest = rMulti.GetRoot().GetNextPortion();
2118 pSecondRest = rMulti.GetRoot().GetNext() ?
2119 rMulti.GetRoot().GetNext()->GetNextPortion() : nullptr;
2120 if( pFirstRest )
2121 rMulti.GetRoot().SetNextPortion( nullptr );
2122 if( pSecondRest )
2123 rMulti.GetRoot().GetNext()->SetNextPortion( nullptr );
2124 rMulti.SetFormatted();
2125 nMultiLen = nMultiLen - rInf.GetIdx();
2128 // save some values
2129 const OUString* pOldText = &(rInf.GetText());
2130 const SwTwips nOldPaintOfst = rInf.GetPaintOfst();
2131 std::shared_ptr<const vcl::text::TextLayoutCache> const pOldCachedVclData(rInf.GetCachedVclData());
2132 rInf.SetCachedVclData(nullptr);
2134 OUString const aMultiStr( rInf.GetText().copy(0, sal_Int32(nMultiLen + rInf.GetIdx())) );
2135 rInf.SetText( aMultiStr );
2136 SwTextFormatInfo aInf( rInf, rMulti.GetRoot(), nActWidth );
2137 // Do we allow break cuts? The FirstMulti-Flag is evaluated during
2138 // line break determination.
2139 bool bFirstMulti = rInf.GetIdx() != rInf.GetLineStart();
2141 SwLinePortion *pNextFirst = nullptr;
2142 SwLinePortion *pNextSecond = nullptr;
2143 bool bRet = false;
2145 SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
2146 const bool bHasGrid = pGrid && SwTextGrid::LinesAndChars == pGrid->GetGridType();
2148 bool bRubyTop = false;
2150 if ( bHasGrid )
2151 bRubyTop = ! pGrid->GetRubyTextBelow();
2155 m_pCurr = &rMulti.GetRoot();
2156 m_nStart = nStartIdx;
2157 bRet = false;
2158 FormatReset( aInf );
2159 aInf.X( nTmpX );
2160 aInf.Width(nActWidth);
2161 aInf.RealWidth(nActWidth);
2162 aInf.SetFirstMulti( bFirstMulti );
2163 aInf.SetNumDone( rInf.IsNumDone() );
2164 aInf.SetFootnoteDone( rInf.IsFootnoteDone() );
2166 // tdf#157829: Bidi portions contain text; word wrapping should underflow.
2167 // By default, the SwTextFormatInfo constructor assumes the current index is the start of
2168 // a new line. As a result, Writer cut breaks MultiPortions as if they were wider than the
2169 // entire document. This is incorrect behavior for bidi portions.
2170 if (rMulti.IsBidi())
2172 aInf.SetLineStart(rInf.GetLineStart());
2175 // if there's a bookmark at the start of the MultiPortion, it will be
2176 // painted with the rotation etc. of the MultiPortion; move it *inside*
2177 // so it gets positioned correctly; currently there's no other portion
2178 // inserted between the end of WhichFirstPortion() and
2179 // BuildMultiPortion()
2180 if (rInf.GetLast()->GetWhichPor() == PortionType::Bookmark)
2182 auto const pBookmark(static_cast<SwBookmarkPortion*>(rInf.GetLast()));
2183 auto *const pPrevious = pBookmark->FindPrevPortion(rInf.GetRoot());
2184 assert(!pPrevious || pPrevious->GetNextPortion() == pBookmark);
2185 if (pPrevious)
2187 pPrevious->SetNextPortion(nullptr);
2189 rInf.SetLast(pPrevious);
2190 assert(m_pCurr->GetNextPortion() == nullptr);
2191 m_pCurr->SetNextPortion(pBookmark);
2194 if( pFirstRest )
2196 OSL_ENSURE( pFirstRest->InFieldGrp(), "BuildMulti: Fieldrest expected");
2197 SwFieldPortion *pField =
2198 static_cast<SwFieldPortion*>(pFirstRest)->Clone(
2199 static_cast<SwFieldPortion*>(pFirstRest)->GetExp() );
2200 pField->SetFollow( true );
2201 aInf.SetRest( pField );
2203 aInf.SetRuby( rMulti.IsRuby() && rMulti.OnTop() );
2205 // in grid mode we temporarily have to disable the grid for the ruby line
2206 const bool bOldGridModeAllowed = GetInfo().SnapToGrid();
2207 if ( bHasGrid && aInf.IsRuby() && bRubyTop )
2208 aInf.SetSnapToGrid( false );
2210 // If there's no more rubytext, then buildportion is forbidden
2211 if( pFirstRest || !aInf.IsRuby() )
2212 BuildPortions( aInf );
2214 aInf.SetSnapToGrid( bOldGridModeAllowed );
2216 rMulti.CalcSize( *this, aInf );
2217 m_pCurr->SetRealHeight( m_pCurr->Height() );
2219 if( rMulti.IsBidi() )
2221 pNextFirst = aInf.GetRest();
2222 break;
2225 if( rMulti.HasRotation() && !rMulti.IsDouble() )
2226 break;
2227 // second line has to be formatted
2228 else if( m_pCurr->GetLen()<nMultiLen || rMulti.IsRuby() || aInf.GetRest())
2230 TextFrameIndex const nFirstLen = m_pCurr->GetLen();
2231 delete m_pCurr->GetNext();
2232 m_pCurr->SetNext( new SwLineLayout() );
2233 m_pCurr = m_pCurr->GetNext();
2234 m_nStart = aInf.GetIdx();
2235 aInf.X( nTmpX );
2236 SwTextFormatInfo aTmp( aInf, *m_pCurr, nActWidth );
2237 if( rMulti.IsRuby() )
2239 aTmp.SetRuby( !rMulti.OnTop() );
2240 pNextFirst = aInf.GetRest();
2241 if( pSecondRest )
2243 OSL_ENSURE( pSecondRest->InFieldGrp(), "Fieldrest expected");
2244 SwFieldPortion *pField = static_cast<SwFieldPortion*>(pSecondRest)->Clone(
2245 static_cast<SwFieldPortion*>(pSecondRest)->GetExp() );
2246 pField->SetFollow( true );
2247 aTmp.SetRest( pField );
2249 if( !rMulti.OnTop() && nFirstLen < nMultiLen )
2250 bRet = true;
2252 else
2253 aTmp.SetRest( aInf.GetRest() );
2254 aInf.SetRest( nullptr );
2256 // in grid mode we temporarily have to disable the grid for the ruby line
2257 if ( bHasGrid && aTmp.IsRuby() && ! bRubyTop )
2258 aTmp.SetSnapToGrid( false );
2260 BuildPortions( aTmp );
2262 const SwLinePortion *pRightPortion = rMulti.OnRight() ?
2263 rMulti.GetRoot().GetNext()->GetNextPortion() : nullptr;
2264 if (pRightPortion)
2266 // The ruby text on the right is vertical.
2267 // The width and the height are swapped.
2268 SwTwips nHeight = pRightPortion->Height();
2269 // Keep room for the ruby text.
2270 rMulti.GetRoot().FindLastPortion()->AddPrtWidth( nHeight );
2273 aTmp.SetSnapToGrid( bOldGridModeAllowed );
2275 rMulti.CalcSize( *this, aInf );
2276 rMulti.GetRoot().SetRealHeight( rMulti.GetRoot().Height() );
2277 m_pCurr->SetRealHeight( m_pCurr->Height() );
2278 if( rMulti.IsRuby() )
2280 pNextSecond = aTmp.GetRest();
2281 if( pNextFirst )
2282 bRet = true;
2284 else
2285 pNextFirst = aTmp.GetRest();
2286 if( ( !aTmp.IsRuby() && nFirstLen + m_pCurr->GetLen() < nMultiLen )
2287 || aTmp.GetRest() )
2288 // our guess for width of multiportion was too small,
2289 // text did not fit into multiportion
2290 bRet = true;
2292 if( rMulti.IsRuby() )
2293 break;
2294 if( bRet )
2296 // our guess for multiportion width was too small,
2297 // we set min to act
2298 nMinWidth = nActWidth;
2299 nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4;
2300 if ( nActWidth == nMaxWidth && rInf.GetLineStart() == rInf.GetIdx() )
2301 // we have too less space, we must allow break cuts
2302 // ( the first multi flag is considered during TextPortion::Format_() )
2303 bFirstMulti = false;
2304 if( nActWidth <= nMinWidth )
2305 break;
2307 else
2309 // For Solaris, this optimization can causes trouble:
2310 // Setting this to the portion width ( = rMulti.Width() )
2311 // can make GetTextBreak inside SwTextGuess::Guess return too small
2312 // values. Therefore we add some extra twips.
2313 if( nActWidth > nTmpX + rMulti.Width() + 6 )
2314 nActWidth = nTmpX + rMulti.Width() + 6;
2315 nMaxWidth = nActWidth;
2316 nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4;
2317 if( nActWidth >= nMaxWidth )
2318 break;
2319 // we do not allow break cuts during formatting
2320 bFirstMulti = true;
2322 delete pNextFirst;
2323 pNextFirst = nullptr;
2324 } while ( true );
2326 m_pMulti = pOldMulti;
2328 m_pCurr = pOldCurr;
2329 m_nStart = nOldStart;
2330 SetPropFont( 0 );
2332 rMulti.SetLen( rMulti.GetRoot().GetLen() + ( rMulti.GetRoot().GetNext() ?
2333 rMulti.GetRoot().GetNext()->GetLen() : TextFrameIndex(0) ) );
2335 if( rMulti.IsDouble() )
2337 static_cast<SwDoubleLinePortion&>(rMulti).CalcBlanks( rInf );
2338 if( static_cast<SwDoubleLinePortion&>(rMulti).GetLineDiff() )
2340 SwLineLayout* pLine = &rMulti.GetRoot();
2341 if( static_cast<SwDoubleLinePortion&>(rMulti).GetLineDiff() > 0 )
2343 rInf.SetIdx( nStartIdx + pLine->GetLen() );
2344 pLine = pLine->GetNext();
2346 if( pLine )
2348 GetInfo().SetMulti( true );
2350 // If the fourth element bSkipKashida of function CalcNewBlock is true, multiportion will be showed in justification.
2351 // Kashida (Persian) is a type of justification used in some cursive scripts, particularly Arabic.
2352 // In contrast to white-space justification, which increases the length of a line of text by expanding spaces between words or individual letters,
2353 // kashida justification is accomplished by elongating characters at certain chosen points.
2354 // Kashida justification can be combined with white-space justification to various extents.
2355 // The default value of bSkipKashida (the 4th parameter passed to 'CalcNewBlock') is false.
2356 // Only when Adjust is SvxAdjust::Block ( alignment is justify ), multiportion will be showed in justification in new code.
2357 CalcNewBlock( pLine, nullptr, rMulti.Width(), GetAdjust() != SvxAdjust::Block );
2359 GetInfo().SetMulti( false );
2361 rInf.SetIdx( nStartIdx );
2363 if( static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets() )
2365 rMulti.Width( rMulti.Width() +
2366 static_cast<SwDoubleLinePortion&>(rMulti).BracketWidth() );
2367 GetInfo().X( nOldX );
2370 else
2372 rMulti.ActualizeTabulator();
2373 if( rMulti.IsRuby() )
2375 static_cast<SwRubyPortion&>(rMulti).Adjust( rInf );
2376 static_cast<SwRubyPortion&>(rMulti).CalcRubyOffset();
2379 if( rMulti.HasRotation() )
2381 SwTwips nH = rMulti.Width();
2382 SwTwips nAsc = rMulti.GetAscent() + ( nH - rMulti.Height() )/2;
2383 if( nAsc > nH )
2384 nAsc = nH;
2385 else if( nAsc < 0 )
2386 nAsc = 0;
2387 rMulti.Width( rMulti.Height() );
2388 rMulti.Height( sal_uInt16(nH) );
2389 rMulti.SetAscent( sal_uInt16(nAsc) );
2390 bRet = ( rInf.GetPos().X() + rMulti.Width() > rInf.Width() ) &&
2391 nStartIdx != rInf.GetLineStart();
2393 else if ( rMulti.IsBidi() )
2395 bRet = rMulti.GetLen() < nMultiLen || pNextFirst;
2398 // line break has to be performed!
2399 if( bRet )
2401 OSL_ENSURE( !pNextFirst || pNextFirst->InFieldGrp(),
2402 "BuildMultiPortion: Surprising restportion, field expected" );
2403 SwMultiPortion *pTmp;
2404 if( rMulti.IsDouble() )
2405 pTmp = new SwDoubleLinePortion( static_cast<SwDoubleLinePortion&>(rMulti),
2406 nMultiLen + rInf.GetIdx() );
2407 else if( rMulti.IsRuby() )
2409 OSL_ENSURE( !pNextSecond || pNextSecond->InFieldGrp(),
2410 "BuildMultiPortion: Surprising restportion, field expected" );
2412 if ( rInf.GetIdx() == rInf.GetLineStart() )
2414 // the ruby portion has to be split in two portions
2415 pTmp = new SwRubyPortion( static_cast<SwRubyPortion&>(rMulti),
2416 nMultiLen + rInf.GetIdx() );
2418 if( pNextSecond )
2420 pTmp->GetRoot().SetNext( new SwLineLayout() );
2421 pTmp->GetRoot().GetNext()->SetNextPortion( pNextSecond );
2423 pTmp->SetFollowField();
2425 else
2427 // we try to keep our ruby portion together
2428 lcl_TruncateMultiPortion(rMulti, rInf, nStartIdx);
2429 pTmp = nullptr;
2430 // A follow field portion may still be waiting. If nobody wants
2431 // it, we delete it.
2432 delete pNextSecond;
2435 else if( rMulti.HasRotation() )
2437 // we try to keep our rotated portion together
2438 lcl_TruncateMultiPortion(rMulti, rInf, nStartIdx);
2439 pTmp = new SwRotatedPortion( nMultiLen + rInf.GetIdx(),
2440 rMulti.GetDirection() );
2442 // during a recursion of BuildMultiPortions we may not build
2443 // a new SwBidiPortion, this would cause a memory leak
2444 else if( rMulti.IsBidi() && ! m_pMulti )
2446 auto nTruncType = lcl_BidiPortionNeedsTruncation(rMulti, rInf, aInf, nStartIdx);
2447 if (nTruncType != BidiTruncationType::None)
2449 lcl_TruncateMultiPortion(rMulti, rInf, nStartIdx, nTruncType);
2452 // If there is a HolePortion at the end of the bidi portion,
2453 // it has to be moved behind the bidi portion. Otherwise
2454 // the visual cursor travelling gets into trouble.
2455 SwLineLayout& aRoot = rMulti.GetRoot();
2456 SwLinePortion* pPor = aRoot.GetFirstPortion();
2457 while ( pPor )
2459 if ( pPor->GetNextPortion() && pPor->GetNextPortion()->IsHolePortion() )
2461 SwLinePortion* pHolePor = pPor->GetNextPortion();
2462 pPor->SetNextPortion( nullptr );
2463 aRoot.SetLen( aRoot.GetLen() - pHolePor->GetLen() );
2464 rMulti.SetLen( rMulti.GetLen() - pHolePor->GetLen() );
2465 rMulti.SetNextPortion( pHolePor );
2466 break;
2468 pPor = pPor->GetNextPortion();
2471 pTmp = new SwBidiPortion( nMultiLen + rInf.GetIdx(),
2472 static_cast<SwBidiPortion&>(rMulti).GetLevel() );
2474 else
2475 pTmp = nullptr;
2477 if ( ! rMulti.GetLen() && rInf.GetLast() )
2479 SeekAndChgBefore( rInf );
2480 rInf.GetLast()->FormatEOL( rInf );
2483 if( pNextFirst && pTmp )
2485 pTmp->SetFollowField();
2486 pTmp->GetRoot().SetNextPortion( pNextFirst );
2488 else
2489 // A follow field portion is still waiting. If nobody wants it,
2490 // we delete it.
2491 delete pNextFirst;
2493 rInf.SetRest( pTmp );
2496 rInf.SetCachedVclData(pOldCachedVclData);
2497 rInf.SetText( *pOldText );
2498 rInf.SetPaintOfst( nOldPaintOfst );
2499 rInf.SetStop( aInf.IsStop() );
2500 rInf.SetNumDone( true );
2501 rInf.SetFootnoteDone( true );
2502 SeekAndChg( rInf );
2503 delete pFirstRest;
2504 delete pSecondRest;
2505 oFontSave.reset();
2506 return bRet;
2509 static bool IsIncompleteRuby(const SwMultiPortion& rHelpMulti)
2511 return rHelpMulti.IsRuby() && static_cast<const SwRubyPortion&>(rHelpMulti).GetRubyOffset() < TextFrameIndex(COMPLETE_STRING);
2514 // When a fieldportion at the end of line breaks and needs a following
2515 // fieldportion in the next line, then the "restportion" of the formatinfo
2516 // has to be set. Normally this happens during the formatting of the first
2517 // part of the fieldportion.
2518 // But sometimes the formatting starts at the line with the following part,
2519 // especially when the following part is on the next page.
2520 // In this case the MakeRestPortion-function has to create the following part.
2521 // The first parameter is the line that contains possibly a first part
2522 // of a field. When the function finds such field part, it creates the right
2523 // restportion. This may be a multiportion, e.g. if the field is surrounded by
2524 // a doubleline- or ruby-portion.
2525 // The second parameter is the start index of the line.
2526 SwLinePortion* SwTextFormatter::MakeRestPortion( const SwLineLayout* pLine,
2527 TextFrameIndex nPosition)
2529 if( !nPosition )
2530 return nullptr;
2531 TextFrameIndex nMultiPos = nPosition - pLine->GetLen();
2532 const SwMultiPortion *pTmpMulti = nullptr;
2533 const SwMultiPortion *pHelpMulti = nullptr;
2534 const SwLinePortion* pPor = pLine->GetFirstPortion();
2535 SwFieldPortion *pField = nullptr;
2536 while( pPor )
2538 if( pPor->GetLen() && !pHelpMulti )
2540 nMultiPos = nMultiPos + pPor->GetLen();
2541 pTmpMulti = nullptr;
2543 if( pPor->InFieldGrp() )
2545 if( !pHelpMulti )
2546 pTmpMulti = nullptr;
2547 pField = const_cast<SwFieldPortion*>(static_cast<const SwFieldPortion*>(pPor));
2549 else if( pPor->IsMultiPortion() )
2551 OSL_ENSURE( !pHelpMulti || pHelpMulti->IsBidi(),
2552 "Nested multiportions are forbidden." );
2554 pField = nullptr;
2555 pTmpMulti = static_cast<const SwMultiPortion*>(pPor);
2557 pPor = pPor->GetNextPortion();
2558 // If the last portion is a multi-portion, we enter it
2559 // and look for a field portion inside.
2560 // If we are already in a multiportion, we could change to the
2561 // next line
2562 if( !pPor && pTmpMulti )
2564 if( pHelpMulti )
2565 { // We're already inside the multiportion, let's take the second
2566 // line, if we are in a double line portion
2567 if( !pHelpMulti->IsRuby() )
2568 pPor = pHelpMulti->GetRoot().GetNext();
2569 pTmpMulti = nullptr;
2571 else
2572 { // Now we enter a multiportion, in a ruby portion we take the
2573 // main line, not the phonetic line, in a doublelineportion we
2574 // starts with the first line.
2575 pHelpMulti = pTmpMulti;
2576 nMultiPos = nMultiPos - pHelpMulti->GetLen();
2577 if( pHelpMulti->IsRuby() && pHelpMulti->OnTop() )
2578 pPor = pHelpMulti->GetRoot().GetNext();
2579 else
2580 pPor = pHelpMulti->GetRoot().GetFirstPortion();
2584 if( pField && !pField->HasFollow() )
2585 pField = nullptr;
2587 SwLinePortion *pRest = nullptr;
2588 if( pField )
2590 const SwTextAttr *pHint = GetAttr(nPosition - TextFrameIndex(1));
2591 if ( pHint
2592 && ( pHint->Which() == RES_TXTATR_FIELD
2593 || pHint->Which() == RES_TXTATR_ANNOTATION ) )
2595 pRest = NewFieldPortion( GetInfo(), pHint );
2596 if( pRest->InFieldGrp() )
2597 static_cast<SwFieldPortion*>(pRest)->TakeNextOffset( pField );
2598 else
2600 delete pRest;
2601 pRest = nullptr;
2605 if( !pHelpMulti )
2606 return pRest;
2608 nPosition = nMultiPos + pHelpMulti->GetLen();
2609 std::optional<SwMultiCreator> pCreate = GetInfo().GetMultiCreator( nMultiPos, nullptr );
2611 if ( !pCreate )
2613 OSL_ENSURE( !pHelpMulti->GetLen(), "Multiportion without attribute?" );
2614 if ( nMultiPos )
2615 --nMultiPos;
2616 pCreate = GetInfo().GetMultiCreator( --nMultiPos, nullptr );
2619 if (!pCreate)
2620 return pRest;
2622 if( pRest || nMultiPos > nPosition || IsIncompleteRuby(*pHelpMulti))
2624 SwMultiPortion* pTmp;
2625 if( pHelpMulti->IsDouble() )
2626 pTmp = new SwDoubleLinePortion( *pCreate, nMultiPos );
2627 else if( pHelpMulti->IsBidi() )
2628 pTmp = new SwBidiPortion( nMultiPos, pCreate->nLevel );
2629 else if (IsIncompleteRuby(*pHelpMulti) && pCreate->pAttr)
2631 TextFrameIndex nRubyOffset = static_cast<const SwRubyPortion*>(pHelpMulti)->GetRubyOffset();
2632 pTmp = new SwRubyPortion( *pCreate, *GetInfo().GetFont(),
2633 m_pFrame->GetDoc().getIDocumentSettingAccess(),
2634 nMultiPos, nRubyOffset,
2635 GetInfo() );
2637 else if( pHelpMulti->HasRotation() )
2638 pTmp = new SwRotatedPortion( nMultiPos, pHelpMulti->GetDirection() );
2639 else
2641 return pRest;
2643 pCreate.reset();
2644 pTmp->SetFollowField();
2645 if( pRest )
2647 SwLineLayout *pLay = &pTmp->GetRoot();
2648 if( pTmp->IsRuby() && pTmp->OnTop() )
2650 pLay->SetNext( new SwLineLayout() );
2651 pLay = pLay->GetNext();
2653 pLay->SetNextPortion( pRest );
2655 return pTmp;
2657 return pRest;
2660 // SwTextCursorSave notes the start and current line of a SwTextCursor,
2661 // sets them to the values for GetModelPositionForViewPoint inside a multiportion
2662 // and restores them in the destructor.
2663 SwTextCursorSave::SwTextCursorSave( SwTextCursor* pCursor,
2664 SwMultiPortion* pMulti,
2665 SwTwips nY,
2666 SwTwips& nX,
2667 TextFrameIndex const nCurrStart,
2668 tools::Long nSpaceAdd )
2669 : pTextCursor(pCursor),
2670 pCurr(pCursor->m_pCurr),
2671 nStart(pCursor->m_nStart)
2673 pCursor->m_nStart = nCurrStart;
2674 pCursor->m_pCurr = &pMulti->GetRoot();
2675 while( pCursor->Y() + pCursor->GetLineHeight() < nY &&
2676 pCursor->Next() )
2677 ; // nothing
2678 nWidth = pCursor->m_pCurr->Width();
2679 nOldProp = pCursor->GetPropFont();
2681 if ( pMulti->IsDouble() || pMulti->IsBidi() )
2683 bSpaceChg = pMulti->ChgSpaceAdd( pCursor->m_pCurr, nSpaceAdd );
2685 TextFrameIndex nSpaceCnt;
2686 if ( pMulti->IsDouble() )
2688 pCursor->SetPropFont( 50 );
2689 nSpaceCnt = static_cast<SwDoubleLinePortion*>(pMulti)->GetSpaceCnt();
2691 else
2693 TextFrameIndex const nOldIdx = pCursor->GetInfo().GetIdx();
2694 pCursor->GetInfo().SetIdx ( nCurrStart );
2695 nSpaceCnt = static_cast<SwBidiPortion*>(pMulti)->GetSpaceCnt(pCursor->GetInfo());
2696 pCursor->GetInfo().SetIdx ( nOldIdx );
2699 if( nSpaceAdd > 0 && !pMulti->HasTabulator() )
2700 pCursor->m_pCurr->Width( o3tl::narrowing<sal_uInt16>(nWidth + nSpaceAdd * sal_Int32(nSpaceCnt) / SPACING_PRECISION_FACTOR) );
2702 // For a BidiPortion we have to calculate the offset from the
2703 // end of the portion
2704 if ( nX && pMulti->IsBidi() )
2705 nX = pCursor->m_pCurr->Width() - nX;
2707 else
2708 bSpaceChg = false;
2711 SwTextCursorSave::~SwTextCursorSave()
2713 if( bSpaceChg )
2714 SwDoubleLinePortion::ResetSpaceAdd( pTextCursor->m_pCurr );
2715 pTextCursor->m_pCurr->Width( nWidth );
2716 pTextCursor->m_pCurr = pCurr;
2717 pTextCursor->m_nStart = nStart;
2718 pTextCursor->SetPropFont( nOldProp );
2721 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */