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 131 kB image not shown  

Quelle  itrform2.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 <hintids.hxx>

#include <memory>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <editeng/lspcitem.hxx>
#include <txtflcnt.hxx>
#include <txtftn.hxx>
#include <flyfrms.hxx>
#include <fmtflcnt.hxx>
#include <fmtftn.hxx>
#include <ftninfo.hxx>
#include <charfmt.hxx>
#include <editeng/charrotateitem.hxx>
#include <layfrm.hxx>
#include <viewsh.hxx>
#include <viewopt.hxx>
#include <paratr.hxx>
#include "itrform2.hxx"
#include "porrst.hxx"
#include "portab.hxx"
#include "porfly.hxx"
#include "portox.hxx"
#include "porref.hxx"
#include "porfld.hxx"
#include "porftn.hxx"
#include "porhyph.hxx"
#include "pordrop.hxx"
#include "redlnitr.hxx"
#include <sortedobjs.hxx>
#include <fmtanchr.hxx>
#include <pagefrm.hxx>
#include <tgrditem.hxx>
#include <doc.hxx>
#include "pormulti.hxx"
#include <unotools/charclass.hxx>
#include <xmloff/odffields.hxx>
#include <IDocumentSettingAccess.hxx>
#include <IMark.hxx>
#include <IDocumentMarkAccess.hxx>
#include <comphelper/processfactory.hxx>
#include <vcl/pdfextoutdevdata.hxx>
#include <comphelper/string.hxx>
#include <docsh.hxx>
#include <unocrsrhelper.hxx>
#include <textcontentcontrol.hxx>
#include <EnhancedPDFExportHelper.hxx>
#include <com/sun/star/rdf/Statement.hpp>
#include <com/sun/star/rdf/URI.hpp>
#include <com/sun/star/rdf/URIs.hpp>
#include <com/sun/star/rdf/XDocumentMetadataAccess.hpp>
#include <com/sun/star/rdf/XLiteral.hpp>
#include <com/sun/star/text/XTextContent.hpp>
#include <unotxdoc.hxx>

using namespace ::com::sun::star;

namespace {
    //! Calculates and sets optimal repaint offset for the current line
    tools::Long lcl_CalcOptRepaint( SwTextFormatter &rThis,
                                    SwLineLayout const &rCurr,
                                    TextFrameIndex nOldLineEnd,
                                    const std::vector<tools::Long> &rFlyStarts );
    //! Determine if we need to build hidden portions
    bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex &rPos);

    // Check whether the two font has the same border
    bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond);
}

static void ClearFly( SwTextFormatInfo &rInf )
{
    delete rInf.GetFly();
    rInf.SetFly(nullptr);
}

void SwTextFormatter::CtorInitTextFormatter( SwTextFrame *pNewFrame, SwTextFormatInfo *pNewInf )
{
    CtorInitTextPainter( pNewFrame, pNewInf );
    m_pInf = pNewInf;
    m_pDropFormat = GetInfo().GetDropFormat();
    m_pMulti = nullptr;

    m_bOnceMore = false;
    m_bFlyInContentBase = false;
    m_bTruncLines = false;
    m_nContentEndHyph = 0;
    m_nContentMidHyph = 0;
    m_nLeftScanIdx = TextFrameIndex(COMPLETE_STRING);
    m_nRightScanIdx = TextFrameIndex(0);
    m_pByEndIter.reset();
    m_pFirstOfBorderMerge = nullptr;

    if (m_nStart > TextFrameIndex(GetInfo().GetText().getLength()))
    {
        OSL_ENSURE( false"+SwTextFormatter::CTOR: bad offset" );
        m_nStart = TextFrameIndex(GetInfo().GetText().getLength());
    }

}

SwTextFormatter::~SwTextFormatter()
{
    // Extremely unlikely, but still possible
    // e.g.: field splits up, widows start to matter
    if( GetInfo().GetRest() )
    {
        delete GetInfo().GetRest();
        GetInfo().SetRest(nullptr);
    }
}

void SwTextFormatter::Insert( SwLineLayout *pLay )
{
    // Insert BEHIND the current element
    if ( m_pCurr )
    {
        pLay->SetNext( m_pCurr->GetNext() );
        m_pCurr->SetNext( pLay );
    }
    else
        m_pCurr = pLay;
}

SwTwips SwTextFormatter::GetFrameRstHeight() const
{
    // We want the rest height relative to the page.
    // If we're in a table, then pFrame->GetUpper() is not the page.

    // GetFrameRstHeight() is being called with Footnote.
    // Wrong: const SwFrame *pUpper = pFrame->GetUpper();
    const SwFrame *pPage = m_pFrame->FindPageFrame();
    const SwTwips nHeight = pPage->getFrameArea().Top()
                          + pPage->getFramePrintArea().Top()
                          + pPage->getFramePrintArea().Height() - Y();
    if( 0 > nHeight )
        return m_pCurr->Height();
    else
        return nHeight;
}

bool SwTextFormatter::ClearIfIsFirstOfBorderMerge(const SwLinePortion* pPortion)
{
    if (pPortion == m_pFirstOfBorderMerge)
    {
        m_pFirstOfBorderMerge = nullptr;
        return true;
    }
    return false;
}

SwLinePortion *SwTextFormatter::Underflow( SwTextFormatInfo &rInf )
{
    // Save values and initialize rInf
    SwLinePortion *pUnderflow = rInf.GetUnderflow();
    if( !pUnderflow )
        return nullptr;

    // We format backwards, i.e. attribute changes can happen the next
    // line again.
    // Can be seen in 8081.sdw, if you enter text in the first line

    TextFrameIndex const nSoftHyphPos = rInf.GetSoftHyphPos();

    // Save flys and set to 0, or else segmentation fault
    // Not ClearFly(rInf) !
    SwFlyPortion *pFly = rInf.GetFly();
    rInf.SetFly( nullptr );

    FeedInf( rInf );
    rInf.SetLast( m_pCurr );
    // pUnderflow does not need to be deleted, because it will drown in the following
    // Truncate()
    rInf.SetUnderflow(nullptr);
    rInf.SetSoftHyphPos( nSoftHyphPos );
    rInf.SetPaintOfst( GetLeftMargin() );

    // We look for the portion with the under-flow position
    SwLinePortion *pPor = m_pCurr->GetFirstPortion();
    if( pPor != pUnderflow )
    {
        // pPrev will be the last portion before pUnderflow,
        // which still has a real width.
        // Exception: SoftHyphPortion must not be forgotten, of course!
        // Although they don't have a width.
        SwLinePortion *pTmpPrev = pPor;
        while( pPor && pPor != pUnderflow )
        {
            if( !pPor->IsKernPortion() &&
                ( pPor->Width() || pPor->IsSoftHyphPortion() ) )
            {
                while( pTmpPrev != pPor )
                {
                    pTmpPrev->Move( rInf );
                    rInf.SetLast( pTmpPrev );
                    pTmpPrev = pTmpPrev->GetNextPortion();
                    OSL_ENSURE( pTmpPrev, "Underflow: losing control!" );
                };
            }
            pPor = pPor->GetNextPortion();
        }
        pPor = pTmpPrev;
        if( pPor && // Skip flys and initials when underflow.
            ( pPor->IsFlyPortion() || pPor->IsDropPortion() ||
              pPor->IsFlyCntPortion() ) )
        {
            pPor->Move( rInf );
            rInf.SetLast( pPor );
            rInf.SetStopUnderflow( true );
            pPor = pUnderflow;
        }
    }

    // What? The under-flow portion is not in the portion chain?
    OSL_ENSURE( pPor, "SwTextFormatter::Underflow: overflow but underflow" );

    // Snapshot
    if ( pPor==rInf.GetLast() )
    {
        // We end up here, if the portion triggering the under-flow
        // spans over the whole line. E.g. if a word spans across
        // multiple lines and flows into a fly in the second line.
        rInf.SetFly( pFly );
        pPor->Truncate();
        return pPor; // Is that enough?
    }
    // End the snapshot

    // X + Width == 0 with SoftHyph > Line?!
    if( !pPor || !(rInf.X() + pPor->Width()) )
    {
        delete pFly;
        return nullptr;
    }

    // Preparing for Format()
    // We need to chip off the chain behind pLast, because we Insert after the Format()
    SeekAndChg( rInf );

    // line width is adjusted, so that pPor does not fit to current
    // line anymore
    rInf.Width( rInf.X() + (pPor->Width() ? pPor->Width() - 1 : 0) );
    rInf.SetLen( pPor->GetLen() );
    rInf.SetFull( false );
    if( pFly )
    {
        // We need to recalculate the FlyPortion due to the following reason:
        // If the base line is lowered by a big font in the middle of the line,
        // causing overlapping with a fly, the FlyPortion has a wrong size/fixed
        // size.
        rInf.SetFly( pFly );
        CalcFlyWidth( rInf );
    }
    rInf.GetLast()->SetNextPortion(nullptr);

    // The SwLineLayout is an exception to this, which splits at the first
    // portion change.
    // Here only the other way around:
    if( rInf.GetLast() == m_pCurr )
    {
        if( pPor->InTextGrp() && !pPor->InExpGrp() )
        {
            const PortionType nOldWhich = m_pCurr->GetWhichPor();
            *static_cast<SwLinePortion*>(m_pCurr) = *pPor;
            m_pCurr->SetNextPortion( pPor->GetNextPortion() );
            m_pCurr->SetWhichPor( nOldWhich );
            pPor->SetNextPortion( nullptr );
            delete pPor;
            pPor = m_pCurr;
        }
    }

    // Make sure that m_pFirstOfBorderMerge does not point to a portion which
    // will be deleted by Truncate() below.
    SwLinePortion* pNext = pPor->GetNextPortion();
    while (pNext)
    {
        if (ClearIfIsFirstOfBorderMerge(pNext))
            break;
        pNext = pNext->GetNextPortion();
    }
    pPor->Truncate();
    SwLinePortion *const pRest( rInf.GetRest() );
    if (pRest && pRest->InFieldGrp() &&
        static_cast<SwFieldPortion*>(pRest)->IsNoLength())
    {
        // HACK: decrement again, so we pick up the suffix in next line!
        m_pByEndIter->PrevAttr();
    }
    delete pRest;
    rInf.SetRest(nullptr);
    return pPor;
}

void SwTextFormatter::InsertPortion( SwTextFormatInfo &rInf,
                                    SwLinePortion *pPor )
{
    SwLinePortion *pLast = nullptr;
    // The new portion is inserted, but everything's different for
    // LineLayout...
    if( pPor == m_pCurr )
    {
        if ( m_pCurr->GetNextPortion() )
        {
            pLast = pPor;
            pPor = m_pCurr->GetNextPortion();
        }

        // i#112181 - Prevent footnote anchor being wrapped to next line
        // without preceding word
        rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() );
    }
    else
    {
        pLast = rInf.GetLast();
        if( pLast->GetNextPortion() )
        {
            while( pLast->GetNextPortion() )
                pLast = pLast->GetNextPortion();
            rInf.SetLast( pLast );
        }
        pLast->Insert( pPor );

        rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() );

        // Adjust maxima
        if( m_pCurr->Height() < pPor->Height() )
            m_pCurr->Height( pPor->Height(), pPor->IsTextPortion() );
        if( m_pCurr->GetAscent() < pPor->GetAscent() )
            m_pCurr->SetAscent( pPor->GetAscent() );
        if( m_pCurr->GetHangingBaseline() < pPor->GetHangingBaseline() )
            m_pCurr->SetHangingBaseline( pPor->GetHangingBaseline() );

        if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY))
        {
            // For DOCX with compat=14 the only shape in line defines height of the line in spite of used font
            if (pLast->IsFlyCntPortion() && pPor->IsTextPortion() && pPor->GetLen() == TextFrameIndex(0))
            {
                m_pCurr->SetAscent(pLast->GetAscent());
                m_pCurr->Height(pLast->Height());
            }
        }
    }

    // Sometimes chains are constructed (e.g. by hyphenate)
    rInf.SetLast( pPor );
    while( pPor )
    {
        if (!pPor->IsDropPortion())
            MergeCharacterBorder(*pPor, pLast, rInf);

        pPor->Move( rInf );
        rInf.SetLast( pPor );
        pLast = pPor;
        pPor = pPor->GetNextPortion();
    }
}

void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf )
{
    OSL_ENSURE( rInf.GetText().getLength() < COMPLETE_STRING,
            "SwTextFormatter::BuildPortions: bad text length in info" );

    rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() );

    // First NewTextPortion() decides whether pCurr ends up in pPor.
    // We need to make sure that the font is being set in any case.
    // This is done automatically in CalcAscent.
    rInf.SetLast( m_pCurr );
    rInf.ForcedLeftMargin( 0 );

    OSL_ENSURE( m_pCurr->FindLastPortion() == m_pCurr, "pLast supposed to equal pCurr" );

    if( !m_pCurr->GetAscent() && !m_pCurr->Height() )
        CalcAscent( rInf, m_pCurr );

    SeekAndChg( rInf );

    // Width() is shortened in CalcFlyWidth if we have a FlyPortion
    OSL_ENSURE( !rInf.X() || m_pMulti, "SwTextFormatter::BuildPortion X=0?" );
    CalcFlyWidth( rInf );
    SwFlyPortion *pFly = rInf.GetFly();
    if( pFly )
    {
        if ( 0 < pFly->GetFix() )
            ClearFly( rInf );
        else
            rInf.SetFull(true);
    }

    ::std::optional<TextFrameIndex> oMovedFlyIndex;
    if (SwTextFrame const*const pFollow = GetTextFrame()->GetFollow())
    {
        // flys are always on master!
        if (GetTextFrame()->GetDrawObjs() && pFollow->GetUpper() != GetTextFrame()->GetUpper())
        {
            for (SwAnchoredObject const*const pAnchoredObj : *GetTextFrame()->GetDrawObjs())
            {
                // tdf#146500 try to stop where a fly is anchored in the follow
                // that has recently been moved (presumably by splitting this
                // frame); similar to check in SwFlowFrame::MoveBwd()
                if (pAnchoredObj->RestartLayoutProcess()
                    && !pAnchoredObj->IsTmpConsiderWrapInfluence())
                {
                    SwFormatAnchor const& rAnchor(pAnchoredObj->GetFrameFormat()->GetAnchor());
                    assert(rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR || rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA);
                    TextFrameIndex const nAnchor(GetTextFrame()->MapModelToViewPos(*rAnchor.GetContentAnchor()));
                    if (pFollow->GetOffset() <= nAnchor
                        && (pFollow->GetFollow() == nullptr
                            || nAnchor < pFollow->GetFollow()->GetOffset()))
                    {
                        if (!oMovedFlyIndex || nAnchor < *oMovedFlyIndex)
                        {
                            oMovedFlyIndex.emplace(nAnchor);
                        }
                    }
                }
            }
        }
    }

    SwLinePortion *pPor = NewPortion(rInf, oMovedFlyIndex);

    // Asian grid stuff
    SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));

    // tdf#149089: For compatibility with MSO grid layout, do not insert kern portions to
    // align successive portions to the char grid when MS_WORD_COMP_GRID_METRICS is set.
    // See also tdf#161145.
    // tdf#139418: However, in testing, this only seems to apply to horizontal text.
    const bool bUseGridKernPors = GetTextFrame()->IsVertical()
                                  || !GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
                                      DocumentSettingId::MS_WORD_COMP_GRID_METRICS);
    const bool bHasGrid = pGrid && rInf.SnapToGrid()
                          && SwTextGrid::LinesAndChars == pGrid->GetGridType() && bUseGridKernPors;

    const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc();
    const sal_uInt16 nGridWidth = bHasGrid ? GetGridWidth(*pGrid, rDoc) : 0;

    // used for grid mode only:
    // the pointer is stored, because after formatting of non-asian text,
    // the width of the kerning portion has to be adjusted
    // Inserting a SwKernPortion before a SwTabPortion isn't necessary
    // and will break the SwTabPortion.
    SwKernPortion* pGridKernPortion = nullptr;

    bool bFull = false;
    SwTwips nUnderLineStart = 0;
    rInf.Y( Y() );

    while( pPor && !rInf.IsStop() )
    {
        OSL_ENSURE(rInf.GetLen() < TextFrameIndex(COMPLETE_STRING) &&
                rInf.GetIdx() <= TextFrameIndex(rInf.GetText().getLength()),
                "SwTextFormatter::BuildPortions: bad length in info" );

        // We have to check the script for fields in order to set the
        // correct nActual value for the font.
        if( pPor->InFieldGrp() )
            static_cast<SwFieldPortion*>(pPor)->CheckScript( rInf );

        auto fnRequireKerningAtPosition = [&](TextFrameIndex nIdx)
        {
            if (nIdx == TextFrameIndex{ 0 })
                return false;

            auto nCurrScriptType = m_pScriptInfo->ScriptType(nIdx);
            auto nPrevScriptType = m_pScriptInfo->ScriptType(nIdx - TextFrameIndex{ 1 });
            if (nCurrScriptType == nPrevScriptType)
                return false;

            // tdf#89288: Only insert space between CJK and non-CJK text
            if (nCurrScriptType != css::i18n::ScriptType::ASIAN
                && nPrevScriptType != css::i18n::ScriptType::ASIAN)
                return false;

            // tdf#136663: However, this extra space is only a feature of Chinese and Japanese
            // typesetting. Extra space shouldn't be inserted between Hangul and non-CJK characters.
            const CharClass& rCC = GetAppCharClass();
            auto fnIsHangul = [&](TextFrameIndex nPos)
            {
                auto nType = rCC.getScript(rInf.GetText(), static_cast<sal_Int32>(nPos));
                switch (nType)
                {
                    case css::i18n::UnicodeScript_kHangulJamo:
                    case css::i18n::UnicodeScript_kHangulCompatibilityJamo:
                    case css::i18n::UnicodeScript_kHangulSyllable:
                        return true;

                    default:
                        return false;
                }
            };

            bool bCurrIsHangul = fnIsHangul(nIdx);
            bool bPrevIsHangul = fnIsHangul(nIdx - TextFrameIndex{ 1 });
            return !bPrevIsHangul && !bCurrIsHangul;
        };

        if( ! bHasGrid && rInf.HasScriptSpace() &&
            rInf.GetLast() && rInf.GetLast()->InTextGrp() &&
            rInf.GetLast()->Width() && !rInf.GetLast()->InNumberGrp() )
        {
            tools::Long nLstHeight = rInf.GetFont()->GetHeight();
            bool bAllowBehind = false;
            const CharClass& rCC = GetAppCharClass();

            // are there any punctuation characters on both sides
            // of the kerning portion?
            if ( pPor->InFieldGrp() )
            {
                OUString aAltText;
                if ( static_cast<SwFieldPortion*>(pPor)->GetExpText( rInf, aAltText ) &&
                        !aAltText.isEmpty() )
                {
                    bAllowBehind = rCC.isLetterNumeric( aAltText, 0 );
                }
            }
            else
            {
                const OUString& rText = rInf.GetText();
                sal_Int32 nIdx = sal_Int32(rInf.GetIdx());
                bAllowBehind = nIdx < rText.getLength() && rCC.isLetterNumeric(rText, nIdx);
            }

            const SwLinePortion* pLast = rInf.GetLast();
            if ( bAllowBehind && pLast )
            {
                bool bAllowBefore = false;

                if ( pLast->InFieldGrp() )
                {
                    OUString aAltText;
                    if ( static_cast<const SwFieldPortion*>(pLast)->GetExpText( rInf, aAltText ) &&
                         !aAltText.isEmpty() )
                    {
                        bAllowBefore = rCC.isLetterNumeric( aAltText, aAltText.getLength() - 1 );

                        const SwFont* pTmpFnt = static_cast<const SwFieldPortion*>(pLast)->GetFont();
                        if ( pTmpFnt )
                        {
                            nLstHeight = pTmpFnt->GetHeight();
                        }
                    }
                }
                else if ( rInf.GetIdx() )
                {
                    bAllowBefore = rCC.isLetterNumeric(rInf.GetText(), sal_Int32(rInf.GetIdx()) - 1);
                }

                nLstHeight /= 5;
                // does the kerning portion still fit into the line?
                if (bAllowBefore && fnRequireKerningAtPosition(rInf.GetIdx()) && nLstHeight
                    && rInf.X() + nLstHeight <= rInf.Width() && !pPor->InTabGrp())
                {
                    SwKernPortion* pKrn =
                        new SwKernPortion( *rInf.GetLast(), nLstHeight,
                                           pLast->InFieldGrp() && pPor->InFieldGrp() );

                    // ofz#58550 Direct-leak, pKrn adds itself as the NextPortion
                    // of rInf.GetLast(), but may use CopyLinePortion to add a copy
                    // of itself, which will then be left dangling with the following
                    // SetNextPortion(nullptr)
                    SwLinePortion *pNext = rInf.GetLast()->GetNextPortion();
                    if (pNext != pKrn)
                        delete pNext;

                    rInf.GetLast()->SetNextPortion( nullptr );
                    InsertPortion( rInf, pKrn );
                }
            }
        }
        else if ( bHasGrid && ! pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() )
        {
            // insert a grid kerning portion
            pGridKernPortion = pPor->IsKernPortion() ?
                               static_cast<SwKernPortion*>(pPor) :
                               new SwKernPortion( *m_pCurr );

            // if we have a new GridKernPortion, we initially calculate
            // its size so that its ends on the grid
            const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame();
            const SwLayoutFrame* pBody = pPageFrame->FindBodyCont();
            SwRectFnSet aRectFnSet(pPageFrame);

            const tools::Long nGridOrigin = pBody ?
                                    aRectFnSet.GetPrtLeft(*pBody) :
                                    aRectFnSet.GetPrtLeft(*pPageFrame);

            SwTwips nStartX = rInf.X() + GetLeftMargin();
            if ( aRectFnSet.IsVert() )
            {
                Point aPoint( nStartX, 0 );
                m_pFrame->SwitchHorizontalToVertical( aPoint );
                nStartX = aPoint.Y();
            }

            const SwTwips nOfst = nStartX - nGridOrigin;
            if ( nOfst )
            {
                const sal_uLong i = ( nOfst > 0 ) ?
                                ( ( nOfst - 1 ) / nGridWidth + 1 ) :
                                0;
                const SwTwips nKernWidth = i * nGridWidth - nOfst;
                const SwTwips nRestWidth = rInf.Width() - rInf.X();

                if ( nKernWidth <= nRestWidth )
                    pGridKernPortion->Width( nKernWidth );
            }

            if ( pGridKernPortion != pPor )
                InsertPortion( rInf, pGridKernPortion );
        }

        if( pPor->IsDropPortion() )
            MergeCharacterBorder(*static_cast<SwDropPortion*>(pPor));

        // the multi-portion has its own format function
        if( pPor->IsMultiPortion() && ( !m_pMulti || m_pMulti->IsBidi() ) )
            bFull = BuildMultiPortion( rInf, *static_cast<SwMultiPortion*>(pPor) );
        else
            bFull = pPor->Format( rInf );

        if( rInf.IsRuby() && !rInf.GetRest() )
            bFull = true;

        // if we are underlined, we store the beginning of this underlined
        // segment for repaint optimization
        if ( LINESTYLE_NONE != m_pFont->GetUnderline() && ! nUnderLineStart )
            nUnderLineStart = GetLeftMargin() + rInf.X();

        if ( pPor->IsFlyPortion() )
            m_pCurr->SetFly( true );
        // some special cases, where we have to take care for the repaint
        // offset:
        // 1. Underlined portions due to special underline feature
        // 2. Right Tab
        // 3. BidiPortions
        // 4. other Multiportions
        // 5. DropCaps
        // 6. Grid Mode
        else if ( ( ! rInf.GetPaintOfst() || nUnderLineStart < rInf.GetPaintOfst() ) &&
                  // 1. Underlined portions
                  nUnderLineStart &&
                     // reformat is at end of an underlined portion and next portion
                     // is not underlined
                  ( ( rInf.GetReformatStart() == rInf.GetIdx() &&
                      LINESTYLE_NONE == m_pFont->GetUnderline()
                    ) ||
                     // reformat is inside portion and portion is underlined
                    ( rInf.GetReformatStart() >= rInf.GetIdx() &&
                      rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() &&
                      LINESTYLE_NONE != m_pFont->GetUnderline() ) ) )
            rInf.SetPaintOfst( nUnderLineStart );
        else if (  ! rInf.GetPaintOfst() &&
                   // 2. Right Tab
                   ( ( pPor->InTabGrp() && !pPor->IsTabLeftPortion() ) ||
                   // 3. BidiPortions
                     ( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() ) ||
                   // 4. Multi Portion and 5. Drop Caps
                     ( ( pPor->IsDropPortion() || pPor->IsMultiPortion() ) &&
                       rInf.GetReformatStart() >= rInf.GetIdx() &&
                       rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() )
                   // 6. Grid Mode
                     || ( bHasGrid && SwFontScript::CJK != m_pFont->GetActual() )
                   )
                )
            // we store the beginning of the critical portion as our
            // paint offset
            rInf.SetPaintOfst( GetLeftMargin() + rInf.X() );

        // under one of these conditions we are allowed to delete the
        // start of the underline portion
        if ( IsUnderlineBreak( *pPor, *m_pFont ) )
            nUnderLineStart = 0;

        if( pPor->IsFlyCntPortion() || ( pPor->IsMultiPortion() &&
            static_cast<SwMultiPortion*>(pPor)->HasFlyInContent() ) )
            SetFlyInCntBase();
        // bUnderflow needs to be reset or we wrap again at the next softhyphen
        if ( !bFull )
        {
            rInf.ClrUnderflow();
            if( ! bHasGrid && rInf.HasScriptSpace() && pPor->InTextGrp() &&
                pPor->GetLen() && !pPor->InFieldGrp() )
            {
                // The distance between two different scripts is set
                // to 20% of the fontheight.
                TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen();
                if (nTmp == m_pScriptInfo->NextScriptChg(nTmp - TextFrameIndex(1))
                    && nTmp != TextFrameIndex(rInf.GetText().getLength())
                    && fnRequireKerningAtPosition(nTmp))
                {
                    const SwTwips nDist = rInf.GetFont()->GetHeight()/5;

                    if( nDist )
                    {
                        // we do not want a kerning portion if any end
                        // would be a punctuation character
                        const CharClass& rCC = GetAppCharClass();
                        if (rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp) - 1)
                            && rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp)))
                        {
                            // does the kerning portion still fit into the line?
                            if ( rInf.X() + pPor->Width() + nDist <= rInf.Width() )
                                new SwKernPortion( *pPor, nDist );
                            else
                                bFull = true;
                        }
                    }
                }
            }
        }

        if ( bHasGrid && pPor != pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() )
        {
            TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen();
            const SwTwips nRestWidth = rInf.Width() - rInf.X() - pPor->Width();

            const SwFontScript nCurrScript = m_pFont->GetActual(); // pScriptInfo->ScriptType( rInf.GetIdx() );
            const SwFontScript nNextScript =
                nTmp >= TextFrameIndex(rInf.GetText().getLength())
                    ? SwFontScript::CJK
                    : m_pScriptInfo->WhichFont(nTmp);

            // snap non-asian text to grid if next portion is ASIAN or
            // there are no more portions in this line
            // be careful when handling an underflow event: the gridkernportion
            // could have been deleted
            if ( nRestWidth > 0 && SwFontScript::CJK != nCurrScript &&
                ! rInf.IsUnderflow() && ( bFull || SwFontScript::CJK == nNextScript ) )
            {
                OSL_ENSURE( pGridKernPortion, "No GridKernPortion available" );

                // calculate size
                SwLinePortion* pTmpPor = pGridKernPortion->GetNextPortion();
                SwTwips nSumWidth = pPor->Width();
                while ( pTmpPor )
                {
                    nSumWidth = nSumWidth + pTmpPor->Width();
                    pTmpPor = pTmpPor->GetNextPortion();
                }

                const SwTwips i = nSumWidth ?
                                 ( nSumWidth - 1 ) / nGridWidth + 1 :
                                 0;
                const SwTwips nTmpWidth = i * nGridWidth;
                const SwTwips nKernWidth = std::min(nTmpWidth - nSumWidth, nRestWidth);
                const SwTwips nKernWidth_1 = pGrid->IsSnapToChars() ?
                    nKernWidth / 2 : 0;

                OSL_ENSURE( nKernWidth <= nRestWidth,
                        "Not enough space left for adjusting non-asian text in grid mode" );
                if (nKernWidth_1)
                {
                    pGridKernPortion->Width( pGridKernPortion->Width() + nKernWidth_1 );
                    rInf.X( rInf.X() + nKernWidth_1 );
                }

                if ( ! bFull && nKernWidth - nKernWidth_1 > 0 )
                    new SwKernPortion( *pPor, static_cast<short>(nKernWidth - nKernWidth_1),
                                       falsetrue );

                pGridKernPortion = nullptr;
            }
            else if ( pPor->IsMultiPortion() || pPor->InFixMargGrp() ||
                      pPor->IsFlyCntPortion() || pPor->InNumberGrp() ||
                      pPor->InFieldGrp() || nCurrScript != nNextScript )
                // next portion should snap to grid
                pGridKernPortion = nullptr;
        }

        rInf.SetFull( bFull );

        // Restportions from fields with multiple lines don't yet have the right ascent
        if ( !pPor->GetLen() && !pPor->IsFlyPortion()
            && !pPor->IsGrfNumPortion() && ! pPor->InNumberGrp()
            && !pPor->IsMultiPortion() )
            CalcAscent( rInf, pPor );

        InsertPortion( rInf, pPor );
        if (pPor->IsMultiPortion() && (!m_pMulti || m_pMulti->IsBidi()))
        {
            (void) rInf.CheckCurrentPosBookmark(); // bookmark was already created inside MultiPortion!
        }
        pPor = NewPortion(rInf, oMovedFlyIndex);
    }

    if( !rInf.IsStop() )
    {
        // The last right centered, decimal tab
        SwTabPortion *pLastTab = rInf.GetLastTab();
        if( pLastTab )
            pLastTab->FormatEOL( rInf );
        else if( rInf.GetLast() && rInf.LastKernPortion() )
            rInf.GetLast()->FormatEOL( rInf );
    }
    if( m_pCurr->GetNextPortion() && m_pCurr->GetNextPortion()->InNumberGrp()
        && static_cast<SwNumberPortion*>(m_pCurr->GetNextPortion())->IsHide() )
        rInf.SetNumDone( false );

    // Delete fly in any case
    ClearFly( rInf );

    // Reinit the tab overflow flag after the line
    rInf.SetTabOverflow( false );
}

