summaryrefslogtreecommitdiffstats
path: root/sw/source/core/text/pormulti.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sw/source/core/text/pormulti.cxx')
-rw-r--r--sw/source/core/text/pormulti.cxx2593
1 files changed, 2593 insertions, 0 deletions
diff --git a/sw/source/core/text/pormulti.cxx b/sw/source/core/text/pormulti.cxx
new file mode 100644
index 000000000..518294e12
--- /dev/null
+++ b/sw/source/core/text/pormulti.cxx
@@ -0,0 +1,2593 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <deque>
+#include <memory>
+
+#include <hintids.hxx>
+
+#include <editeng/twolinesitem.hxx>
+#include <editeng/charrotateitem.hxx>
+#include <vcl/outdev.hxx>
+#include <txatbase.hxx>
+#include <fmtruby.hxx>
+#include <txtatr.hxx>
+#include <charfmt.hxx>
+#include <layfrm.hxx>
+#include <SwPortionHandler.hxx>
+#include "pormulti.hxx"
+#include "inftxt.hxx"
+#include "itrpaint.hxx"
+#include <viewopt.hxx>
+#include "itrform2.hxx"
+#include "porfld.hxx"
+#include "porglue.hxx"
+#include "porrst.hxx"
+#include <pagefrm.hxx>
+#include <rowfrm.hxx>
+#include <tgrditem.hxx>
+#include <swtable.hxx>
+#include <fmtfsize.hxx>
+#include <doc.hxx>
+
+using namespace ::com::sun::star;
+
+// A SwMultiPortion is not a simple portion,
+// it's a container, which contains almost a SwLineLayoutPortion.
+// This SwLineLayout could be followed by other textportions via pPortion
+// and by another SwLineLayout via pNext to realize a doubleline portion.
+SwMultiPortion::~SwMultiPortion()
+{
+}
+
+void SwMultiPortion::Paint( const SwTextPaintInfo & ) const
+{
+ OSL_FAIL( "Don't try SwMultiPortion::Paint, try SwTextPainter::PaintMultiPortion" );
+}
+
+// Summarize the internal lines to calculate the (external) size.
+// The internal line has to calculate first.
+void SwMultiPortion::CalcSize( SwTextFormatter& rLine, SwTextFormatInfo &rInf )
+{
+ Width( 0 );
+ Height( 0 );
+ SetAscent( 0 );
+ SetFlyInContent( false );
+ SwLineLayout *pLay = &GetRoot();
+ do
+ {
+ pLay->CalcLine( rLine, rInf );
+ if( rLine.IsFlyInCntBase() )
+ SetFlyInContent( true );
+ if( IsRuby() && ( OnTop() == ( pLay == &GetRoot() ) ) )
+ {
+ // An empty phonetic line don't need an ascent or a height.
+ if( !pLay->Width() )
+ {
+ pLay->SetAscent( 0 );
+ pLay->Height( 0 );
+ }
+ if( OnTop() )
+ SetAscent( GetAscent() + pLay->Height() );
+ }
+ else
+ SetAscent( GetAscent() + pLay->GetAscent() );
+
+ // Increase the line height, except for ruby text on the right.
+ if ( !IsRuby() || !OnRight() || pLay == &GetRoot() )
+ Height( Height() + pLay->Height() );
+ else
+ {
+ // We already added the width after building the portion,
+ // so no need to add it twice.
+ break;
+ }
+
+ if( Width() < pLay->Width() )
+ Width( pLay->Width() );
+ pLay = pLay->GetNext();
+ } while ( pLay );
+ if( !HasBrackets() )
+ return;
+
+ sal_uInt16 nTmp = static_cast<SwDoubleLinePortion*>(this)->GetBrackets()->nHeight;
+ if( nTmp > Height() )
+ {
+ const sal_uInt16 nAdd = ( nTmp - Height() ) / 2;
+ GetRoot().SetAscent( GetRoot().GetAscent() + nAdd );
+ GetRoot().Height( GetRoot().Height() + nAdd );
+ Height( nTmp );
+ }
+ nTmp = static_cast<SwDoubleLinePortion*>(this)->GetBrackets()->nAscent;
+ if( nTmp > GetAscent() )
+ SetAscent( nTmp );
+}
+
+tools::Long SwMultiPortion::CalcSpacing( tools::Long , const SwTextSizeInfo & ) const
+{
+ return 0;
+}
+
+bool SwMultiPortion::ChgSpaceAdd( SwLineLayout*, tools::Long ) const
+{
+ return false;
+}
+
+void SwMultiPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Text( GetLen(), GetWhichPor() );
+}
+
+void SwMultiPortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwMultiPortion"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ // Intentionally not incrementing nOffset here, one of the child portions will do that.
+
+ const SwLineLayout* pLine = &GetRoot();
+ while (pLine)
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwLineLayout"));
+ pLine->dumpAsXmlAttributes(pWriter, rText, nOffset);
+ const SwLinePortion* pPor = pLine->GetFirstPortion();
+ while (pPor)
+ {
+ pPor->dumpAsXml(pWriter, rText, nOffset);
+ pPor = pPor->GetNextPortion();
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+ pLine = pLine->GetNext();
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+// sets the tabulator-flag, if there's any tabulator-portion inside.
+void SwMultiPortion::ActualizeTabulator()
+{
+ SwLinePortion* pPor = GetRoot().GetFirstPortion();
+ // First line
+ for( m_bTab1 = m_bTab2 = false; pPor; pPor = pPor->GetNextPortion() )
+ if( pPor->InTabGrp() )
+ SetTab1( true );
+ if( GetRoot().GetNext() )
+ {
+ // Second line
+ pPor = GetRoot().GetNext()->GetFirstPortion();
+ do
+ {
+ if( pPor->InTabGrp() )
+ SetTab2( true );
+ pPor = pPor->GetNextPortion();
+ } while ( pPor );
+ }
+}
+
+SwRotatedPortion::SwRotatedPortion( const SwMultiCreator& rCreate,
+ TextFrameIndex const nEnd, bool bRTL )
+ : SwMultiPortion( nEnd )
+{
+ const SvxCharRotateItem* pRot = static_cast<const SvxCharRotateItem*>(rCreate.pItem);
+ if( !pRot )
+ {
+ const SwTextAttr& rAttr = *rCreate.pAttr;
+ const SfxPoolItem *const pItem =
+ CharFormat::GetItem(rAttr, RES_CHRATR_ROTATE);
+ if ( pItem )
+ {
+ pRot = static_cast<const SvxCharRotateItem*>(pItem);
+ }
+ }
+ if( pRot )
+ {
+ sal_uInt8 nDir;
+ if ( bRTL )
+ nDir = pRot->IsBottomToTop() ? 3 : 1;
+ else
+ nDir = pRot->IsBottomToTop() ? 1 : 3;
+
+ SetDirection( nDir );
+ }
+}
+
+SwBidiPortion::SwBidiPortion(TextFrameIndex const nEnd, sal_uInt8 nLv)
+ : SwMultiPortion( nEnd ), m_nLevel( nLv )
+{
+ SetBidi();
+
+ if ( m_nLevel % 2 )
+ SetDirection( DIR_RIGHT2LEFT );
+ else
+ SetDirection( DIR_LEFT2RIGHT );
+}
+
+tools::Long SwBidiPortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo& rInf ) const
+{
+ return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt(rInf)) * nSpaceAdd / SPACING_PRECISION_FACTOR;
+}
+
+bool SwBidiPortion::ChgSpaceAdd( SwLineLayout* pCurr, tools::Long nSpaceAdd ) const
+{
+ if( !HasTabulator() && nSpaceAdd > 0 && !pCurr->IsSpaceAdd() )
+ {
+ pCurr->CreateSpaceAdd();
+ pCurr->SetLLSpaceAdd( nSpaceAdd, 0 );
+ return true;
+ }
+
+ return false;
+}
+
+TextFrameIndex SwBidiPortion::GetSpaceCnt(const SwTextSizeInfo &rInf) const
+{
+ // Calculate number of blanks for justified alignment
+ TextFrameIndex nTmpStart = rInf.GetIdx();
+ TextFrameIndex nNull(0);
+ TextFrameIndex nBlanks(0);
+
+ for (SwLinePortion* pPor = GetRoot().GetFirstPortion(); pPor; pPor = pPor->GetNextPortion())
+ {
+ if( pPor->InTextGrp() )
+ nBlanks = nBlanks + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
+ else if ( pPor->IsMultiPortion() &&
+ static_cast<SwMultiPortion*>(pPor)->IsBidi() )
+ nBlanks = nBlanks + static_cast<SwBidiPortion*>(pPor)->GetSpaceCnt( rInf );
+
+ const_cast<SwTextSizeInfo &>(rInf).SetIdx( rInf.GetIdx() + pPor->GetLen() );
+ }
+ const_cast<SwTextSizeInfo &>(rInf).SetIdx( nTmpStart );
+ return nBlanks;
+}
+
+// This constructor is for the continuation of a doubleline portion
+// in the next line.
+// It takes the same brackets and if the original has no content except
+// brackets, these will be deleted.
+SwDoubleLinePortion::SwDoubleLinePortion(
+ SwDoubleLinePortion& rDouble, TextFrameIndex const nEnd)
+ : SwMultiPortion(nEnd)
+ , m_nLineDiff(0)
+ , m_nBlank1(0)
+ , m_nBlank2(0)
+{
+ SetDirection( rDouble.GetDirection() );
+ SetDouble();
+ if( rDouble.GetBrackets() )
+ {
+ SetBrackets( rDouble );
+ // An empty multiportion needs no brackets.
+ // Notice: GetLen() might be zero, if the multiportion contains
+ // the second part of a field and the width might be zero, if
+ // it contains a note only. In this cases the brackets are okay.
+ // But if the length and the width are both zero, the portion
+ // is really empty.
+ if( rDouble.Width() == rDouble.BracketWidth() )
+ rDouble.ClearBrackets();
+ }
+}
+
+// This constructor uses the textattribute to get the right brackets.
+// The textattribute could be a 2-line-attribute or a character- or
+// internet style, which contains the 2-line-attribute.
+SwDoubleLinePortion::SwDoubleLinePortion(
+ const SwMultiCreator& rCreate, TextFrameIndex const nEnd)
+ : SwMultiPortion(nEnd)
+ , m_pBracket(new SwBracket)
+ , m_nLineDiff(0)
+ , m_nBlank1(0)
+ , m_nBlank2(0)
+{
+ m_pBracket->nAscent = 0;
+ m_pBracket->nHeight = 0;
+ m_pBracket->nPreWidth = 0;
+ m_pBracket->nPostWidth = 0;
+
+ SetDouble();
+ const SvxTwoLinesItem* pTwo = static_cast<const SvxTwoLinesItem*>(rCreate.pItem);
+ if( pTwo )
+ m_pBracket->nStart = TextFrameIndex(0);
+ else
+ {
+ const SwTextAttr& rAttr = *rCreate.pAttr;
+ m_pBracket->nStart = rCreate.nStartOfAttr;
+
+ const SfxPoolItem * const pItem =
+ CharFormat::GetItem( rAttr, RES_CHRATR_TWO_LINES );
+ if ( pItem )
+ {
+ pTwo = static_cast<const SvxTwoLinesItem*>(pItem);
+ }
+ }
+ if( pTwo )
+ {
+ m_pBracket->cPre = pTwo->GetStartBracket();
+ m_pBracket->cPost = pTwo->GetEndBracket();
+ }
+ else
+ {
+ m_pBracket->cPre = 0;
+ m_pBracket->cPost = 0;
+ }
+ SwFontScript nTmp = SW_SCRIPTS;
+ if( m_pBracket->cPre > 255 )
+ {
+ OUString aText(m_pBracket->cPre);
+ nTmp = SwScriptInfo::WhichFont(0, aText);
+ }
+ m_pBracket->nPreScript = nTmp;
+ nTmp = SW_SCRIPTS;
+ if( m_pBracket->cPost > 255 )
+ {
+ OUString aText(m_pBracket->cPost);
+ nTmp = SwScriptInfo::WhichFont(0, aText);
+ }
+ m_pBracket->nPostScript = nTmp;
+
+ if( !m_pBracket->cPre && !m_pBracket->cPost )
+ {
+ m_pBracket.reset();
+ }
+
+ // double line portions have the same direction as the frame directions
+ if ( rCreate.nLevel % 2 )
+ SetDirection( DIR_RIGHT2LEFT );
+ else
+ SetDirection( DIR_LEFT2RIGHT );
+}
+
+// paints the wished bracket,
+// if the multiportion has surrounding brackets.
+// The X-position of the SwTextPaintInfo will be modified:
+// the open bracket sets position behind itself,
+// the close bracket in front of itself.
+void SwDoubleLinePortion::PaintBracket( SwTextPaintInfo &rInf,
+ tools::Long nSpaceAdd,
+ bool bOpen ) const
+{
+ sal_Unicode cCh = bOpen ? m_pBracket->cPre : m_pBracket->cPost;
+ if( !cCh )
+ return;
+ const sal_uInt16 nChWidth = bOpen ? PreWidth() : PostWidth();
+ if( !nChWidth )
+ return;
+ if( !bOpen )
+ rInf.X( rInf.X() + Width() - PostWidth() +
+ ( nSpaceAdd > 0 ? CalcSpacing( nSpaceAdd, rInf ) : 0 ) );
+
+ SwBlankPortion aBlank( cCh, true );
+ aBlank.SetAscent( m_pBracket->nAscent );
+ aBlank.Width( nChWidth );
+ aBlank.Height( m_pBracket->nHeight );
+ {
+ SwFont aTmpFnt( *rInf.GetFont() );
+ SwFontScript nAct = bOpen ? m_pBracket->nPreScript : m_pBracket->nPostScript;
+ if( SW_SCRIPTS > nAct )
+ aTmpFnt.SetActual( nAct );
+ aTmpFnt.SetProportion( 100 );
+ SwFontSave aSave( rInf, &aTmpFnt );
+ aBlank.Paint( rInf );
+ }
+ if( bOpen )
+ rInf.X( rInf.X() + PreWidth() );
+}
+
+// creates the bracket-structure
+// and fills it, if not both characters are 0x00.
+void SwDoubleLinePortion::SetBrackets( const SwDoubleLinePortion& rDouble )
+{
+ if( rDouble.m_pBracket )
+ {
+ m_pBracket.reset( new SwBracket );
+ m_pBracket->cPre = rDouble.m_pBracket->cPre;
+ m_pBracket->cPost = rDouble.m_pBracket->cPost;
+ m_pBracket->nPreScript = rDouble.m_pBracket->nPreScript;
+ m_pBracket->nPostScript = rDouble.m_pBracket->nPostScript;
+ m_pBracket->nStart = rDouble.m_pBracket->nStart;
+ }
+}
+
+// calculates the size of the brackets => pBracket,
+// reduces the nMaxWidth-parameter ( minus bracket-width )
+// and moves the rInf-x-position behind the opening bracket.
+void SwDoubleLinePortion::FormatBrackets( SwTextFormatInfo &rInf, SwTwips& nMaxWidth )
+{
+ nMaxWidth -= rInf.X();
+ SwFont aTmpFnt( *rInf.GetFont() );
+ aTmpFnt.SetProportion( 100 );
+ m_pBracket->nAscent = 0;
+ m_pBracket->nHeight = 0;
+ if( m_pBracket->cPre )
+ {
+ OUString aStr( m_pBracket->cPre );
+ SwFontScript nActualScr = aTmpFnt.GetActual();
+ if( SW_SCRIPTS > m_pBracket->nPreScript )
+ aTmpFnt.SetActual( m_pBracket->nPreScript );
+ SwFontSave aSave( rInf, &aTmpFnt );
+ SwPosSize aSize = rInf.GetTextSize( aStr );
+ m_pBracket->nAscent = rInf.GetAscent();
+ m_pBracket->nHeight = aSize.Height();
+ aTmpFnt.SetActual( nActualScr );
+ if( nMaxWidth > aSize.Width() )
+ {
+ m_pBracket->nPreWidth = aSize.Width();
+ nMaxWidth -= aSize.Width();
+ rInf.X( rInf.X() + aSize.Width() );
+ }
+ else
+ {
+ m_pBracket->nPreWidth = 0;
+ nMaxWidth = 0;
+ }
+ }
+ else
+ m_pBracket->nPreWidth = 0;
+ if( m_pBracket->cPost )
+ {
+ OUString aStr( m_pBracket->cPost );
+ if( SW_SCRIPTS > m_pBracket->nPostScript )
+ aTmpFnt.SetActual( m_pBracket->nPostScript );
+ SwFontSave aSave( rInf, &aTmpFnt );
+ SwPosSize aSize = rInf.GetTextSize( aStr );
+ const sal_uInt16 nTmpAsc = rInf.GetAscent();
+ if( nTmpAsc > m_pBracket->nAscent )
+ {
+ m_pBracket->nHeight += nTmpAsc - m_pBracket->nAscent;
+ m_pBracket->nAscent = nTmpAsc;
+ }
+ if( aSize.Height() > m_pBracket->nHeight )
+ m_pBracket->nHeight = aSize.Height();
+ if( nMaxWidth > aSize.Width() )
+ {
+ m_pBracket->nPostWidth = aSize.Width();
+ nMaxWidth -= aSize.Width();
+ }
+ else
+ {
+ m_pBracket->nPostWidth = 0;
+ nMaxWidth = 0;
+ }
+ }
+ else
+ m_pBracket->nPostWidth = 0;
+ nMaxWidth += rInf.X();
+}
+
+// calculates the number of blanks in each line and
+// the difference of the width of the two lines.
+// These results are used from the text adjustment.
+void SwDoubleLinePortion::CalcBlanks( SwTextFormatInfo &rInf )
+{
+ SwLinePortion* pPor = GetRoot().GetFirstPortion();
+ TextFrameIndex nNull(0);
+ TextFrameIndex nStart = rInf.GetIdx();
+ SetTab1( false );
+ SetTab2( false );
+ for (m_nBlank1 = TextFrameIndex(0); pPor; pPor = pPor->GetNextPortion())
+ {
+ if( pPor->InTextGrp() )
+ m_nBlank1 = m_nBlank1 + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
+ rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
+ if( pPor->InTabGrp() )
+ SetTab1( true );
+ }
+ m_nLineDiff = GetRoot().Width();
+ if( GetRoot().GetNext() )
+ {
+ pPor = GetRoot().GetNext()->GetFirstPortion();
+ m_nLineDiff -= GetRoot().GetNext()->Width();
+ }
+ for (m_nBlank2 = TextFrameIndex(0); pPor; pPor = pPor->GetNextPortion())
+ {
+ if( pPor->InTextGrp() )
+ m_nBlank2 = m_nBlank2 + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
+ rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
+ if( pPor->InTabGrp() )
+ SetTab2( true );
+ }
+ rInf.SetIdx( nStart );
+}
+
+tools::Long SwDoubleLinePortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo & ) const
+{
+ return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt()) * nSpaceAdd / SPACING_PRECISION_FACTOR;
+}
+
+// Merges the spaces for text adjustment from the inner and outer part.
+// Inside the doubleline portion the wider line has no spaceadd-array, the
+// smaller line has such an array to reach width of the wider line.
+// If the surrounding line has text adjustment and the doubleline portion
+// contains no tabulator, it is necessary to create/manipulate the inner
+// space arrays.
+bool SwDoubleLinePortion::ChgSpaceAdd( SwLineLayout* pCurr,
+ tools::Long nSpaceAdd ) const
+{
+ bool bRet = false;
+ if( !HasTabulator() && nSpaceAdd > 0 )
+ {
+ if( !pCurr->IsSpaceAdd() )
+ {
+ // The wider line gets the spaceadd from the surrounding line direct
+ pCurr->CreateSpaceAdd();
+ pCurr->SetLLSpaceAdd( nSpaceAdd, 0 );
+ bRet = true;
+ }
+ else
+ {
+ sal_Int32 const nMyBlank = sal_Int32(GetSmallerSpaceCnt());
+ sal_Int32 const nOther = sal_Int32(GetSpaceCnt());
+ SwTwips nMultiSpace = pCurr->GetLLSpaceAdd( 0 ) * nMyBlank + nOther * nSpaceAdd;
+
+ if( nMyBlank )
+ nMultiSpace /= sal_Int32(nMyBlank);
+
+// pCurr->SetLLSpaceAdd( nMultiSpace, 0 );
+ // #i65711# SetLLSpaceAdd replaces the first value,
+ // instead we want to insert a new first value:
+ std::vector<tools::Long>* pVec = pCurr->GetpLLSpaceAdd();
+ pVec->insert( pVec->begin(), nMultiSpace );
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+// cancels the manipulation from SwDoubleLinePortion::ChangeSpaceAdd(..)
+void SwDoubleLinePortion::ResetSpaceAdd( SwLineLayout* pCurr )
+{
+ pCurr->RemoveFirstLLSpaceAdd();
+ if( !pCurr->GetLLSpaceAddCount() )
+ pCurr->FinishSpaceAdd();
+}
+
+SwDoubleLinePortion::~SwDoubleLinePortion()
+{
+}
+
+// constructs a ruby portion, i.e. an additional text is displayed
+// beside the main text, e.g. phonetic characters.
+SwRubyPortion::SwRubyPortion(const SwRubyPortion& rRuby, TextFrameIndex const nEnd)
+ : SwMultiPortion( nEnd )
+ , m_nRubyOffset( rRuby.GetRubyOffset() )
+ , m_nAdjustment( rRuby.GetAdjustment() )
+{
+ SetDirection( rRuby.GetDirection() );
+ SetRubyPosition( rRuby.GetRubyPosition() );
+ SetRuby();
+}
+
+// constructs a ruby portion, i.e. an additional text is displayed
+// beside the main text, e.g. phonetic characters.
+SwRubyPortion::SwRubyPortion( const SwMultiCreator& rCreate, const SwFont& rFnt,
+ const IDocumentSettingAccess& rIDocumentSettingAccess,
+ TextFrameIndex const nEnd, TextFrameIndex const nOffs,
+ const SwTextSizeInfo &rInf )
+ : SwMultiPortion( nEnd )
+{
+ SetRuby();
+ OSL_ENSURE( SwMultiCreatorId::Ruby == rCreate.nId, "Ruby expected" );
+ OSL_ENSURE( RES_TXTATR_CJK_RUBY == rCreate.pAttr->Which(), "Wrong attribute" );
+ const SwFormatRuby& rRuby = rCreate.pAttr->GetRuby();
+ m_nAdjustment = rRuby.GetAdjustment();
+ m_nRubyOffset = nOffs;
+
+ const SwTextFrame *pFrame = rInf.GetTextFrame();
+ RubyPosition ePos = static_cast<RubyPosition>( rRuby.GetPosition() );
+
+ // RIGHT is designed for horizontal writing mode only.
+ if ( ePos == RubyPosition::RIGHT && pFrame->IsVertical() )
+ ePos = RubyPosition::ABOVE;
+
+ // In grid mode we force the ruby text to the upper or lower line
+ if ( rInf.SnapToGrid() )
+ {
+ SwTextGridItem const*const pGrid( GetGridItem(pFrame->FindPageFrame()) );
+ if ( pGrid )
+ ePos = pGrid->GetRubyTextBelow() ? RubyPosition::BELOW : RubyPosition::ABOVE;
+ }
+
+ SetRubyPosition( ePos );
+
+ const SwCharFormat *const pFormat =
+ static_txtattr_cast<SwTextRuby const*>(rCreate.pAttr)->GetCharFormat();
+ std::unique_ptr<SwFont> pRubyFont;
+ if( pFormat )
+ {
+ const SwAttrSet& rSet = pFormat->GetAttrSet();
+ pRubyFont.reset(new SwFont( rFnt ));
+ pRubyFont->SetDiffFnt( &rSet, &rIDocumentSettingAccess );
+
+ // we do not allow a vertical font for the ruby text
+ pRubyFont->SetVertical( rFnt.GetOrientation() , OnRight() );
+ }
+
+ OUString aStr = rRuby.GetText().copy( sal_Int32(nOffs) );
+ SwFieldPortion *pField = new SwFieldPortion( aStr, std::move(pRubyFont) );
+ pField->SetNextOffset( nOffs );
+ pField->SetFollow( true );
+
+ if( OnTop() )
+ GetRoot().SetNextPortion( pField );
+ else
+ {
+ GetRoot().SetNext( new SwLineLayout() );
+ GetRoot().GetNext()->SetNextPortion( pField );
+ }
+
+ // ruby portions have the same direction as the frame directions
+ if ( rCreate.nLevel % 2 )
+ {
+ // switch right and left ruby adjustment in rtl environment
+ if ( css::text::RubyAdjust_LEFT == m_nAdjustment )
+ m_nAdjustment = css::text::RubyAdjust_RIGHT;
+ else if ( css::text::RubyAdjust_RIGHT == m_nAdjustment )
+ m_nAdjustment = css::text::RubyAdjust_LEFT;
+
+ SetDirection( DIR_RIGHT2LEFT );
+ }
+ else
+ SetDirection( DIR_LEFT2RIGHT );
+}
+
+// In ruby portion there are different alignments for
+// the ruby text and the main text.
+// Left, right, centered and two possibilities of block adjustment
+// The block adjustment is realized by spacing between the characters,
+// either with a half space or no space in front of the first letter and
+// a half space at the end of the last letter.
+// Notice: the smaller line will be manipulated, normally it's the ruby line,
+// but it could be the main text, too.
+// If there is a tabulator in smaller line, no adjustment is possible.
+void SwRubyPortion::Adjust_( SwTextFormatInfo &rInf )
+{
+ SwTwips nLineDiff = GetRoot().Width() - GetRoot().GetNext()->Width();
+ TextFrameIndex const nOldIdx = rInf.GetIdx();
+ if( !nLineDiff )
+ return;
+ SwLineLayout *pCurr;
+ if( nLineDiff < 0 )
+ { // The first line has to be adjusted.
+ if( GetTab1() )
+ return;
+ pCurr = &GetRoot();
+ nLineDiff = -nLineDiff;
+ }
+ else
+ { // The second line has to be adjusted.
+ if( GetTab2() )
+ return;
+ pCurr = GetRoot().GetNext();
+ rInf.SetIdx( nOldIdx + GetRoot().GetLen() );
+ }
+ sal_uInt16 nLeft = 0; // the space in front of the first letter
+ sal_uInt16 nRight = 0; // the space at the end of the last letter
+ TextFrameIndex nSub(0);
+ switch ( m_nAdjustment )
+ {
+ case css::text::RubyAdjust_CENTER: nRight = o3tl::narrowing<sal_uInt16>(nLineDiff / 2);
+ [[fallthrough]];
+ case css::text::RubyAdjust_RIGHT: nLeft = o3tl::narrowing<sal_uInt16>(nLineDiff - nRight); break;
+ case css::text::RubyAdjust_BLOCK: nSub = TextFrameIndex(1);
+ [[fallthrough]];
+ case css::text::RubyAdjust_INDENT_BLOCK:
+ {
+ TextFrameIndex nCharCnt(0);
+ SwLinePortion *pPor;
+ for( pPor = pCurr->GetFirstPortion(); pPor; pPor = pPor->GetNextPortion() )
+ {
+ if( pPor->InTextGrp() )
+ static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nCharCnt );
+ rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
+ }
+ if( nCharCnt > nSub )
+ {
+ SwTwips nCalc = nLineDiff / sal_Int32(nCharCnt - nSub);
+ short nTmp;
+ if( nCalc < SHRT_MAX )
+ nTmp = -short(nCalc);
+ else
+ nTmp = SHRT_MIN;
+
+ pCurr->CreateSpaceAdd( SPACING_PRECISION_FACTOR * nTmp );
+ nLineDiff -= nCalc * (sal_Int32(nCharCnt) - 1);
+ }
+ if( nLineDiff > 1 )
+ {
+ nRight = o3tl::narrowing<sal_uInt16>(nLineDiff / 2);
+ nLeft = o3tl::narrowing<sal_uInt16>(nLineDiff - nRight);
+ }
+ break;
+ }
+ default: OSL_FAIL( "New ruby adjustment" );
+ }
+ if( nLeft || nRight )
+ {
+ if( !pCurr->GetNextPortion() )
+ pCurr->SetNextPortion(SwTextPortion::CopyLinePortion(*pCurr));
+ if( nLeft )
+ {
+ SwMarginPortion *pMarg = new SwMarginPortion;
+ pMarg->AddPrtWidth( nLeft );
+ pMarg->SetNextPortion( pCurr->GetNextPortion() );
+ pCurr->SetNextPortion( pMarg );
+ }
+ if( nRight )
+ {
+ SwMarginPortion *pMarg = new SwMarginPortion;
+ pMarg->AddPrtWidth( nRight );
+ pCurr->FindLastPortion()->Append( pMarg );
+ }
+ }
+
+ pCurr->Width( Width() );
+ rInf.SetIdx( nOldIdx );
+}
+
+// has to change the nRubyOffset, if there's a fieldportion
+// in the phonetic line.
+// The nRubyOffset is the position in the rubystring, where the
+// next SwRubyPortion has start the displaying of the phonetics.
+void SwRubyPortion::CalcRubyOffset()
+{
+ const SwLineLayout *pCurr = &GetRoot();
+ if( !OnTop() )
+ {
+ pCurr = pCurr->GetNext();
+ if( !pCurr )
+ return;
+ }
+ const SwLinePortion *pPor = pCurr->GetFirstPortion();
+ const SwFieldPortion *pField = nullptr;
+ while( pPor )
+ {
+ if( pPor->InFieldGrp() )
+ pField = static_cast<const SwFieldPortion*>(pPor);
+ pPor = pPor->GetNextPortion();
+ }
+ if( pField )
+ {
+ if( pField->HasFollow() )
+ m_nRubyOffset = pField->GetNextOffset();
+ else
+ m_nRubyOffset = TextFrameIndex(COMPLETE_STRING);
+ }
+}
+
+// A little helper function for GetMultiCreator(..)
+// It extracts the 2-line-format from a 2-line-attribute or a character style.
+// The rValue is set to true, if the 2-line-attribute's value is set and
+// no 2-line-format reference is passed. If there is a 2-line-format reference,
+// then the rValue is set only, if the 2-line-attribute's value is set _and_
+// the 2-line-formats has the same brackets.
+static bool lcl_Check2Lines(const SfxPoolItem *const pItem,
+ const SvxTwoLinesItem* &rpRef, bool &rValue)
+{
+ if( pItem )
+ {
+ rValue = static_cast<const SvxTwoLinesItem*>(pItem)->GetValue();
+ if( !rpRef )
+ rpRef = static_cast<const SvxTwoLinesItem*>(pItem);
+ else if( static_cast<const SvxTwoLinesItem*>(pItem)->GetEndBracket() !=
+ rpRef->GetEndBracket() ||
+ static_cast<const SvxTwoLinesItem*>(pItem)->GetStartBracket() !=
+ rpRef->GetStartBracket() )
+ rValue = false;
+ return true;
+ }
+ return false;
+}
+
+static bool lcl_Has2Lines(const SwTextAttr& rAttr,
+ const SvxTwoLinesItem* &rpRef, bool &rValue)
+{
+ const SfxPoolItem* pItem = CharFormat::GetItem(rAttr, RES_CHRATR_TWO_LINES);
+ return lcl_Check2Lines(pItem, rpRef, rValue);
+}
+
+// is a little help function for GetMultiCreator(..)
+// It extracts the charrotation from a charrotate-attribute or a character style.
+// The rValue is set to true, if the charrotate-attribute's value is set and
+// no charrotate-format reference is passed.
+// If there is a charrotate-format reference, then the rValue is set only,
+// if the charrotate-attribute's value is set _and_ identical
+// to the charrotate-format's value.
+static bool lcl_CheckRotation(const SfxPoolItem *const pItem,
+ const SvxCharRotateItem* &rpRef, bool &rValue)
+{
+ if ( pItem )
+ {
+ rValue = static_cast<const SvxCharRotateItem*>(pItem)->GetValue() != 0_deg10;
+ if( !rpRef )
+ rpRef = static_cast<const SvxCharRotateItem*>(pItem);
+ else if( static_cast<const SvxCharRotateItem*>(pItem)->GetValue() !=
+ rpRef->GetValue() )
+ rValue = false;
+ return true;
+ }
+
+ return false;
+}
+
+static bool lcl_HasRotation(const SwTextAttr& rAttr,
+ const SvxCharRotateItem* &rpRef, bool &rValue)
+{
+ const SfxPoolItem* pItem = CharFormat::GetItem( rAttr, RES_CHRATR_ROTATE );
+ return lcl_CheckRotation(pItem, rpRef, rValue);
+}
+
+namespace sw {
+ namespace {
+
+ // need to use a very special attribute iterator here that returns
+ // both the hints and the nodes, so that GetMultiCreator() can handle
+ // items in the nodes' set properly
+ class MergedAttrIterMulti
+ : public MergedAttrIterBase
+ {
+ private:
+ bool m_First = true;
+ public:
+ MergedAttrIterMulti(SwTextFrame const& rFrame) : MergedAttrIterBase(rFrame) {}
+ SwTextAttr const* NextAttr(SwTextNode const*& rpNode);
+ // can't have operator= because m_pMerged/m_pNode const
+ void Assign(MergedAttrIterMulti const& rOther)
+ {
+ assert(m_pMerged == rOther.m_pMerged);
+ assert(m_pNode == rOther.m_pNode);
+ m_CurrentExtent = rOther.m_CurrentExtent;
+ m_CurrentHint = rOther.m_CurrentHint;
+ m_First = rOther.m_First;
+ }
+ };
+
+ }
+
+ SwTextAttr const* MergedAttrIterMulti::NextAttr(SwTextNode const*& rpNode)
+ {
+ if (m_First)
+ {
+ m_First = false;
+ rpNode = m_pMerged
+ ? !m_pMerged->extents.empty()
+ ? m_pMerged->extents[0].pNode
+ : m_pMerged->pFirstNode
+ : m_pNode;
+ return nullptr;
+ }
+ if (m_pMerged)
+ {
+ const auto nExtentsSize = m_pMerged->extents.size();
+ while (m_CurrentExtent < nExtentsSize)
+ {
+ sw::Extent const& rExtent(m_pMerged->extents[m_CurrentExtent]);
+ if (SwpHints const*const pHints = rExtent.pNode->GetpSwpHints())
+ {
+ auto nHintsCount = pHints->Count();
+ while (m_CurrentHint < nHintsCount)
+ {
+ SwTextAttr const*const pHint(pHints->Get(m_CurrentHint));
+ if (rExtent.nEnd < pHint->GetStart())
+ {
+ break;
+ }
+ ++m_CurrentHint;
+ if (rExtent.nStart <= pHint->GetStart())
+ {
+ rpNode = rExtent.pNode;
+ return pHint;
+ }
+ }
+ }
+ ++m_CurrentExtent;
+ if (m_CurrentExtent < nExtentsSize &&
+ rExtent.pNode != m_pMerged->extents[m_CurrentExtent].pNode)
+ {
+ m_CurrentHint = 0; // reset
+ rpNode = m_pMerged->extents[m_CurrentExtent].pNode;
+ return nullptr;
+ }
+ }
+ return nullptr;
+ }
+ else
+ {
+ SwpHints const*const pHints(m_pNode->GetpSwpHints());
+ if (pHints)
+ {
+ if (m_CurrentHint < pHints->Count())
+ {
+ SwTextAttr const*const pHint(pHints->Get(m_CurrentHint));
+ ++m_CurrentHint;
+ rpNode = m_pNode;
+ return pHint;
+ }
+ }
+ return nullptr;
+ }
+ }
+}
+
+// If we (e.g. the position rPos) are inside a two-line-attribute or
+// a ruby-attribute, the attribute will be returned in a SwMultiCreator-struct,
+// otherwise the function returns zero.
+// The rPos parameter is set to the end of the multiportion,
+// normally this is the end of the attribute,
+// but sometimes it is the start of another attribute, which finished or
+// interrupts the first attribute.
+// E.g. a ruby portion interrupts a 2-line-attribute, a 2-line-attribute
+// with different brackets interrupts another 2-line-attribute.
+std::optional<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex &rPos,
+ SwMultiPortion const * pMulti ) const
+{
+ SwScriptInfo& rSI = const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo();
+
+ // get the last embedding level
+ sal_uInt8 nCurrLevel;
+ if ( pMulti )
+ {
+ OSL_ENSURE( pMulti->IsBidi(), "Nested MultiPortion is not BidiPortion" );
+ // level associated with bidi-portion;
+ nCurrLevel = static_cast<SwBidiPortion const *>(pMulti)->GetLevel();
+ }
+ else
+ // no nested bidi portion required
+ nCurrLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
+
+ // check if there is a field at rPos:
+ sal_uInt8 nNextLevel = nCurrLevel;
+ bool bFieldBidi = false;
+
+ if (rPos < TextFrameIndex(GetText().getLength()) && CH_TXTATR_BREAKWORD == GetChar(rPos))
+ {
+ bFieldBidi = true;
+ }
+ else
+ nNextLevel = rSI.DirType( rPos );
+
+ if (TextFrameIndex(GetText().getLength()) != rPos && nNextLevel > nCurrLevel)
+ {
+ rPos = bFieldBidi ? rPos + TextFrameIndex(1) : rSI.NextDirChg(rPos, &nCurrLevel);
+ if (TextFrameIndex(COMPLETE_STRING) == rPos)
+ return {};
+ SwMultiCreator aRet;
+ aRet.pItem = nullptr;
+ aRet.pAttr = nullptr;
+ aRet.nStartOfAttr = TextFrameIndex(-1);
+ aRet.nId = SwMultiCreatorId::Bidi;
+ aRet.nLevel = nCurrLevel + 1;
+ return aRet;
+ }
+
+ // a bidi portion can only contain other bidi portions
+ if ( pMulti )
+ return {};
+
+ // need the node that contains input rPos
+ std::pair<SwTextNode const*, sal_Int32> startPos(m_pFrame->MapViewToModel(rPos));
+ const SvxCharRotateItem* pActiveRotateItem(nullptr);
+ const SvxCharRotateItem* pNodeRotateItem(nullptr);
+ const SvxTwoLinesItem* pActiveTwoLinesItem(nullptr);
+ const SvxTwoLinesItem* pNodeTwoLinesItem(nullptr);
+ SwTextAttr const* pActiveTwoLinesHint(nullptr);
+ SwTextAttr const* pActiveRotateHint(nullptr);
+ const SwTextAttr *pRuby = nullptr;
+ sw::MergedAttrIterMulti iterAtStartOfNode(*m_pFrame);
+ bool bTwo = false;
+ bool bRot = false;
+
+ for (sw::MergedAttrIterMulti iter = *m_pFrame; ; )
+ {
+ SwTextNode const* pNode(nullptr);
+ SwTextAttr const*const pAttr = iter.NextAttr(pNode);
+ if (!pNode)
+ {
+ break;
+ }
+ if (pAttr)
+ {
+ assert(pNode->GetIndex() <= startPos.first->GetIndex()); // should break earlier
+ if (startPos.first->GetIndex() <= pNode->GetIndex())
+ {
+ if (startPos.first->GetIndex() != pNode->GetIndex()
+ || startPos.second < pAttr->GetStart())
+ {
+ break;
+ }
+ if (startPos.second < pAttr->GetAnyEnd())
+ {
+ // sw_redlinehide: ruby *always* splits
+ if (RES_TXTATR_CJK_RUBY == pAttr->Which())
+ pRuby = pAttr;
+ else
+ {
+ const SvxCharRotateItem* pRoTmp = nullptr;
+ if (lcl_HasRotation( *pAttr, pRoTmp, bRot ))
+ {
+ pActiveRotateHint = bRot ? pAttr : nullptr;
+ pActiveRotateItem = pRoTmp;
+ }
+ const SvxTwoLinesItem* p2Tmp = nullptr;
+ if (lcl_Has2Lines( *pAttr, p2Tmp, bTwo ))
+ {
+ pActiveTwoLinesHint = bTwo ? pAttr : nullptr;
+ pActiveTwoLinesItem = p2Tmp;
+ }
+ }
+ }
+ }
+ }
+ else if (pNode) // !pAttr && pNode means the node changed
+ {
+ if (startPos.first->GetIndex() < pNode->GetIndex())
+ {
+ break; // only one node initially
+ }
+ if (startPos.first->GetIndex() == pNode->GetIndex())
+ {
+ iterAtStartOfNode.Assign(iter);
+ if (SfxItemState::SET == pNode->GetSwAttrSet().GetItemState(
+ RES_CHRATR_ROTATE, true, &pNodeRotateItem) &&
+ pNodeRotateItem->GetValue())
+ {
+ pActiveRotateItem = pNodeRotateItem;
+ }
+ else
+ {
+ pNodeRotateItem = nullptr;
+ }
+ if (SfxItemState::SET == startPos.first->GetSwAttrSet().GetItemState(
+ RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem) &&
+ pNodeTwoLinesItem->GetValue())
+ {
+ pActiveTwoLinesItem = pNodeTwoLinesItem;
+ }
+ else
+ {
+ pNodeTwoLinesItem = nullptr;
+ }
+ }
+ }
+ }
+ if (!pRuby && !pActiveTwoLinesItem && !pActiveRotateItem)
+ return {};
+
+ if( pRuby )
+ { // The winner is ... a ruby attribute and so
+ // the end of the multiportion is the end of the ruby attribute.
+ rPos = m_pFrame->MapModelToView(startPos.first, *pRuby->End());
+ SwMultiCreator aRet;
+ aRet.pItem = nullptr;
+ aRet.pAttr = pRuby;
+ aRet.nStartOfAttr = m_pFrame->MapModelToView(startPos.first, aRet.pAttr->GetStart());
+ aRet.nId = SwMultiCreatorId::Ruby;
+ aRet.nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
+ return aRet;
+ }
+ if (pActiveTwoLinesHint ||
+ (pNodeTwoLinesItem && pNodeTwoLinesItem == pActiveTwoLinesItem &&
+ rPos < TextFrameIndex(GetText().getLength())))
+ { // The winner is a 2-line-attribute,
+ // the end of the multiportion depends on the following attributes...
+ SwMultiCreator aRet;
+
+ // We note the endpositions of the 2-line attributes in aEnd as stack
+ std::deque<TextFrameIndex> aEnd;
+
+ // The bOn flag signs the state of the last 2-line attribute in the
+ // aEnd-stack, it is compatible with the winner-attribute or
+ // it interrupts the other attribute.
+ bool bOn = true;
+
+ if (pActiveTwoLinesHint)
+ {
+ aRet.pItem = nullptr;
+ aRet.pAttr = pActiveTwoLinesHint;
+ aRet.nStartOfAttr = m_pFrame->MapModelToView(startPos.first, aRet.pAttr->GetStart());
+ if (pNodeTwoLinesItem)
+ {
+ aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
+ bOn = pNodeTwoLinesItem->GetEndBracket() ==
+ pActiveTwoLinesItem->GetEndBracket() &&
+ pNodeTwoLinesItem->GetStartBracket() ==
+ pActiveTwoLinesItem->GetStartBracket();
+ }
+ else
+ {
+ aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *aRet.pAttr->End()));
+ }
+ }
+ else
+ {
+ aRet.pItem = pNodeTwoLinesItem;
+ aRet.pAttr = nullptr;
+ aRet.nStartOfAttr = TextFrameIndex(-1);
+ aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
+ }
+ aRet.nId = SwMultiCreatorId::Double;
+ aRet.nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
+
+ // pActiveTwoLinesHint is the last 2-line-attribute, which contains
+ // the actual position.
+
+ // At this moment we know that at position rPos the "winner"-attribute
+ // causes a 2-line-portion. The end of the attribute is the end of the
+ // portion, if there's no interrupting attribute.
+ // There are two kinds of interrupters:
+ // - ruby attributes stops the 2-line-attribute, the end of the
+ // multiline is the start of the ruby attribute
+ // - 2-line-attributes with value "Off" or with different brackets,
+ // these attributes may interrupt the winner, but they could be
+ // neutralized by another 2-line-attribute starting at the same
+ // position with the same brackets as the winner-attribute.
+
+ // In the following loop rPos is the critical position and it will be
+ // evaluated, if at rPos starts an interrupting or a maintaining
+ // continuity attribute.
+
+ // iterAtStartOfNode is positioned to the first hint of the node
+ // (if any); the node item itself has already been handled above
+ for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
+ {
+ SwTextNode const* pNode(nullptr);
+ SwTextAttr const*const pTmp = iter.NextAttr(pNode);
+ if (!pNode)
+ {
+ break;
+ }
+ assert(startPos.first->GetIndex() <= pNode->GetIndex());
+ TextFrameIndex nTmpStart;
+ TextFrameIndex nTmpEnd;
+ if (pTmp)
+ {
+ nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
+ if (nTmpEnd <= rPos)
+ continue;
+ nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
+ }
+ else
+ {
+ pNodeTwoLinesItem = pNode->GetSwAttrSet().GetItemIfSet(
+ RES_CHRATR_TWO_LINES);
+ nTmpStart = m_pFrame->MapModelToView(pNode, 0);
+ nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
+ assert(rPos <= nTmpEnd); // next node must not have smaller index
+ }
+
+ if (rPos < nTmpStart)
+ {
+ // If bOn is false and the next attribute starts later than rPos
+ // the winner attribute is interrupted at rPos.
+ // If the start of the next attribute is behind the end of
+ // the last attribute on the aEnd-stack, this is the endposition
+ // on the stack is the end of the 2-line portion.
+ if (!bOn || aEnd.back() < nTmpStart)
+ break;
+ // At this moment, bOn is true and the next attribute starts
+ // behind rPos, so we could move rPos to the next startpoint
+ rPos = nTmpStart;
+ // We clean up the aEnd-stack, endpositions equal to rPos are
+ // superfluous.
+ while( !aEnd.empty() && aEnd.back() <= rPos )
+ {
+ bOn = !bOn;
+ aEnd.pop_back();
+ }
+ // If the endstack is empty, we simulate an attribute with
+ // state true and endposition rPos
+ if( aEnd.empty() )
+ {
+ aEnd.push_front( rPos );
+ bOn = true;
+ }
+ }
+ // A ruby attribute stops the 2-line immediately
+ if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
+ return aRet;
+ if (pTmp ? lcl_Has2Lines(*pTmp, pActiveTwoLinesItem, bTwo)
+ : lcl_Check2Lines(pNodeTwoLinesItem, pActiveTwoLinesItem, bTwo))
+ { // We have an interesting attribute...
+ if( bTwo == bOn )
+ { // .. with the same state, so the last attribute could
+ // be continued.
+ if (aEnd.back() < nTmpEnd)
+ aEnd.back() = nTmpEnd;
+ }
+ else
+ { // .. with a different state.
+ bOn = bTwo;
+ // If this is smaller than the last on the stack, we put
+ // it on the stack. If it has the same endposition, the last
+ // could be removed.
+ if (nTmpEnd < aEnd.back())
+ aEnd.push_back( nTmpEnd );
+ else if( aEnd.size() > 1 )
+ aEnd.pop_back();
+ else
+ aEnd.back() = nTmpEnd;
+ }
+ }
+ }
+ if( bOn && !aEnd.empty() )
+ rPos = aEnd.back();
+ return aRet;
+ }
+ if (pActiveRotateHint ||
+ (pNodeRotateItem && pNodeRotateItem == pActiveRotateItem &&
+ rPos < TextFrameIndex(GetText().getLength())))
+ { // The winner is a rotate-attribute,
+ // the end of the multiportion depends on the following attributes...
+ SwMultiCreator aRet;
+ aRet.nId = SwMultiCreatorId::Rotate;
+
+ // We note the endpositions of the 2-line attributes in aEnd as stack
+ std::deque<TextFrameIndex> aEnd;
+
+ // The bOn flag signs the state of the last 2-line attribute in the
+ // aEnd-stack, which could interrupts the winning rotation attribute.
+ bool bOn = pNodeTwoLinesItem != nullptr;
+ aEnd.push_front(TextFrameIndex(GetText().getLength()));
+
+ // first, search for the start position of the next TWOLINE portion
+ // because the ROTATE portion must end there at the latest
+ TextFrameIndex n2Start = rPos;
+ for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
+ {
+ SwTextNode const* pNode(nullptr);
+ SwTextAttr const*const pTmp = iter.NextAttr(pNode);
+ if (!pNode)
+ {
+ break;
+ }
+ assert(startPos.first->GetIndex() <= pNode->GetIndex());
+ TextFrameIndex nTmpStart;
+ TextFrameIndex nTmpEnd;
+ if (pTmp)
+ {
+ nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
+ if (nTmpEnd <= n2Start)
+ continue;
+ nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
+ }
+ else
+ {
+ pNodeTwoLinesItem = pNode->GetSwAttrSet().GetItemIfSet(
+ RES_CHRATR_TWO_LINES);
+ nTmpStart = m_pFrame->MapModelToView(pNode, 0);
+ nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
+ assert(n2Start <= nTmpEnd); // next node must not have smaller index
+ }
+
+ if (n2Start < nTmpStart)
+ {
+ if (bOn || aEnd.back() < nTmpStart)
+ break;
+ n2Start = nTmpStart;
+ while( !aEnd.empty() && aEnd.back() <= n2Start )
+ {
+ bOn = !bOn;
+ aEnd.pop_back();
+ }
+ if( aEnd.empty() )
+ {
+ aEnd.push_front( n2Start );
+ bOn = false;
+ }
+ }
+ // A ruby attribute stops immediately
+ if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
+ {
+ bOn = true;
+ break;
+ }
+ const SvxTwoLinesItem* p2Lines = nullptr;
+ if (pTmp ? lcl_Has2Lines(*pTmp, p2Lines, bTwo)
+ : lcl_Check2Lines(pNodeTwoLinesItem, p2Lines, bTwo))
+ {
+ if( bTwo == bOn )
+ {
+ if (aEnd.back() < nTmpEnd)
+ aEnd.back() = nTmpEnd;
+ }
+ else
+ {
+ bOn = bTwo;
+ if (nTmpEnd < aEnd.back())
+ aEnd.push_back( nTmpEnd );
+ else if( aEnd.size() > 1 )
+ aEnd.pop_back();
+ else
+ aEnd.back() = nTmpEnd;
+ }
+ }
+ }
+ if( !bOn && !aEnd.empty() )
+ n2Start = aEnd.back();
+
+ aEnd.clear();
+
+ // now, search for the end of the ROTATE portion, similar to above
+ bOn = true;
+ if (pActiveRotateHint)
+ {
+ aRet.pItem = nullptr;
+ aRet.pAttr = pActiveRotateHint;
+ aRet.nStartOfAttr = m_pFrame->MapModelToView(startPos.first, aRet.pAttr->GetStart());
+ if (pNodeRotateItem)
+ {
+ aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
+ bOn = pNodeRotateItem->GetValue() ==
+ pActiveRotateItem->GetValue();
+ }
+ else
+ {
+ aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *aRet.pAttr->End()));
+ }
+ }
+ else
+ {
+ aRet.pItem = pNodeRotateItem;
+ aRet.pAttr = nullptr;
+ aRet.nStartOfAttr = TextFrameIndex(-1);
+ aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
+ }
+ for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
+ {
+ SwTextNode const* pNode(nullptr);
+ SwTextAttr const*const pTmp = iter.NextAttr(pNode);
+ if (!pNode)
+ {
+ break;
+ }
+ assert(startPos.first->GetIndex() <= pNode->GetIndex());
+ TextFrameIndex nTmpStart;
+ TextFrameIndex nTmpEnd;
+ if (pTmp)
+ {
+ nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
+ if (nTmpEnd <= rPos)
+ continue;
+ nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
+ }
+ else
+ {
+ pNodeRotateItem = pNode->GetSwAttrSet().GetItemIfSet(
+ RES_CHRATR_ROTATE);
+ nTmpStart = m_pFrame->MapModelToView(pNode, 0);
+ nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
+ assert(rPos <= nTmpEnd); // next node must not have smaller index
+ }
+
+ if (rPos < nTmpStart)
+ {
+ if (!bOn || aEnd.back() < nTmpStart)
+ break;
+ rPos = nTmpStart;
+ while( !aEnd.empty() && aEnd.back() <= rPos )
+ {
+ bOn = !bOn;
+ aEnd.pop_back();
+ }
+ if( aEnd.empty() )
+ {
+ aEnd.push_front( rPos );
+ bOn = true;
+ }
+ }
+ if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
+ {
+ bOn = false;
+ break;
+ }
+ // TODO why does this use bTwo, not bRot ???
+ if (pTmp ? lcl_HasRotation(*pTmp, pActiveRotateItem, bTwo)
+ : lcl_CheckRotation(pNodeRotateItem, pActiveRotateItem, bTwo))
+ {
+ if( bTwo == bOn )
+ {
+ if (aEnd.back() < nTmpEnd)
+ aEnd.back() = nTmpEnd;
+ }
+ else
+ {
+ bOn = bTwo;
+ if (nTmpEnd < aEnd.back())
+ aEnd.push_back( nTmpEnd );
+ else if( aEnd.size() > 1 )
+ aEnd.pop_back();
+ else
+ aEnd.back() = nTmpEnd;
+ }
+ }
+ }
+ if( bOn && !aEnd.empty() )
+ rPos = aEnd.back();
+ if( rPos > n2Start )
+ rPos = n2Start;
+ return aRet;
+ }
+ return {};
+}
+
+namespace {
+
+// A little helper class to manage the spaceadd-arrays of the text adjustment
+// during a PaintMultiPortion.
+// The constructor prepares the array for the first line of multiportion,
+// the SecondLine-function restores the values for the first line and prepares
+// the second line.
+// The destructor restores the values of the last manipulation.
+class SwSpaceManipulator
+{
+ SwTextPaintInfo& m_rInfo;
+ SwMultiPortion& m_rMulti;
+ std::vector<tools::Long>* m_pOldSpaceAdd;
+ sal_uInt16 m_nOldSpaceIndex;
+ tools::Long m_nSpaceAdd;
+ bool m_bSpaceChg;
+ sal_uInt8 m_nOldDir;
+
+public:
+ SwSpaceManipulator( SwTextPaintInfo& rInf, SwMultiPortion& rMult );
+ ~SwSpaceManipulator();
+ void SecondLine();
+ tools::Long GetSpaceAdd() const { return m_nSpaceAdd; }
+};
+
+}
+
+SwSpaceManipulator::SwSpaceManipulator(SwTextPaintInfo& rInf, SwMultiPortion& rMult)
+ : m_rInfo(rInf)
+ , m_rMulti(rMult)
+ , m_nSpaceAdd(0)
+{
+ m_pOldSpaceAdd = m_rInfo.GetpSpaceAdd();
+ m_nOldSpaceIndex = m_rInfo.GetSpaceIdx();
+ m_nOldDir = m_rInfo.GetDirection();
+ m_rInfo.SetDirection(m_rMulti.GetDirection());
+ m_bSpaceChg = false;
+
+ if (m_rMulti.IsDouble())
+ {
+ m_nSpaceAdd = (m_pOldSpaceAdd && !m_rMulti.HasTabulator()) ? m_rInfo.GetSpaceAdd() : 0;
+ if (m_rMulti.GetRoot().IsSpaceAdd())
+ {
+ m_rInfo.SetpSpaceAdd(m_rMulti.GetRoot().GetpLLSpaceAdd());
+ m_rInfo.ResetSpaceIdx();
+ m_bSpaceChg = m_rMulti.ChgSpaceAdd(&m_rMulti.GetRoot(), m_nSpaceAdd);
+ }
+ else if (m_rMulti.HasTabulator())
+ m_rInfo.SetpSpaceAdd(nullptr);
+ }
+ else if (!m_rMulti.IsBidi())
+ {
+ m_rInfo.SetpSpaceAdd(m_rMulti.GetRoot().GetpLLSpaceAdd());
+ m_rInfo.ResetSpaceIdx();
+ }
+}
+
+void SwSpaceManipulator::SecondLine()
+{
+ if (m_bSpaceChg)
+ {
+ m_rInfo.RemoveFirstSpaceAdd();
+ m_bSpaceChg = false;
+ }
+ SwLineLayout* pLay = m_rMulti.GetRoot().GetNext();
+ if( pLay->IsSpaceAdd() )
+ {
+ m_rInfo.SetpSpaceAdd(pLay->GetpLLSpaceAdd());
+ m_rInfo.ResetSpaceIdx();
+ m_bSpaceChg = m_rMulti.ChgSpaceAdd(pLay, m_nSpaceAdd);
+ }
+ else
+ {
+ m_rInfo.SetpSpaceAdd((!m_rMulti.IsDouble() || m_rMulti.HasTabulator()) ? nullptr
+ : m_pOldSpaceAdd);
+ m_rInfo.SetSpaceIdx(m_nOldSpaceIndex);
+ }
+}
+
+SwSpaceManipulator::~SwSpaceManipulator()
+{
+ if (m_bSpaceChg)
+ {
+ m_rInfo.RemoveFirstSpaceAdd();
+ m_bSpaceChg = false;
+ }
+ m_rInfo.SetpSpaceAdd(m_pOldSpaceAdd);
+ m_rInfo.SetSpaceIdx(m_nOldSpaceIndex);
+ m_rInfo.SetDirection(m_nOldDir);
+}
+
+// Manages the paint for a SwMultiPortion.
+// External, for the calling function, it seems to be a normal Paint-function,
+// internal it is like a SwTextFrame::PaintSwFrame with multiple DrawTextLines
+void SwTextPainter::PaintMultiPortion( const SwRect &rPaint,
+ SwMultiPortion& rMulti, const SwMultiPortion* pEnvPor )
+{
+ SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
+ const bool bHasGrid = pGrid && GetInfo().SnapToGrid();
+ sal_uInt16 nRubyHeight = 0;
+ bool bRubyTop = true;
+
+ if ( bHasGrid && pGrid->IsSquaredMode() )
+ {
+ nRubyHeight = pGrid->GetRubyHeight();
+ bRubyTop = ! pGrid->GetRubyTextBelow();
+ }
+
+ // do not allow grid mode for first line in ruby portion
+ const bool bRubyInGrid = bHasGrid && rMulti.IsRuby();
+
+ const sal_uInt16 nOldHeight = rMulti.Height();
+ const bool bOldGridModeAllowed = GetInfo().SnapToGrid();
+
+ if ( bRubyInGrid )
+ {
+ GetInfo().SetSnapToGrid( ! bRubyTop );
+ if (pGrid->IsSquaredMode())
+ rMulti.Height( m_pCurr->Height() );
+ }
+
+ SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() );
+ bool bEnvDir = false;
+ bool bThisDir = false;
+ bool bFrameDir = false;
+ if ( rMulti.IsBidi() )
+ {
+ // these values are needed for the calculation of the x coordinate
+ // and the layout mode
+ OSL_ENSURE( ! pEnvPor || pEnvPor->IsBidi(),
+ "Oh no, I expected a BidiPortion" );
+ bFrameDir = GetInfo().GetTextFrame()->IsRightToLeft();
+ bEnvDir = pEnvPor ? ((static_cast<const SwBidiPortion*>(pEnvPor)->GetLevel() % 2) != 0) : bFrameDir;
+ bThisDir = (static_cast<SwBidiPortion&>(rMulti).GetLevel() % 2) != 0;
+ }
+
+#if OSL_DEBUG_LEVEL > 1
+ // only paint first level bidi portions
+ if( rMulti.Width() > 1 && ! pEnvPor )
+ GetInfo().DrawViewOpt( rMulti, PortionType::Field );
+#endif
+
+ if ( bRubyInGrid && pGrid->IsSquaredMode() )
+ rMulti.Height( nOldHeight );
+
+ // do we have to repaint a post it portion?
+ if( GetInfo().OnWin() && rMulti.GetNextPortion() &&
+ ! rMulti.GetNextPortion()->Width() )
+ rMulti.GetNextPortion()->PrePaint( GetInfo(), &rMulti );
+
+ // old values must be saved and restored at the end
+ TextFrameIndex const nOldLen = GetInfo().GetLen();
+ const SwTwips nOldX = GetInfo().X();
+ const SwTwips nOldY = GetInfo().Y();
+ TextFrameIndex const nOldIdx = GetInfo().GetIdx();
+
+ SwSpaceManipulator aManip( GetInfo(), rMulti );
+
+ std::unique_ptr<SwFontSave> pFontSave;
+ std::unique_ptr<SwFont> pTmpFnt;
+
+ if( rMulti.IsDouble() )
+ {
+ pTmpFnt.reset(new SwFont( *GetInfo().GetFont() ));
+ if( rMulti.IsDouble() )
+ {
+ SetPropFont( 50 );
+ pTmpFnt->SetProportion( GetPropFont() );
+ }
+ pFontSave.reset(new SwFontSave( GetInfo(), pTmpFnt.get(), this ));
+ }
+ else
+ {
+ pFontSave = nullptr;
+ pTmpFnt = nullptr;
+ }
+
+ if( rMulti.HasBrackets() )
+ {
+ TextFrameIndex const nTmpOldIdx = GetInfo().GetIdx();
+ GetInfo().SetIdx(static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart);
+ SeekAndChg( GetInfo() );
+ static_cast<SwDoubleLinePortion&>(rMulti).PaintBracket( GetInfo(), 0, true );
+ GetInfo().SetIdx( nTmpOldIdx );
+ }
+
+ const SwTwips nTmpX = GetInfo().X();
+
+ SwLineLayout* pLay = &rMulti.GetRoot();// the first line of the multiportion
+ SwLinePortion* pPor = pLay->GetFirstPortion();//first portion of these line
+ SwTwips nOfst = 0;
+
+ // GetInfo().Y() is the baseline from the surrounding line. We must switch
+ // this temporary to the baseline of the inner lines of the multiportion.
+ if( rMulti.HasRotation() )
+ {
+ if( rMulti.IsRevers() )
+ {
+ GetInfo().Y( nOldY - rMulti.GetAscent() );
+ nOfst = nTmpX + rMulti.Width();
+ }
+ else
+ {
+ GetInfo().Y( nOldY - rMulti.GetAscent() + rMulti.Height() );
+ nOfst = nTmpX;
+ }
+ }
+ else if ( rMulti.IsBidi() )
+ {
+ // does the current bidi portion has the same direction
+ // as its environment?
+ if ( bEnvDir != bThisDir )
+ {
+ // different directions, we have to adjust the x coordinate
+ SwTwips nMultiWidth = rMulti.Width() +
+ rMulti.CalcSpacing( GetInfo().GetSpaceAdd(), GetInfo() );
+
+ if ( bFrameDir == bThisDir )
+ GetInfo().X( GetInfo().X() - nMultiWidth );
+ else
+ GetInfo().X( GetInfo().X() + nMultiWidth );
+ }
+
+ nOfst = nOldY - rMulti.GetAscent();
+
+ // set layout mode
+ aLayoutModeModifier.Modify( bThisDir );
+ }
+ else
+ nOfst = nOldY - rMulti.GetAscent();
+
+ bool bRest = pLay->IsRest();
+ bool bFirst = true;
+
+ OSL_ENSURE( nullptr == GetInfo().GetUnderFnt() || rMulti.IsBidi(),
+ " Only BiDi portions are allowed to use the common underlining font" );
+
+ if ( rMulti.IsRuby() )
+ GetInfo().SetRuby( rMulti.OnTop() );
+
+ do
+ {
+ if ( bHasGrid && pGrid->IsSquaredMode() )
+ {
+ if( rMulti.HasRotation() )
+ {
+ const sal_uInt16 nAdjustment = ( pLay->Height() - pPor->Height() ) / 2 +
+ pPor->GetAscent();
+ if( rMulti.IsRevers() )
+ GetInfo().X( nOfst - nAdjustment );
+ else
+ GetInfo().X( nOfst + nAdjustment );
+ }
+ else
+ {
+ // special treatment for ruby portions in grid mode
+ SwTwips nAdjustment = 0;
+ if ( rMulti.IsRuby() )
+ {
+ if ( bRubyTop != ( pLay == &rMulti.GetRoot() ) )
+ // adjust base text
+ nAdjustment = ( m_pCurr->Height() - nRubyHeight - pPor->Height() ) / 2;
+ else if ( bRubyTop )
+ // adjust upper ruby text
+ nAdjustment = nRubyHeight - pPor->Height();
+ // else adjust lower ruby text
+ }
+
+ GetInfo().Y( nOfst + nAdjustment + pPor->GetAscent() );
+ }
+ }
+ else if( rMulti.HasRotation() )
+ {
+ if( rMulti.IsRevers() )
+ GetInfo().X( nOfst - AdjustBaseLine( *pLay, pPor, 0, 0, true ) );
+ else
+ GetInfo().X( nOfst + AdjustBaseLine( *pLay, pPor ) );
+ }
+ else if ( rMulti.IsRuby() && rMulti.OnRight() && GetInfo().IsRuby() )
+ {
+ SwTwips nLineDiff = std::max(( rMulti.GetRoot().Height() - pPor->Width() ) / 2, static_cast<SwTwips>(0) );
+ GetInfo().Y( nOfst + nLineDiff );
+ // Draw the ruby text on top of the preserved space.
+ GetInfo().X( GetInfo().X() - pPor->Height() );
+ }
+ else
+ GetInfo().Y( nOfst + AdjustBaseLine( *pLay, pPor ) );
+
+ bool bSeeked = true;
+ GetInfo().SetLen( pPor->GetLen() );
+
+ if( bRest && pPor->InFieldGrp() && !pPor->GetLen() )
+ {
+ if( static_cast<SwFieldPortion*>(pPor)->HasFont() )
+ bSeeked = false;
+ else
+ SeekAndChgBefore( GetInfo() );
+ }
+ else if( pPor->InTextGrp() || pPor->InFieldGrp() || pPor->InTabGrp() )
+ SeekAndChg( GetInfo() );
+ else if ( !bFirst && pPor->IsBreakPortion() && GetInfo().GetOpt().IsParagraph() )
+ {
+ if( GetRedln() )
+ SeekAndChg( GetInfo() );
+ else
+ SeekAndChgBefore( GetInfo() );
+ }
+ else
+ bSeeked = false;
+
+ SwLinePortion *pNext = pPor->GetNextPortion();
+ if(GetInfo().OnWin() && pNext && !pNext->Width() )
+ {
+ if ( !bSeeked )
+ SeekAndChg( GetInfo() );
+ pNext->PrePaint( GetInfo(), pPor );
+ }
+
+ CheckSpecialUnderline( pPor );
+ SwUnderlineFont* pUnderLineFnt = GetInfo().GetUnderFnt();
+ if ( pUnderLineFnt )
+ {
+ if ( rMulti.IsDouble() )
+ pUnderLineFnt->GetFont().SetProportion( 50 );
+ pUnderLineFnt->SetPos( GetInfo().GetPos() );
+ }
+
+ if ( rMulti.IsBidi() )
+ {
+ // we do not allow any rotation inside a bidi portion
+ SwFont* pTmpFont = GetInfo().GetFont();
+ pTmpFont->SetVertical( 0_deg10, GetInfo().GetTextFrame()->IsVertical() );
+ }
+
+ if( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() )
+ {
+ // but we do allow nested bidi portions
+ OSL_ENSURE( rMulti.IsBidi(), "Only nesting of bidi portions is allowed" );
+ PaintMultiPortion( rPaint, static_cast<SwMultiPortion&>(*pPor), &rMulti );
+ }
+ else
+ pPor->Paint( GetInfo() );
+
+ bFirst &= !pPor->GetLen();
+ if( pNext || !pPor->IsMarginPortion() )
+ pPor->Move( GetInfo() );
+
+ pPor = pNext;
+
+ // If there's no portion left, we go to the next line
+ if( !pPor && pLay->GetNext() )
+ {
+ pLay = pLay->GetNext();
+ pPor = pLay->GetFirstPortion();
+ bRest = pLay->IsRest();
+ aManip.SecondLine();
+
+ // delete underline font
+ delete GetInfo().GetUnderFnt();
+ GetInfo().SetUnderFnt( nullptr );
+
+ if( rMulti.HasRotation() )
+ {
+ if( rMulti.IsRevers() )
+ {
+ nOfst += pLay->Height();
+ GetInfo().Y( nOldY - rMulti.GetAscent() );
+ }
+ else
+ {
+ nOfst -= pLay->Height();
+ GetInfo().Y( nOldY - rMulti.GetAscent() + rMulti.Height() );
+ }
+ }
+ else if ( bHasGrid && rMulti.IsRuby() )
+ {
+ GetInfo().SetSnapToGrid( bRubyTop );
+ GetInfo().X( nTmpX );
+ if (pGrid->IsSquaredMode() )
+ {
+ if ( bRubyTop )
+ nOfst += nRubyHeight;
+ else
+ nOfst += m_pCurr->Height() - nRubyHeight;
+ }
+ else
+ {
+ nOfst += rMulti.GetRoot().Height();
+ }
+ }
+ else if ( rMulti.IsRuby() && rMulti.OnRight() )
+ {
+ GetInfo().SetDirection( DIR_TOP2BOTTOM );
+ GetInfo().SetRuby( true );
+ } else
+ {
+ GetInfo().X( nTmpX );
+ // We switch to the baseline of the next inner line
+ nOfst += rMulti.GetRoot().Height();
+ }
+ }
+ } while( pPor );
+
+ if ( bRubyInGrid )
+ GetInfo().SetSnapToGrid( bOldGridModeAllowed );
+
+ // delete underline font
+ if ( ! rMulti.IsBidi() )
+ {
+ delete GetInfo().GetUnderFnt();
+ GetInfo().SetUnderFnt( nullptr );
+ }
+
+ GetInfo().SetIdx( nOldIdx );
+ GetInfo().Y( nOldY );
+
+ if( rMulti.HasBrackets() )
+ {
+ TextFrameIndex const nTmpOldIdx = GetInfo().GetIdx();
+ GetInfo().SetIdx(static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart);
+ SeekAndChg( GetInfo() );
+ GetInfo().X( nOldX );
+ static_cast<SwDoubleLinePortion&>(rMulti).PaintBracket( GetInfo(),
+ aManip.GetSpaceAdd(), false );
+ GetInfo().SetIdx( nTmpOldIdx );
+ }
+ // Restore the saved values
+ GetInfo().X( nOldX );
+ GetInfo().SetLen( nOldLen );
+ pFontSave.reset();
+ pTmpFnt.reset();
+ SetPropFont( 0 );
+}
+
+static bool lcl_ExtractFieldFollow( SwLineLayout* pLine, SwLinePortion* &rpField )
+{
+ SwLinePortion* pLast = pLine;
+ rpField = pLine->GetNextPortion();
+ while( rpField && !rpField->InFieldGrp() )
+ {
+ pLast = rpField;
+ rpField = rpField->GetNextPortion();
+ }
+ bool bRet = rpField != nullptr;
+ if( bRet )
+ {
+ if( static_cast<SwFieldPortion*>(rpField)->IsFollow() )
+ {
+ rpField->Truncate();
+ pLast->SetNextPortion( nullptr );
+ }
+ else
+ rpField = nullptr;
+ }
+ pLine->Truncate();
+ return bRet;
+}
+
+// If a multi portion completely has to go to the
+// next line, this function is called to truncate
+// the rest of the remaining multi portion
+static void lcl_TruncateMultiPortion( SwMultiPortion& rMulti, SwTextFormatInfo& rInf,
+ TextFrameIndex const nStartIdx)
+{
+ rMulti.GetRoot().Truncate();
+ rMulti.GetRoot().SetLen(TextFrameIndex(0));
+ rMulti.GetRoot().Width(0);
+// rMulti.CalcSize( *this, aInf );
+ if ( rMulti.GetRoot().GetNext() )
+ {
+ rMulti.GetRoot().GetNext()->Truncate();
+ rMulti.GetRoot().GetNext()->SetLen(TextFrameIndex(0));
+ rMulti.GetRoot().GetNext()->Width( 0 );
+ }
+ rMulti.Width( 0 );
+ rMulti.SetLen(TextFrameIndex(0));
+ rInf.SetIdx( nStartIdx );
+}
+
+// Manages the formatting of a SwMultiPortion. External, for the calling
+// function, it seems to be a normal Format-function, internal it is like a
+// SwTextFrame::Format_ with multiple BuildPortions
+bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf,
+ SwMultiPortion& rMulti )
+{
+ SwTwips nMaxWidth = rInf.Width();
+ SwTwips nOldX = 0;
+
+ if( rMulti.HasBrackets() )
+ {
+ TextFrameIndex const nOldIdx = rInf.GetIdx();
+ rInf.SetIdx( static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart );
+ SeekAndChg( rInf );
+ nOldX = GetInfo().X();
+ static_cast<SwDoubleLinePortion&>(rMulti).FormatBrackets( rInf, nMaxWidth );
+ rInf.SetIdx( nOldIdx );
+ }
+
+ SeekAndChg( rInf );
+ std::unique_ptr<SwFontSave> xFontSave;
+ std::unique_ptr<SwFont> xTmpFont;
+ if( rMulti.IsDouble() )
+ {
+ xTmpFont.reset(new SwFont( *rInf.GetFont() ));
+ if( rMulti.IsDouble() )
+ {
+ SetPropFont( 50 );
+ xTmpFont->SetProportion( GetPropFont() );
+ }
+ xFontSave.reset(new SwFontSave(rInf, xTmpFont.get(), this));
+ }
+
+ SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() );
+ if ( rMulti.IsBidi() )
+ {
+ // set layout mode
+ aLayoutModeModifier.Modify( ! rInf.GetTextFrame()->IsRightToLeft() );
+ }
+
+ SwTwips nTmpX = 0;
+
+ if( rMulti.HasRotation() )
+ {
+ // For nMaxWidth we take the height of the body frame.
+ // #i25067#: If the current frame is inside a table, we restrict
+ // nMaxWidth to the current frame height, unless the frame size
+ // attribute is set to variable size:
+
+ // We set nTmpX (which is used for portion calculating) to the
+ // current Y value
+ const SwPageFrame* pPage = m_pFrame->FindPageFrame();
+ OSL_ENSURE( pPage, "No page in frame!");
+ const SwLayoutFrame* pUpperFrame = pPage;
+
+ if ( m_pFrame->IsInTab() )
+ {
+ pUpperFrame = m_pFrame->GetUpper();
+ while ( pUpperFrame && !pUpperFrame->IsCellFrame() )
+ pUpperFrame = pUpperFrame->GetUpper();
+ assert(pUpperFrame); //pFrame is in table but does not have an upper cell frame
+ if (!pUpperFrame)
+ return false;
+ const SwTableLine* pLine = static_cast<const SwRowFrame*>(pUpperFrame->GetUpper())->GetTabLine();
+ const SwFormatFrameSize& rFrameFormatSize = pLine->GetFrameFormat()->GetFrameSize();
+ if ( SwFrameSize::Variable == rFrameFormatSize.GetHeightSizeType() )
+ pUpperFrame = pPage;
+ }
+ if ( pUpperFrame == pPage && !m_pFrame->IsInFootnote() )
+ pUpperFrame = pPage->FindBodyCont();
+
+ nMaxWidth = pUpperFrame ?
+ ( rInf.GetTextFrame()->IsVertical() ?
+ pUpperFrame->getFramePrintArea().Width() :
+ pUpperFrame->getFramePrintArea().Height() ) :
+ USHRT_MAX;
+ }
+ else
+ nTmpX = rInf.X();
+
+ SwMultiPortion* pOldMulti = m_pMulti;
+
+ m_pMulti = &rMulti;
+ SwLineLayout *pOldCurr = m_pCurr;
+ TextFrameIndex const nOldStart = GetStart();
+ SwTwips nMinWidth = nTmpX + 1;
+ SwTwips nActWidth = nMaxWidth;
+ const TextFrameIndex nStartIdx = rInf.GetIdx();
+ TextFrameIndex nMultiLen = rMulti.GetLen();
+
+ SwLinePortion *pFirstRest;
+ SwLinePortion *pSecondRest;
+ if( rMulti.IsFormatted() )
+ {
+ if( !lcl_ExtractFieldFollow( &rMulti.GetRoot(), pFirstRest )
+ && rMulti.IsDouble() && rMulti.GetRoot().GetNext() )
+ lcl_ExtractFieldFollow( rMulti.GetRoot().GetNext(), pFirstRest );
+ if( !rMulti.IsDouble() && rMulti.GetRoot().GetNext() )
+ lcl_ExtractFieldFollow( rMulti.GetRoot().GetNext(), pSecondRest );
+ else
+ pSecondRest = nullptr;
+ }
+ else
+ {
+ pFirstRest = rMulti.GetRoot().GetNextPortion();
+ pSecondRest = rMulti.GetRoot().GetNext() ?
+ rMulti.GetRoot().GetNext()->GetNextPortion() : nullptr;
+ if( pFirstRest )
+ rMulti.GetRoot().SetNextPortion( nullptr );
+ if( pSecondRest )
+ rMulti.GetRoot().GetNext()->SetNextPortion( nullptr );
+ rMulti.SetFormatted();
+ nMultiLen = nMultiLen - rInf.GetIdx();
+ }
+
+ // save some values
+ const OUString* pOldText = &(rInf.GetText());
+ const SwTwips nOldPaintOfst = rInf.GetPaintOfst();
+ std::shared_ptr<const vcl::text::TextLayoutCache> const pOldCachedVclData(rInf.GetCachedVclData());
+ rInf.SetCachedVclData(nullptr);
+
+ OUString const aMultiStr( rInf.GetText().copy(0, sal_Int32(nMultiLen + rInf.GetIdx())) );
+ rInf.SetText( aMultiStr );
+ SwTextFormatInfo aInf( rInf, rMulti.GetRoot(), nActWidth );
+ // Do we allow break cuts? The FirstMulti-Flag is evaluated during
+ // line break determination.
+ bool bFirstMulti = rInf.GetIdx() != rInf.GetLineStart();
+
+ SwLinePortion *pNextFirst = nullptr;
+ SwLinePortion *pNextSecond = nullptr;
+ bool bRet = false;
+
+ SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
+ const bool bHasGrid = pGrid && GRID_LINES_CHARS == pGrid->GetGridType();
+
+ bool bRubyTop = false;
+
+ if ( bHasGrid )
+ bRubyTop = ! pGrid->GetRubyTextBelow();
+
+ do
+ {
+ m_pCurr = &rMulti.GetRoot();
+ m_nStart = nStartIdx;
+ bRet = false;
+ FormatReset( aInf );
+ aInf.X( nTmpX );
+ aInf.Width( sal_uInt16(nActWidth) );
+ aInf.RealWidth( sal_uInt16(nActWidth) );
+ aInf.SetFirstMulti( bFirstMulti );
+ aInf.SetNumDone( rInf.IsNumDone() );
+ aInf.SetFootnoteDone( rInf.IsFootnoteDone() );
+
+ // if there's a bookmark at the start of the MultiPortion, it will be
+ // painted with the rotation etc. of the MultiPortion; move it *inside*
+ // so it gets positioned correctly; currently there's no other portion
+ // inserted between the end of WhichFirstPortion() and
+ // BuildMultiPortion()
+ if (rInf.GetLast()->GetWhichPor() == PortionType::Bookmark)
+ {
+ auto const pBookmark(static_cast<SwBookmarkPortion*>(rInf.GetLast()));
+ auto *const pPrevious = pBookmark->FindPrevPortion(rInf.GetRoot());
+ assert(!pPrevious || pPrevious->GetNextPortion() == pBookmark);
+ if (pPrevious)
+ {
+ pPrevious->SetNextPortion(nullptr);
+ }
+ rInf.SetLast(pPrevious);
+ assert(m_pCurr->GetNextPortion() == nullptr);
+ m_pCurr->SetNextPortion(pBookmark);
+ }
+
+ if( pFirstRest )
+ {
+ OSL_ENSURE( pFirstRest->InFieldGrp(), "BuildMulti: Fieldrest expected");
+ SwFieldPortion *pField =
+ static_cast<SwFieldPortion*>(pFirstRest)->Clone(
+ static_cast<SwFieldPortion*>(pFirstRest)->GetExp() );
+ pField->SetFollow( true );
+ aInf.SetRest( pField );
+ }
+ aInf.SetRuby( rMulti.IsRuby() && rMulti.OnTop() );
+
+ // in grid mode we temporarily have to disable the grid for the ruby line
+ const bool bOldGridModeAllowed = GetInfo().SnapToGrid();
+ if ( bHasGrid && aInf.IsRuby() && bRubyTop )
+ aInf.SetSnapToGrid( false );
+
+ // If there's no more rubytext, then buildportion is forbidden
+ if( pFirstRest || !aInf.IsRuby() )
+ BuildPortions( aInf );
+
+ aInf.SetSnapToGrid( bOldGridModeAllowed );
+
+ rMulti.CalcSize( *this, aInf );
+ m_pCurr->SetRealHeight( m_pCurr->Height() );
+
+ if( rMulti.IsBidi() )
+ {
+ pNextFirst = aInf.GetRest();
+ break;
+ }
+
+ if( rMulti.HasRotation() && !rMulti.IsDouble() )
+ break;
+ // second line has to be formatted
+ else if( m_pCurr->GetLen()<nMultiLen || rMulti.IsRuby() || aInf.GetRest())
+ {
+ TextFrameIndex const nFirstLen = m_pCurr->GetLen();
+ delete m_pCurr->GetNext();
+ m_pCurr->SetNext( new SwLineLayout() );
+ m_pCurr = m_pCurr->GetNext();
+ m_nStart = aInf.GetIdx();
+ aInf.X( nTmpX );
+ SwTextFormatInfo aTmp( aInf, *m_pCurr, nActWidth );
+ if( rMulti.IsRuby() )
+ {
+ aTmp.SetRuby( !rMulti.OnTop() );
+ pNextFirst = aInf.GetRest();
+ if( pSecondRest )
+ {
+ OSL_ENSURE( pSecondRest->InFieldGrp(), "Fieldrest expected");
+ SwFieldPortion *pField = static_cast<SwFieldPortion*>(pSecondRest)->Clone(
+ static_cast<SwFieldPortion*>(pSecondRest)->GetExp() );
+ pField->SetFollow( true );
+ aTmp.SetRest( pField );
+ }
+ if( !rMulti.OnTop() && nFirstLen < nMultiLen )
+ bRet = true;
+ }
+ else
+ aTmp.SetRest( aInf.GetRest() );
+ aInf.SetRest( nullptr );
+
+ // in grid mode we temporarily have to disable the grid for the ruby line
+ if ( bHasGrid && aTmp.IsRuby() && ! bRubyTop )
+ aTmp.SetSnapToGrid( false );
+
+ BuildPortions( aTmp );
+
+ const SwLinePortion *pRightPortion = rMulti.OnRight() ?
+ rMulti.GetRoot().GetNext()->GetNextPortion() : nullptr;
+ if (pRightPortion)
+ {
+ // The ruby text on the right is vertical.
+ // The width and the height are swapped.
+ SwTwips nHeight = pRightPortion->Height();
+ // Keep room for the ruby text.
+ rMulti.GetRoot().FindLastPortion()->AddPrtWidth( nHeight );
+ }
+
+ aTmp.SetSnapToGrid( bOldGridModeAllowed );
+
+ rMulti.CalcSize( *this, aInf );
+ rMulti.GetRoot().SetRealHeight( rMulti.GetRoot().Height() );
+ m_pCurr->SetRealHeight( m_pCurr->Height() );
+ if( rMulti.IsRuby() )
+ {
+ pNextSecond = aTmp.GetRest();
+ if( pNextFirst )
+ bRet = true;
+ }
+ else
+ pNextFirst = aTmp.GetRest();
+ if( ( !aTmp.IsRuby() && nFirstLen + m_pCurr->GetLen() < nMultiLen )
+ || aTmp.GetRest() )
+ // our guess for width of multiportion was too small,
+ // text did not fit into multiportion
+ bRet = true;
+ }
+ if( rMulti.IsRuby() )
+ break;
+ if( bRet )
+ {
+ // our guess for multiportion width was too small,
+ // we set min to act
+ nMinWidth = nActWidth;
+ nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4;
+ if ( nActWidth == nMaxWidth && rInf.GetLineStart() == rInf.GetIdx() )
+ // we have too less space, we must allow break cuts
+ // ( the first multi flag is considered during TextPortion::Format_() )
+ bFirstMulti = false;
+ if( nActWidth <= nMinWidth )
+ break;
+ }
+ else
+ {
+ // For Solaris, this optimization can causes trouble:
+ // Setting this to the portion width ( = rMulti.Width() )
+ // can make GetTextBreak inside SwTextGuess::Guess return too small
+ // values. Therefore we add some extra twips.
+ if( nActWidth > nTmpX + rMulti.Width() + 6 )
+ nActWidth = nTmpX + rMulti.Width() + 6;
+ nMaxWidth = nActWidth;
+ nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4;
+ if( nActWidth >= nMaxWidth )
+ break;
+ // we do not allow break cuts during formatting
+ bFirstMulti = true;
+ }
+ delete pNextFirst;
+ pNextFirst = nullptr;
+ } while ( true );
+
+ m_pMulti = pOldMulti;
+
+ m_pCurr = pOldCurr;
+ m_nStart = nOldStart;
+ SetPropFont( 0 );
+
+ rMulti.SetLen( rMulti.GetRoot().GetLen() + ( rMulti.GetRoot().GetNext() ?
+ rMulti.GetRoot().GetNext()->GetLen() : TextFrameIndex(0) ) );
+
+ if( rMulti.IsDouble() )
+ {
+ static_cast<SwDoubleLinePortion&>(rMulti).CalcBlanks( rInf );
+ if( static_cast<SwDoubleLinePortion&>(rMulti).GetLineDiff() )
+ {
+ SwLineLayout* pLine = &rMulti.GetRoot();
+ if( static_cast<SwDoubleLinePortion&>(rMulti).GetLineDiff() > 0 )
+ {
+ rInf.SetIdx( nStartIdx + pLine->GetLen() );
+ pLine = pLine->GetNext();
+ }
+ if( pLine )
+ {
+ GetInfo().SetMulti( true );
+
+ // If the fourth element bSkipKashida of function CalcNewBlock is true, multiportion will be showed in justification.
+ // Kashida (Persian) is a type of justification used in some cursive scripts, particularly Arabic.
+ // In contrast to white-space justification, which increases the length of a line of text by expanding spaces between words or individual letters,
+ // kashida justification is accomplished by elongating characters at certain chosen points.
+ // Kashida justification can be combined with white-space justification to various extents.
+ // The default value of bSkipKashida (the 4th parameter passed to 'CalcNewBlock') is false.
+ // Only when Adjust is SvxAdjust::Block ( alignment is justify ), multiportion will be showed in justification in new code.
+ CalcNewBlock( pLine, nullptr, rMulti.Width(), GetAdjust() != SvxAdjust::Block );
+
+ GetInfo().SetMulti( false );
+ }
+ rInf.SetIdx( nStartIdx );
+ }
+ if( static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets() )
+ {
+ rMulti.Width( rMulti.Width() +
+ static_cast<SwDoubleLinePortion&>(rMulti).BracketWidth() );
+ GetInfo().X( nOldX );
+ }
+ }
+ else
+ {
+ rMulti.ActualizeTabulator();
+ if( rMulti.IsRuby() )
+ {
+ static_cast<SwRubyPortion&>(rMulti).Adjust( rInf );
+ static_cast<SwRubyPortion&>(rMulti).CalcRubyOffset();
+ }
+ }
+ if( rMulti.HasRotation() )
+ {
+ SwTwips nH = rMulti.Width();
+ SwTwips nAsc = rMulti.GetAscent() + ( nH - rMulti.Height() )/2;
+ if( nAsc > nH )
+ nAsc = nH;
+ else if( nAsc < 0 )
+ nAsc = 0;
+ rMulti.Width( rMulti.Height() );
+ rMulti.Height( sal_uInt16(nH) );
+ rMulti.SetAscent( sal_uInt16(nAsc) );
+ bRet = ( rInf.GetPos().X() + rMulti.Width() > rInf.Width() ) &&
+ nStartIdx != rInf.GetLineStart();
+ }
+ else if ( rMulti.IsBidi() )
+ {
+ bRet = rMulti.GetLen() < nMultiLen || pNextFirst;
+ }
+
+ // line break has to be performed!
+ if( bRet )
+ {
+ OSL_ENSURE( !pNextFirst || pNextFirst->InFieldGrp(),
+ "BuildMultiPortion: Surprising restportion, field expected" );
+ SwMultiPortion *pTmp;
+ if( rMulti.IsDouble() )
+ pTmp = new SwDoubleLinePortion( static_cast<SwDoubleLinePortion&>(rMulti),
+ nMultiLen + rInf.GetIdx() );
+ else if( rMulti.IsRuby() )
+ {
+ OSL_ENSURE( !pNextSecond || pNextSecond->InFieldGrp(),
+ "BuildMultiPortion: Surprising restportion, field expected" );
+
+ if ( rInf.GetIdx() == rInf.GetLineStart() )
+ {
+ // the ruby portion has to be split in two portions
+ pTmp = new SwRubyPortion( static_cast<SwRubyPortion&>(rMulti),
+ nMultiLen + rInf.GetIdx() );
+
+ if( pNextSecond )
+ {
+ pTmp->GetRoot().SetNext( new SwLineLayout() );
+ pTmp->GetRoot().GetNext()->SetNextPortion( pNextSecond );
+ }
+ pTmp->SetFollowField();
+ }
+ else
+ {
+ // we try to keep our ruby portion together
+ lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
+ pTmp = nullptr;
+ }
+ }
+ else if( rMulti.HasRotation() )
+ {
+ // we try to keep our rotated portion together
+ lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
+ pTmp = new SwRotatedPortion( nMultiLen + rInf.GetIdx(),
+ rMulti.GetDirection() );
+ }
+ // during a recursion of BuildMultiPortions we may not build
+ // a new SwBidiPortion, this would cause a memory leak
+ else if( rMulti.IsBidi() && ! m_pMulti )
+ {
+ if ( ! rMulti.GetLen() )
+ lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
+
+ // If there is a HolePortion at the end of the bidi portion,
+ // it has to be moved behind the bidi portion. Otherwise
+ // the visual cursor travelling gets into trouble.
+ SwLineLayout& aRoot = rMulti.GetRoot();
+ SwLinePortion* pPor = aRoot.GetFirstPortion();
+ while ( pPor )
+ {
+ if ( pPor->GetNextPortion() && pPor->GetNextPortion()->IsHolePortion() )
+ {
+ SwLinePortion* pHolePor = pPor->GetNextPortion();
+ pPor->SetNextPortion( nullptr );
+ aRoot.SetLen( aRoot.GetLen() - pHolePor->GetLen() );
+ rMulti.SetLen( rMulti.GetLen() - pHolePor->GetLen() );
+ rMulti.SetNextPortion( pHolePor );
+ break;
+ }
+ pPor = pPor->GetNextPortion();
+ }
+
+ pTmp = new SwBidiPortion( nMultiLen + rInf.GetIdx(),
+ static_cast<SwBidiPortion&>(rMulti).GetLevel() );
+ }
+ else
+ pTmp = nullptr;
+
+ if ( ! rMulti.GetLen() && rInf.GetLast() )
+ {
+ SeekAndChgBefore( rInf );
+ rInf.GetLast()->FormatEOL( rInf );
+ }
+
+ if( pNextFirst && pTmp )
+ {
+ pTmp->SetFollowField();
+ pTmp->GetRoot().SetNextPortion( pNextFirst );
+ }
+ else
+ // A follow field portion is still waiting. If nobody wants it,
+ // we delete it.
+ delete pNextFirst;
+
+ rInf.SetRest( pTmp );
+ }
+
+ rInf.SetCachedVclData(pOldCachedVclData);
+ rInf.SetText( *pOldText );
+ rInf.SetPaintOfst( nOldPaintOfst );
+ rInf.SetStop( aInf.IsStop() );
+ rInf.SetNumDone( true );
+ rInf.SetFootnoteDone( true );
+ SeekAndChg( rInf );
+ delete pFirstRest;
+ delete pSecondRest;
+ xFontSave.reset();
+ return bRet;
+}
+
+// When a fieldportion at the end of line breaks and needs a following
+// fieldportion in the next line, then the "restportion" of the formatinfo
+// has to be set. Normally this happens during the formatting of the first
+// part of the fieldportion.
+// But sometimes the formatting starts at the line with the following part,
+// especially when the following part is on the next page.
+// In this case the MakeRestPortion-function has to create the following part.
+// The first parameter is the line that contains possibly a first part
+// of a field. When the function finds such field part, it creates the right
+// restportion. This may be a multiportion, e.g. if the field is surrounded by
+// a doubleline- or ruby-portion.
+// The second parameter is the start index of the line.
+SwLinePortion* SwTextFormatter::MakeRestPortion( const SwLineLayout* pLine,
+ TextFrameIndex nPosition)
+{
+ if( !nPosition )
+ return nullptr;
+ TextFrameIndex nMultiPos = nPosition - pLine->GetLen();
+ const SwMultiPortion *pTmpMulti = nullptr;
+ const SwMultiPortion *pHelpMulti = nullptr;
+ const SwLinePortion* pPor = pLine->GetFirstPortion();
+ SwFieldPortion *pField = nullptr;
+ while( pPor )
+ {
+ if( pPor->GetLen() && !pHelpMulti )
+ {
+ nMultiPos = nMultiPos + pPor->GetLen();
+ pTmpMulti = nullptr;
+ }
+ if( pPor->InFieldGrp() )
+ {
+ if( !pHelpMulti )
+ pTmpMulti = nullptr;
+ pField = const_cast<SwFieldPortion*>(static_cast<const SwFieldPortion*>(pPor));
+ }
+ else if( pPor->IsMultiPortion() )
+ {
+ OSL_ENSURE( !pHelpMulti || pHelpMulti->IsBidi(),
+ "Nested multiportions are forbidden." );
+
+ pField = nullptr;
+ pTmpMulti = static_cast<const SwMultiPortion*>(pPor);
+ }
+ pPor = pPor->GetNextPortion();
+ // If the last portion is a multi-portion, we enter it
+ // and look for a field portion inside.
+ // If we are already in a multiportion, we could change to the
+ // next line
+ if( !pPor && pTmpMulti )
+ {
+ if( pHelpMulti )
+ { // We're already inside the multiportion, let's take the second
+ // line, if we are in a double line portion
+ if( !pHelpMulti->IsRuby() )
+ pPor = pHelpMulti->GetRoot().GetNext();
+ pTmpMulti = nullptr;
+ }
+ else
+ { // Now we enter a multiportion, in a ruby portion we take the
+ // main line, not the phonetic line, in a doublelineportion we
+ // starts with the first line.
+ pHelpMulti = pTmpMulti;
+ nMultiPos = nMultiPos - pHelpMulti->GetLen();
+ if( pHelpMulti->IsRuby() && pHelpMulti->OnTop() )
+ pPor = pHelpMulti->GetRoot().GetNext();
+ else
+ pPor = pHelpMulti->GetRoot().GetFirstPortion();
+ }
+ }
+ }
+ if( pField && !pField->HasFollow() )
+ pField = nullptr;
+
+ SwLinePortion *pRest = nullptr;
+ if( pField )
+ {
+ const SwTextAttr *pHint = GetAttr(nPosition - TextFrameIndex(1));
+ if ( pHint
+ && ( pHint->Which() == RES_TXTATR_FIELD
+ || pHint->Which() == RES_TXTATR_ANNOTATION ) )
+ {
+ pRest = NewFieldPortion( GetInfo(), pHint );
+ if( pRest->InFieldGrp() )
+ static_cast<SwFieldPortion*>(pRest)->TakeNextOffset( pField );
+ else
+ {
+ delete pRest;
+ pRest = nullptr;
+ }
+ }
+ }
+ if( !pHelpMulti )
+ return pRest;
+
+ nPosition = nMultiPos + pHelpMulti->GetLen();
+ std::optional<SwMultiCreator> pCreate = GetInfo().GetMultiCreator( nMultiPos, nullptr );
+
+ if ( !pCreate )
+ {
+ OSL_ENSURE( !pHelpMulti->GetLen(), "Multiportion without attribute?" );
+ if ( nMultiPos )
+ --nMultiPos;
+ pCreate = GetInfo().GetMultiCreator( --nMultiPos, nullptr );
+ }
+
+ if (!pCreate)
+ return pRest;
+
+ if( pRest || nMultiPos > nPosition || ( pHelpMulti->IsRuby() &&
+ static_cast<const SwRubyPortion*>(pHelpMulti)->GetRubyOffset() < TextFrameIndex(COMPLETE_STRING)))
+ {
+ SwMultiPortion* pTmp;
+ if( pHelpMulti->IsDouble() )
+ pTmp = new SwDoubleLinePortion( *pCreate, nMultiPos );
+ else if( pHelpMulti->IsBidi() )
+ pTmp = new SwBidiPortion( nMultiPos, pCreate->nLevel );
+ else if( pHelpMulti->IsRuby() )
+ {
+ pTmp = new SwRubyPortion( *pCreate, *GetInfo().GetFont(),
+ m_pFrame->GetDoc().getIDocumentSettingAccess(),
+ nMultiPos, static_cast<const SwRubyPortion*>(pHelpMulti)->GetRubyOffset(),
+ GetInfo() );
+ }
+ else if( pHelpMulti->HasRotation() )
+ pTmp = new SwRotatedPortion( nMultiPos, pHelpMulti->GetDirection() );
+ else
+ {
+ return pRest;
+ }
+ pCreate.reset();
+ pTmp->SetFollowField();
+ if( pRest )
+ {
+ SwLineLayout *pLay = &pTmp->GetRoot();
+ if( pTmp->IsRuby() && pTmp->OnTop() )
+ {
+ pLay->SetNext( new SwLineLayout() );
+ pLay = pLay->GetNext();
+ }
+ pLay->SetNextPortion( pRest );
+ }
+ return pTmp;
+ }
+ return pRest;
+}
+
+// SwTextCursorSave notes the start and current line of a SwTextCursor,
+// sets them to the values for GetModelPositionForViewPoint inside a multiportion
+// and restores them in the destructor.
+SwTextCursorSave::SwTextCursorSave( SwTextCursor* pCursor,
+ SwMultiPortion* pMulti,
+ SwTwips nY,
+ SwTwips& nX,
+ TextFrameIndex const nCurrStart,
+ tools::Long nSpaceAdd )
+ : pTextCursor(pCursor),
+ pCurr(pCursor->m_pCurr),
+ nStart(pCursor->m_nStart)
+{
+ pCursor->m_nStart = nCurrStart;
+ pCursor->m_pCurr = &pMulti->GetRoot();
+ while( pCursor->Y() + pCursor->GetLineHeight() < nY &&
+ pCursor->Next() )
+ ; // nothing
+ nWidth = pCursor->m_pCurr->Width();
+ nOldProp = pCursor->GetPropFont();
+
+ if ( pMulti->IsDouble() || pMulti->IsBidi() )
+ {
+ bSpaceChg = pMulti->ChgSpaceAdd( pCursor->m_pCurr, nSpaceAdd );
+
+ TextFrameIndex nSpaceCnt;
+ if ( pMulti->IsDouble() )
+ {
+ pCursor->SetPropFont( 50 );
+ nSpaceCnt = static_cast<SwDoubleLinePortion*>(pMulti)->GetSpaceCnt();
+ }
+ else
+ {
+ TextFrameIndex const nOldIdx = pCursor->GetInfo().GetIdx();
+ pCursor->GetInfo().SetIdx ( nCurrStart );
+ nSpaceCnt = static_cast<SwBidiPortion*>(pMulti)->GetSpaceCnt(pCursor->GetInfo());
+ pCursor->GetInfo().SetIdx ( nOldIdx );
+ }
+
+ if( nSpaceAdd > 0 && !pMulti->HasTabulator() )
+ pCursor->m_pCurr->Width( o3tl::narrowing<sal_uInt16>(nWidth + nSpaceAdd * sal_Int32(nSpaceCnt) / SPACING_PRECISION_FACTOR) );
+
+ // For a BidiPortion we have to calculate the offset from the
+ // end of the portion
+ if ( nX && pMulti->IsBidi() )
+ nX = pCursor->m_pCurr->Width() - nX;
+ }
+ else
+ bSpaceChg = false;
+}
+
+SwTextCursorSave::~SwTextCursorSave()
+{
+ if( bSpaceChg )
+ SwDoubleLinePortion::ResetSpaceAdd( pTextCursor->m_pCurr );
+ pTextCursor->m_pCurr->Width( nWidth );
+ pTextCursor->m_pCurr = pCurr;
+ pTextCursor->m_nStart = nStart;
+ pTextCursor->SetPropFont( nOldProp );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */