Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/sw/source/core/text/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 96 kB image not shown  

Quelle  pormulti.cxx   Sprache: C

 
/* -*- 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 <EnhancedPDFExportHelper.hxx>
#include <com/sun/star/i18n/BreakType.hpp>
#include <com/sun/star/i18n/XBreakIterator.hpp>
#include <breakit.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 );
}

SwTwips 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 );
}

SwTwips SwBidiPortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo& rInf ) const
{
    nSpaceAdd = nSpaceAdd > LONG_MAX/2 ? LONG_MAX/2 - nSpaceAdd : nSpaceAdd;
    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 );
        SwPositiveSize 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 );
        SwPositiveSize 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 );
}

SwTwips SwDoubleLinePortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &&nbsp;) const
{
    nSpaceAdd = nSpaceAdd > LONG_MAX/2 ? LONG_MAX/2 - nSpaceAdd : nSpaceAdd;
    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( std::move(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 ifstatic_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 ifstatic_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;
                        }
                    }
                }
            }
        }
        // !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 && SfxPoolItem::areSame(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 && SfxPoolItem::areSame(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&&nbsp;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::optional<SwFontSave> oFontSave;
    std::unique_ptr<SwFont> pTmpFnt;

    if( rMulti.IsDouble() )
    {
        pTmpFnt.reset(new SwFont( *GetInfo().GetFont() ));
        if( rMulti.IsDouble() )
        {
            SetPropFont( 50 );
            pTmpFnt->SetProportion( GetPropFont() );
        }
        oFontSave.emplace( GetInfo(), pTmpFnt.get(), this );
    }
    else
    {
        pTmpFnt = nullptr;
    }

    if( rMulti.HasBrackets() )
    {
        // WP is mandatory
        Por_Info const por(rMulti, *this, 1);
        SwTaggedPDFHelper const tag(nullptr, nullptr, &por, *GetInfo().GetOut());

        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" );

    ::std::optional<SwTaggedPDFHelper> oTag;
    if (rMulti.IsDouble())
    {
        Por_Info const por(rMulti, *this, 2);
        oTag.emplace(nullptr, nullptr, &por, *GetInfo().GetOut());
    }
    else if (rMulti.IsRuby())
    {
        Por_Info const por(rMulti, *this, bRubyTop ? 1 : 2);
        oTag.emplace(nullptr, nullptr, &por, *GetInfo().GetOut());
        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 if (!rMulti.IsBidi())
        {
            GetInfo().Y(nOfst + AdjustBaseLine(*pLay, pPor));
        }

        bool bSeeked = true;
        GetInfo().SetLen( pPor->GetLen() );

        if( bRest && pPor->InFieldGrp() && !pPor->GetLen() )
        {
            ifstatic_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
        {
            Por_Info const por(*pPor, *this, 0);
            SwTaggedPDFHelper const tag(nullptr, nullptr, &por, *GetInfo().GetOut());

            pPor->Paint( GetInfo() );
        }

        if (GetFnt()->IsURL() && pPor->InTextGrp())
            GetInfo().NotifyURL(*pPor);

        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
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=94 H=97 G=95

¤ Dauer der Verarbeitung: 0.27 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.