void SwTextFormatter::CalcAdjustLine( SwLineLayout *pCurrent )
{
    if( SvxAdjust::Left != GetAdjust() && !m_pMulti)
    {
        pCurrent->SetFormatAdj(true);
        if( IsFlyInCntBase() )
        {
            CalcAdjLine( pCurrent );
            // For e.g. centered fly we need to switch the RefPoint
            // That's why bAlways = true
            UpdatePos( pCurrent, GetTopLeft(), GetStart(), true );
        }
    }
}

void SwTextFormatter::CalcAscent( SwTextFormatInfo &rInf, SwLinePortion *pPor )
{
    bool bCalc = false;
    if ( pPor->InFieldGrp() && static_cast<SwFieldPortion*>(pPor)->GetFont() )
    {
        // Numbering + InterNetFields can keep an own font, then their size is
        // independent from hard attribute values
        SwFont* pFieldFnt = static_cast<SwFieldPortion*>(pPor)->m_pFont.get();
        SwFontSave aSave( rInf, pFieldFnt );
        pPor->Height( rInf.GetTextHeight() );
        pPor->SetAscent( rInf.GetAscent() );
        bCalc = true;
    }
    // i#89179
    // tab portion representing the list tab of a list label gets the
    // same height and ascent as the corresponding number portion
    else if ( pPor->InTabGrp() && pPor->GetLen() == TextFrameIndex(0) &&
              rInf.GetLast() && rInf.GetLast()->InNumberGrp() &&
              static_cast<const SwNumberPortion*>(rInf.GetLast())->HasFont() )
    {
        const SwLinePortion* pLast = rInf.GetLast();
        pPor->Height( pLast->Height() );
        pPor->SetAscent( pLast->GetAscent() );
    }
    else if (pPor->GetWhichPor() == PortionType::Bookmark
            && rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
    {
        // bookmark at end of paragraph: *don't* advance iterator, use the
        // current font instead; it's possible that there's a font size on the
        // paragraph and it's overridden on the last line of the paragraph and
        // we don't want to apply it via SwBookmarkPortion and grow the line
        // height (example: n758883.docx)
        SwLinePortion const*const pLast = rInf.GetLast();
        assert(pLast);
        pPor->Height( pLast->Height(), false );
        pPor->SetAscent( pLast->GetAscent() );
    }
    else
    {
        const SwLinePortion *pLast = rInf.GetLast();
        bool bChg = false;

        // In empty lines the attributes are switched on via SeekStart
        const bool bFirstPor = rInf.GetLineStart() == rInf.GetIdx();

        if ( pPor->IsQuoVadisPortion() )
            bChg = SeekStartAndChg( rInf, true );
        else
        {
            if( bFirstPor )
            {
                if( !rInf.GetText().isEmpty() )
                {
                    if ((rInf.GetIdx() != TextFrameIndex(rInf.GetText().getLength())
                            || rInf.GetRest() // field continued - not empty
                            || !GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
                                DocumentSettingId::APPLY_TEXT_ATTR_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH))
                        && (pPor->GetLen() || !rInf.GetIdx()
                            || (m_pCurr != pLast && !pLast->IsFlyPortion())
                            || !m_pCurr->IsRest())) // instead of !rInf.GetRest()
                    {
                        bChg = SeekAndChg( rInf );
                    }
                    else
                        bChg = SeekAndChgBefore( rInf );
                }
                else if ( m_pMulti )
                    // do not open attributes starting at 0 in empty multi
                    // portions (rotated numbering followed by a footnote
                    // can cause trouble, because the footnote attribute
                    // starts at 0, but if we open it, the attribute handler
                    // cannot handle it.
                    bChg = false;
                else
                    bChg = SeekStartAndChg( rInf );
            }
            else
                bChg = SeekAndChg( rInf );
        }
        if( bChg || bFirstPor || !pPor->GetAscent()
            || !rInf.GetLast()->InTextGrp() )
        {
            pPor->SetHangingBaseline( rInf.GetHangingBaseline() );
            pPor->SetAscent( rInf.GetAscent()  );
            pPor->Height(rInf.GetTextHeight());
            bCalc = true;
        }
        else
        {
            pPor->Height( pLast->Height() );
            pPor->SetAscent( pLast->GetAscent() );
        }
    }

    if( pPor->InTextGrp() && bCalc )
    {
        pPor->SetAscent(pPor->GetAscent() +
            rInf.GetFont()->GetTopBorderSpace());
        pPor->Height(pPor->Height() +
            rInf.GetFont()->GetTopBorderSpace() +
            rInf.GetFont()->GetBottomBorderSpace() );
    }
}

namespace {

class SwMetaPortion : public SwTextPortion
{
    Color m_aShadowColor;
public:
    SwMetaPortion() { SetWhichPor( PortionType::Meta ); }
    virtual void Paint( const SwTextPaintInfo &rInf ) const override;
    void SetShadowColor(const Color& rCol ) { m_aShadowColor = rCol; }
};

/// A content control portion is a text portion that is inside RES_TXTATR_CONTENTCONTROL.
class SwContentControlPortion : public SwTextPortion
{
    SwTextContentControl* m_pTextContentControl;
public:
    SwContentControlPortion(SwTextContentControl* pTextContentControl);
    virtual void Paint(const SwTextPaintInfo& rInf) const override;

    /// Emits a PDF form widget for this portion on success, does nothing on failure.
    bool DescribePDFControl(const SwTextPaintInfo& rInf) const;
};
}

void SwMetaPortion::Paint( const SwTextPaintInfo &rInf ) const
{
    if ( Width() )
    {
        rInf.DrawViewOpt( *this, PortionType::Meta,
                // custom shading (RDF metadata)
                COL_BLACK == m_aShadowColor
                    ? nullptr
                    : &m_aShadowColor );

        SwTextPortion::Paint( rInf );
    }
}

SwContentControlPortion::SwContentControlPortion(SwTextContentControl* pTextContentControl)
    : m_pTextContentControl(pTextContentControl)
{
    SetWhichPor(PortionType::ContentControl);
}

bool SwContentControlPortion::DescribePDFControl(const SwTextPaintInfo& rInf) const
{
    auto pPDFExtOutDevData = dynamic_cast<vcl::PDFExtOutDevData*>(rInf.GetOut()->GetExtOutDevData());
    if (!pPDFExtOutDevData)
    {
        return false;
    }

    if (!pPDFExtOutDevData->GetIsExportFormFields())
    {
        return false;
    }

    if (!m_pTextContentControl)
    {
        return false;
    }

    const SwFormatContentControl& rFormatContentControl = m_pTextContentControl->GetContentControl();
    const std::shared_ptr<SwContentControl>& pContentControl = rFormatContentControl.GetContentControl();
    if (!pContentControl)
    {
        return false;
    }

    SwTextNode* pTextNode = pContentControl->GetTextNode();
    SwDoc& rDoc = pTextNode->GetDoc();
    if (rDoc.IsInHeaderFooter(*pTextNode))
    {
        // Form control in header/footer makes no sense, would allow multiple values for the same
        // control.
        return false;
    }

    // Check if this is the first content control portion of this content control.
    sal_Int32 nStart = m_pTextContentControl->GetStart();
    sal_Int32 nEnd = *m_pTextContentControl->GetEnd();
    TextFrameIndex nViewStart = rInf.GetTextFrame()->MapModelToView(pTextNode, nStart);
    TextFrameIndex nViewEnd = rInf.GetTextFrame()->MapModelToView(pTextNode, nEnd);
    // The content control portion starts 1 char after the starting dummy character.
    if (rInf.GetIdx() != nViewStart + TextFrameIndex(1))
    {
        // Ignore: don't process and also don't emit plain text fallback.
        return true;
    }

    const SwPaM aPam(*pTextNode, nEnd, *pTextNode, nStart);
    static sal_Unicode const aForbidden[] = {
        CH_TXTATR_BREAKWORD,
        0
    };
    const OUString aText = comphelper::string::removeAny(aPam.GetText(), aForbidden);

    std::unique_ptr<vcl::PDFWriter::AnyWidget> pDescriptor;
    switch (pContentControl->GetType())
    {
        case SwContentControlType::RICH_TEXT:
        case SwContentControlType::PLAIN_TEXT:
        {
            pDescriptor = std::make_unique<vcl::PDFWriter::EditWidget>();
            auto pEditWidget = static_cast<vcl::PDFWriter::EditWidget*>(pDescriptor.get());
            pEditWidget->MultiLine = true;
            break;
        }
        case SwContentControlType::CHECKBOX:
        {
            pDescriptor = std::make_unique<vcl::PDFWriter::CheckBoxWidget>();
            auto pCheckBoxWidget = static_cast<vcl::PDFWriter::CheckBoxWidget*>(pDescriptor.get());
            pCheckBoxWidget->Checked = pContentControl->GetChecked();
            // If it's checked already, then leave the default "Yes" OnValue unchanged, so the
            // appropriate appearance is found by PDF readers.
            if (!pCheckBoxWidget->Checked)
            {
                pCheckBoxWidget->OnValue = pContentControl->GetCheckedState();
                pCheckBoxWidget->OffValue = pContentControl->GetUncheckedState();
            }
            break;
        }
        case SwContentControlType::DROP_DOWN_LIST:
        {
            pDescriptor = std::make_unique<vcl::PDFWriter::ListBoxWidget>();
            auto pListWidget = static_cast<vcl::PDFWriter::ListBoxWidget*>(pDescriptor.get());
            pListWidget->DropDown = true;
            sal_Int32 nIndex = 0;
            bool bTextFound = false;
            for (const auto& rItem : pContentControl->GetListItems())
            {
                pListWidget->Entries.push_back(rItem.m_aDisplayText);
                if (rItem.m_aDisplayText == aText)
                {
                    pListWidget->SelectedEntries.push_back(nIndex);
                    bTextFound = true;
                }
                ++nIndex;
            }
            if (!aText.isEmpty() && !bTextFound)
            {
                // The selected entry has to be an index, if there is no index for it, insert one at
                // the start.
                pListWidget->Entries.insert(pListWidget->Entries.begin(), aText);
                pListWidget->SelectedEntries.push_back(0);
            }
            break;
        }
        case SwContentControlType::COMBO_BOX:
        {
            pDescriptor = std::make_unique<vcl::PDFWriter::ComboBoxWidget>();
            auto pComboWidget = static_cast<vcl::PDFWriter::ComboBoxWidget*>(pDescriptor.get());
            for (const auto& rItem : pContentControl->GetListItems())
            {
                pComboWidget->Entries.push_back(rItem.m_aDisplayText);
            }
            break;
        }
        case SwContentControlType::DATE:
        {
            pDescriptor = std::make_unique<vcl::PDFWriter::EditWidget>();
            auto pEditWidget = static_cast<vcl::PDFWriter::EditWidget*>(pDescriptor.get());
            pEditWidget->Format = vcl::PDFWriter::Date;
            // GetDateFormat() uses a syntax that works with SvNumberFormatter::PutEntry(), PDF's
            // AFDate_FormatEx() uses a similar syntax, but uses lowercase characters in case of
            // "Y", "M" and "D" at least.
            pEditWidget->DateFormat = pContentControl->GetDateFormat().toAsciiLowerCase();
            break;
        }
        default:
            break;
    }

    if (!pDescriptor)
    {
        return false;
    }

    bool bShrinkPageForPostIts = pPDFExtOutDevData->GetIsExportNotesInMargin()
                                 && sw_GetPostIts(rDoc.getIDocumentFieldsAccess(), nullptr);
    const SwFont* pFont = rInf.GetFont();
    if (pFont)
    {
        pDescriptor->TextFont = pFont->GetActualFont();
        if (bShrinkPageForPostIts)
        {
            // Page area is scaled down so we have space for comments. Scale down the font height
            // for the content of the widgets, too.
            double fScale = SwEnhancedPDFExportHelper::GetSwRectToPDFRectScale();
            pDescriptor->TextFont.SetFontHeight(pDescriptor->TextFont.GetFontHeight() * fScale);
        }

        // Need to transport the color explicitly, so it's applied to both already filled in and
        // future content.
        pDescriptor->TextColor = pFont->GetColor();
    }

    // Description for accessibility purposes.
    if (!pContentControl->GetAlias().isEmpty())
    {
        pDescriptor->Description = pContentControl->GetAlias();
    }

    // Map the text of the content control to the descriptor's text.
    pDescriptor->Text = aText;

    // Calculate the bounding rectangle of this content control, which can be one or more layout
    // portions in one or more lines.
    SwRect aLocation;
    auto pTextFrame = const_cast<SwTextFrame*>(rInf.GetTextFrame());
    SwTextSizeInfo aInf(pTextFrame);
    SwTextCursor aLine(pTextFrame, &aInf);
    SwRect aStartRect, aEndRect;
    aLine.GetCharRect(&aStartRect, nViewStart);
    aLine.GetCharRect(&aEndRect, nViewEnd);

    // Handling RTL text direction
    if(rInf.GetTextFrame()->IsRightToLeft())
    {
        rInf.GetTextFrame()->SwitchLTRtoRTL( aStartRect );
        rInf.GetTextFrame()->SwitchLTRtoRTL( aEndRect );
    }
    // TODO: handle rInf.GetTextFrame()->IsVertical()

    aLocation = aStartRect;
    aLocation.Union(aEndRect);

    // PDF spec 12.5.2 Annotation Dictionaries says the default border with is 1pt wide, increase
    // the rectangle to compensate for that, otherwise the text will be cut off at the end.
    aLocation.AddTop(-20);
    aLocation.AddBottom(20);
    aLocation.AddLeft(-20);
    aLocation.AddRight(20);

    tools::Rectangle aRect = aLocation.SVRect();
    if (bShrinkPageForPostIts)
    {
        // Map the rectangle of the form widget, similar to how it's done for e.g. hyperlinks.
        const SwPageFrame* pPageFrame = pTextFrame->FindPageFrame();
        if (pPageFrame)
        {
            aRect = SwEnhancedPDFExportHelper::MapSwRectToPDFRect(pPageFrame, aRect);
        }
    }
    pDescriptor->Location = aRect;

    pPDFExtOutDevData->WrapBeginStructureElement(vcl::pdf::StructElement::Form);
    pPDFExtOutDevData->CreateControl(*pDescriptor);
    pPDFExtOutDevData->EndStructureElement();

    return true;
}

void SwContentControlPortion::Paint(const SwTextPaintInfo& rInf) const
{
    if (Width())
    {
        rInf.DrawViewOpt(*this, PortionType::ContentControl);

        if (DescribePDFControl(rInf))
        {
            return;
        }

        SwTextPortion::Paint(rInf);
    }
}

namespace sw::mark {
    OUString ExpandFieldmark(Fieldmark* pBM)
    {
        if (pBM->GetFieldname() == ODF_FORMCHECKBOX)
        {
            ::sw::mark::CheckboxFieldmark const*const pCheckboxFm(
                    dynamic_cast<CheckboxFieldmark const*>(pBM));
            assert(pCheckboxFm);
            return pCheckboxFm->IsChecked()
                    ? u"\u2612"_ustr
                    : u"\u2610"_ustr;
        }
        assert(pBM->GetFieldname() == ODF_FORMDROPDOWN);
        const Fieldmark::parameter_map_t* const pParameters = pBM->GetParameters();
        sal_Int32 nCurrentIdx = 0;
        const Fieldmark::parameter_map_t::const_iterator pResult = pParameters->find(ODF_FORMDROPDOWN_RESULT);
        if(pResult != pParameters->end())
            pResult->second >>= nCurrentIdx;

        const Fieldmark::parameter_map_t::const_iterator pListEntries = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY);
        if (pListEntries != pParameters->end())
        {
            uno::Sequence< OUString > vListEntries;
            pListEntries->second >>= vListEntries;
            if (nCurrentIdx < vListEntries.getLength())
                return vListEntries[nCurrentIdx];
        }

        return vEnSpaces;
    }
}

SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const
{
    SwTextPortion *pPor = nullptr;
    if( GetFnt()->IsTox() )
    {
        pPor = new SwToxPortion;
    }
    else if ( GetFnt()->IsInputField() )
    {
        if (rInf.GetOpt().IsFieldName())
        {
            // assume this is only the *first* portion and follows will be created elsewhere => input field must start at Idx
            assert(rInf.GetText()[sal_Int32(rInf.GetIdx())] == CH_TXT_ATR_INPUTFIELDSTART);
            TextFrameIndex nFieldLen(-1);
            for (TextFrameIndex i = rInf.GetIdx() + TextFrameIndex(1); ; ++i)
            {
                assert(rInf.GetText()[sal_Int32(i)] != CH_TXT_ATR_INPUTFIELDSTART); // can't nest
                if (rInf.GetText()[sal_Int32(i)] == CH_TXT_ATR_INPUTFIELDEND)
                {
                    nFieldLen = i + TextFrameIndex(1) - rInf.GetIdx();
                    break;
                }
            }
            assert(2 <= sal_Int32(nFieldLen));
            pPor = new SwFieldPortion(SwFieldType::GetTypeStr(SwFieldTypesEnum::Input), nullptr, nFieldLen);
        }
        else
        {
            pPor = new SwTextInputFieldPortion();
        }
    }
    else
    {
        if( GetFnt()->IsRef() )
            pPor = new SwRefPortion;
        else if (GetFnt()->IsMeta())
        {
            auto pMetaPor = new SwMetaPortion;

            // set custom LO_EXT_SHADING color, if it exists
            SwTextFrame const*const pFrame(rInf.GetTextFrame());
            SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
            SwPaM aPam(aPosition);
            uno::Reference<text::XTextContent> const xRet(
                SwUnoCursorHelper::GetNestedTextContent(
                    *aPam.GetPointNode().GetTextNode(), aPosition.GetContentIndex(), false) );
            if (xRet.is())
            {
                const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc();
                static uno::Reference< uno::XComponentContext > xContext(
                    ::comphelper::getProcessComponentContext());

                static uno::Reference< rdf::XURI > xODF_SHADING(
                    rdf::URI::createKnown(xContext, rdf::URIs::LO_EXT_SHADING), uno::UNO_SET_THROW);

                if (const SwDocShell* pShell = rDoc.GetDocShell())
                {
                    rtl::Reference<SwXTextDocument> xDocumentMetadataAccess(pShell->GetBaseModel());

                    const css::uno::Reference<css::rdf::XResource> xSubject(xRet, uno::UNO_QUERY);
                    const uno::Reference<rdf::XRepository> xRepository =
                        xDocumentMetadataAccess->getRDFRepository();
                    const uno::Reference<container::XEnumeration> xEnum(
                        xRepository->getStatements(xSubject, xODF_SHADING, nullptr), uno::UNO_SET_THROW);

                    while (xEnum->hasMoreElements())
                    {
                        rdf::Statement stmt;
                        if (!(xEnum->nextElement() >>= stmt)) {
                            throw uno::RuntimeException();
                        }
                        const uno::Reference<rdf::XLiteral> xObject(stmt.Object, uno::UNO_QUERY);
                        if (!xObject.is()) continue;
                        if (xEnum->hasMoreElements()) {
                            SAL_INFO("sw.uno""ignoring other odf:shading statements");
                        }
                        Color rColor = Color::STRtoRGB(xObject->getValue());
                        pMetaPor->SetShadowColor(rColor);
                        break;
                    }
                }
            }
            pPor = pMetaPor;
        }
        else if (GetFnt()->IsContentControl())
        {
            SwTextFrame const*const pFrame(rInf.GetTextFrame());
            SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
            SwTextNode* pTextNode = aPosition.GetNode().GetTextNode();
            SwTextContentControl* pTextContentControl = nullptr;
            if (pTextNode)
            {
                sal_Int32 nIndex = aPosition.GetContentIndex();
                if (SwTextAttr* pAttr = pTextNode->GetTextAttrAt(nIndex, RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent))
                {
                    pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr);
                }
            }
            pPor = new SwContentControlPortion(pTextContentControl);
        }
        else
        {
            // Only at the End!
            // If pCurr does not have a width, it can however already have content.
            // E.g. for non-displayable characters

            auto const ch(rInf.GetChar(rInf.GetIdx()));
            SwTextFrame const*const pFrame(rInf.GetTextFrame());
            SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
            sw::mark::Fieldmark *pBM = pFrame->GetDoc().getIDocumentMarkAccess()->getInnerFieldmarkFor(aPosition);
            if(pBM != nullptr && pBM->GetFieldname( ) == ODF_FORMDATE)
            {
                if (ch == CH_TXT_ATR_FIELDSTART)
                    pPor = new SwFieldFormDatePortion(pBM, true);
                else if (ch == CH_TXT_ATR_FIELDSEP)
                    pPor = new SwFieldMarkPortion(); // it's added in DateFieldmark?
                else if (ch == CH_TXT_ATR_FIELDEND)
                    pPor = new SwFieldFormDatePortion(pBM, false);
            }
            else if (ch == CH_TXT_ATR_FIELDSTART)
                pPor = new SwFieldMarkPortion();
            else if (ch == CH_TXT_ATR_FIELDSEP)
                pPor = new SwFieldMarkPortion();
            else if (ch == CH_TXT_ATR_FIELDEND)
                pPor = new SwFieldMarkPortion();
            else if (ch == CH_TXT_ATR_FORMELEMENT)
            {
                OSL_ENSURE(pBM != nullptr, "Where is my form field bookmark???");
                if (pBM != nullptr)
                {
                    if (pBM->GetFieldname( ) == ODF_FORMCHECKBOX)
                    {
                        pPor = new SwFieldFormCheckboxPortion();
                    }
                    else if (pBM->GetFieldname( ) == ODF_FORMDROPDOWN)
                    {
                        pPor = new SwFieldFormDropDownPortion(pBM, sw::mark::ExpandFieldmark(pBM));
                    }
                    /* we need to check for ODF_FORMTEXT for scenario having FormFields inside FORMTEXT.
                     * Otherwise file will crash on open.
                     */

                    else if (pBM->GetFieldname( ) == ODF_FORMTEXT)
                    {
                        pPor = new SwFieldMarkPortion();
                    }
                }
            }
            if( !pPor )
            {
                if( !rInf.X() && !m_pCurr->GetNextPortion() && !m_pCurr->GetLen()  && !GetFnt()->IsURL() )
                    pPor = m_pCurr;
                else
                {
                    pPor = new SwTextPortion;
                    if (pBM && pBM->GetFieldname() == ODF_FORMTEXT)
                        pPor->SetFieldmarkText(true);
                }
            }
        }
    }
    return pPor;
}

// We calculate the length, the following portion limits are defined:
// 1) Tabs
// 2) Linebreaks
// 3) CH_TXTATR_BREAKWORD / CH_TXTATR_INWORD
// 4) next attribute change

SwTextPortion *SwTextFormatter::NewTextPortion( SwTextFormatInfo &rInf )
{
    // If we're at the line's beginning, we take pCurr
    // If pCurr is not derived from SwTextPortion, we need to duplicate
    Seek( rInf.GetIdx() );
    SwTextPortion *pPor = WhichTextPor( rInf );

    TextFrameIndex nNextChg(rInf.GetText().getLength());

    // until next attribute change:
    const TextFrameIndex nNextAttr = GetNextAttr();
    // until next layout-breaking attribute change:
    const TextFrameIndex nNextLayoutBreakAttr = GetNextLayoutBreakAttr();
    // end of script type:
    const TextFrameIndex nNextScript = m_pScriptInfo->NextScriptChg(rInf.GetIdx());
    // end of direction:
    const TextFrameIndex nNextDir = m_pScriptInfo->NextDirChg(rInf.GetIdx());
    // hidden change (potentially via bookmark):
    const TextFrameIndex nNextHidden = m_pScriptInfo->NextHiddenChg(rInf.GetIdx());
    // bookmarks
    const TextFrameIndex nNextBookmark = m_pScriptInfo->NextBookmark(rInf.GetIdx());

    auto nNextContext = std::min({ nNextChg, nNextLayoutBreakAttr, nNextScript, nNextDir });
    nNextChg = std::min({ nNextChg, nNextAttr, nNextScript, nNextDir, nNextHidden, nNextBookmark });

    // Turbo boost:
    // We assume that font characters are not larger than twice
    // as wide as height.
    // Very crazy: we need to take the ascent into account.

    // Mind the trap! GetSize() contains the wished-for height, the real height
    // is only known in CalcAscent!

    // The ratio is even crazier: a blank in Times New Roman has an ascent of
    // 182, a height of 200 and a width of 53!
    // It follows that a line with a lot of blanks is processed incorrectly.
    // Therefore we increase from factor 2 to 8 (due to negative kerning).

    pPor->SetLen(TextFrameIndex(1));
    CalcAscent( rInf, pPor );

    const SwFont* pTmpFnt = rInf.GetFont();
    auto nCharWidthGuess = std::min(pTmpFnt->GetHeight(), pPor->GetAscent()) / 8;
    if (!nCharWidthGuess)
        nCharWidthGuess = 1;
    auto nExpect = rInf.GetIdx() + TextFrameIndex(rInf.GetLineWidth() / nCharWidthGuess);
    if (nExpect > rInf.GetIdx())
    {
        nNextChg = std::min(nNextChg, nExpect);
        nNextContext = std::min(nNextContext, nExpect);
    }

    // we keep an invariant during method calls:
    // there are no portion ending characters like hard spaces
    // or tabs in [ nLeftScanIdx, nRightScanIdx ]
    if ( m_nLeftScanIdx <= rInf.GetIdx() && rInf.GetIdx() <= m_nRightScanIdx )
    {
        if ( nNextChg > m_nRightScanIdx )
            nNextChg = m_nRightScanIdx =
                rInf.ScanPortionEnd( m_nRightScanIdx, nNextChg );
    }
    else
    {
        m_nLeftScanIdx = rInf.GetIdx();
        nNextChg = m_nRightScanIdx =
                rInf.ScanPortionEnd( rInf.GetIdx(), nNextChg );
    }

    pPor->SetLen( nNextChg - rInf.GetIdx() );
    rInf.SetLen( pPor->GetLen() );

    // Generate a new layout context for the text portion. This is necessary
    // for the first text portion in a paragraph, or for any successive
    // portions that are outside of the bounds of the previous context.
    if (!rInf.GetLayoutContext().has_value()
        || rInf.GetLayoutContext()->m_nBegin < rInf.GetLineStart().get()
        || rInf.GetLayoutContext()->m_nEnd < nNextChg.get())
    {
        // The layout context must terminate at special characters
        sal_Int32 nEnd = rInf.GetIdx().get();
        for (; nEnd < nNextContext.get(); ++nEnd)
        {
            bool bAtEnd = false;
            switch (rInf.GetText()[nEnd])
            {
                case CH_TXTATR_BREAKWORD:
                case CH_TXTATR_INWORD:
                case CH_TXTATR_TAB:
                case CH_TXTATR_NEWLINE:
                case CH_TXT_ATR_INPUTFIELDSTART:
                case CH_TXT_ATR_INPUTFIELDEND:
                case CH_TXT_ATR_FORMELEMENT:
                case CH_TXT_ATR_FIELDSTART:
                case CH_TXT_ATR_FIELDSEP:
                case CH_TXT_ATR_FIELDEND:
                case CHAR_SOFTHYPHEN:
                    bAtEnd = true;
                    break;

                default:
                    break;
            }

            if (bAtEnd)
            {
                break;
            }
        }

        std::optional<SwLinePortionLayoutContext> nNewContext;
        if (rInf.GetIdx().get() != nEnd)
        {
            nNewContext = SwLinePortionLayoutContext{ rInf.GetIdx().get(), nEnd };
        }

        rInf.SetLayoutContext(nNewContext);
    }

    pPor->SetLayoutContext(rInf.GetLayoutContext());

    return pPor;
}

// first portions have no length
SwLinePortion *SwTextFormatter::WhichFirstPortion(SwTextFormatInfo &rInf)
{
    SwLinePortion *pPor = nullptr;

    if( rInf.GetRest() )
    {
        // Tabs and fields
        if'\0' != rInf.GetHookChar() )
            return nullptr;

        pPor = rInf.GetRest();
        if( pPor->IsErgoSumPortion() )
            rInf.SetErgoDone(true);
        else
            if( pPor->IsFootnoteNumPortion() )
                rInf.SetFootnoteDone(true);
            else
                if( pPor->InNumberGrp() )
                    rInf.SetNumDone(true);

        rInf.SetRest(nullptr);
        m_pCurr->SetRest( true );
        return pPor;
    }

    // We can stand in the follow, it's crucial that
    // pFrame->GetOffset() == 0!
    if( rInf.GetIdx() )
    {
        // We now too can elongate FootnotePortions and ErgoSumPortions

        // 1. The ErgoSumTexts
        if( !rInf.IsErgoDone() )
        {
            if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() )
                pPor = NewErgoSumPortion( rInf );
            rInf.SetErgoDone( true );
        }

        // 2. Arrow portions
        if( !pPor && !rInf.IsArrowDone() )
        {
            if( m_pFrame->GetOffset() && !m_pFrame->IsFollow() &&
                rInf.GetIdx() == m_pFrame->GetOffset() )
                pPor = new SwArrowPortion( *m_pCurr );
            rInf.SetArrowDone( true );
        }

        // 3. Kerning portions at beginning of line in grid mode
        if ( ! pPor && ! m_pCurr->GetNextPortion() )
        {
            SwTextGridItem const*const pGrid(
                    GetGridItem(GetTextFrame()->FindPageFrame()));
            if ( pGrid )
                pPor = new SwKernPortion( *m_pCurr );
        }

        // 4. The line rests (multiline fields)
        if( !pPor )
        {
            pPor = rInf.GetRest();
            // Only for pPor of course
            if( pPor )
            {
                m_pCurr->SetRest( true );
                rInf.SetRest(nullptr);
            }
        }
    }
    else
    {
        // 5. The foot note count
        if( !rInf.IsFootnoteDone() )
        {
            OSL_ENSURE( ( ! rInf.IsMulti() && ! m_pMulti ) || m_pMulti->HasRotation(),
                     "Rotated number portion trouble" );

            const bool bFootnoteNum = m_pFrame->IsFootnoteNumFrame();
            rInf.GetParaPortion()->SetFootnoteNum( bFootnoteNum );
            if( bFootnoteNum )
                pPor = NewFootnoteNumPortion( rInf );
            rInf.SetFootnoteDone( true );
        }

        // 6. The ErgoSumTexts of course also exist in the TextMaster,
        // it's crucial whether the SwFootnoteFrame is aFollow
        if( !rInf.IsErgoDone() && !pPor && ! rInf.IsMulti() )
        {
            if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() )
                pPor = NewErgoSumPortion( rInf );
            rInf.SetErgoDone( true );
        }

        // 7. The numbering
        if( !rInf.IsNumDone() && !pPor )
        {
            OSL_ENSURE( ( ! rInf.IsMulti() && ! m_pMulti ) || m_pMulti->HasRotation(),
                     "Rotated number portion trouble" );

            // If we're in the follow, then of course not
            if (GetTextFrame()->GetTextNodeForParaProps()->GetNumRule())
                pPor = NewNumberPortion( rInf );
            rInf.SetNumDone( true );
        }
        // 8. The DropCaps
        if( !pPor && GetDropFormat() && ! rInf.IsMulti() )
            pPor = NewDropPortion( rInf );

        // 9. Kerning portions at beginning of line in grid mode
        if ( !pPor && !m_pCurr->GetNextPortion() )
        {
            SwTextGridItem const*const pGrid(
                    GetGridItem(GetTextFrame()->FindPageFrame()));
            if ( pGrid )
                pPor = new SwKernPortion( *m_pCurr );
        }
    }

    // 10. Decimal tab portion at the beginning of each line in table cells
    if ( !pPor && !m_pCurr->GetNextPortion() &&
             GetTextFrame()->IsInTab() &&
             GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT))
    {
        pPor = NewTabPortion( rInf, true );
    }

    // 11. suffix of meta-field
    if (!pPor)
    {
        pPor = TryNewNoLengthPortion(rInf);
    }

    // 12. bookmarks
    // check this *last* so that BuildMultiPortion() can find it!
    if (!pPor && rInf.CheckCurrentPosBookmark())
    {
        const auto bookmark = m_pScriptInfo->GetBookmarks(rInf.GetIdx());
        if (!bookmark.empty())
        {
            // only for character width, maybe replaced with ] later
            sal_Unicode mark = '[';

            pPor = new SwBookmarkPortion(mark, bookmark);
        }
    }

    return pPor;
}

static bool lcl_OldFieldRest( const SwLineLayout* pCurr )
{
    if( !pCurr->GetNext() )
        return false;
    const SwLinePortion *pPor = pCurr->GetNext()->GetNextPortion();
    bool bRet = false;
    while( pPor && !bRet )
    {
        bRet = (pPor->InFieldGrp() && static_cast<const SwFieldPortion*>(pPor)->IsFollow()) ||
            (pPor->IsMultiPortion() && static_cast<const SwMultiPortion*>(pPor)->IsFollowField());
        if( !pPor->GetLen() )
            break;
        pPor = pPor->GetNextPortion();
    }
    return bRet;
}

/* NewPortion sets rInf.nLen
 * A SwTextPortion is limited by a tab, break, txtatr or attr change
 * We can have three cases:
 * 1) The line is full and the wrap was not emulated
 *    -> return 0;
 * 2) The line is full and a wrap was emulated
 *    -> Reset width and return new FlyPortion
 * 3) We need to construct a new portion
 *    -> CalcFlyWidth emulates the width and return portion, if needed
 */


SwLinePortion *SwTextFormatter::NewPortion(SwTextFormatInfo &rInf,
--> --------------------

--> maximum size reached

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

Messung V0.5
C=90 H=96 G=93

¤ Dauer der Verarbeitung: 0.64 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.