/* -*- 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 <fmtfld.hxx>
#include <txtfld.hxx>
#include <charfmt.hxx>
#include <fmtautofmt.hxx>

#include <viewsh.hxx>
#include <doc.hxx>
#include <rootfrm.hxx>
#include <pagefrm.hxx>
#include <ndtxt.hxx>
#include <fldbas.hxx>
#include <viewopt.hxx>
#include <flyfrm.hxx>
#include <viewimp.hxx>
#include <swfont.hxx>
#include <swmodule.hxx>
#include "porfld.hxx"
#include "porftn.hxx"
#include "porref.hxx"
#include "portox.hxx"
#include "porfly.hxx"
#include "itrform2.hxx"
#include <chpfld.hxx>
#include <dbfld.hxx>
#include <expfld.hxx>
#include <docufld.hxx>
#include <pagedesc.hxx>
#include <fmtmeta.hxx>
#include <reffld.hxx>
#include <flddat.hxx>
#include <IDocumentSettingAccess.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <redline.hxx>
#include <sfx2/docfile.hxx>
#include <svl/grabbagitem.hxx>
#include <svl/itemiter.hxx>
#include <svl/whiter.hxx>
#include <editeng/cmapitem.hxx>
#include <editeng/colritem.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/crossedoutitem.hxx>
#include <officecfg/Office/Writer.hxx>

static bool lcl_IsInBody( SwFrame const *pFrame )
{
    if ( pFrame->IsInDocBody() )
        return true;

    while (const SwFlyFrame* pFly = pFrame->FindFlyFrame())
        pFrame = pFly->GetAnchorFrame();
    return pFrame->IsInDocBody();
}

static OUString ExpandField(const SwField& rField, const SwTextFormatter& rFormatter,
                            const SwTextFormatInfo& rInf)
{
    if (rInf.GetOpt().IsFieldName())
        return rField.GetFieldName();

    const SwViewShell* pSh = rInf.GetVsh();
    const SwDoc* pDoc(pSh ? pSh->GetDoc() : nullptr);
    const bool bInClipboard(!pDoc || pDoc->IsClipBoard());
    return rField.ExpandField(bInClipboard, rFormatter.GetTextFrame()->getRootFrame());
}

SwExpandPortion *SwTextFormatter::NewFieldPortion( SwTextFormatInfo &rInf,
                                                const SwTextAttr *pHint ) const
{
    SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField());
    const bool bName = rInf.GetOpt().IsFieldName();

    // set language
    const_cast<SwTextFormatter*>(this)->SeekAndChg( rInf );
    if (pField->GetLanguage() != GetFnt()->GetLanguage())
        pField->SetLanguage( GetFnt()->GetLanguage() );

    SwViewShell *pSh = rInf.GetVsh();

    switch (pField->GetTyp()->Which())
    {
        case SwFieldIds::Script:
        case SwFieldIds::Postit:
            return new SwPostItsPortion(SwFieldIds::Script == pField->GetTyp()->Which());
        case SwFieldIds::CombinedChars:
            if (!bName)
                return new SwCombinedPortion(ExpandField(*pField, *this, rInf));
            break;
        case SwFieldIds::HiddenText:
            return new SwHiddenPortion(ExpandField(*pField, *this, rInf));
        case SwFieldIds::Chapter:
            if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields())
            {
                static_cast<SwChapterField*>(pField)->ChangeExpansion(
                    *m_pFrame, &static_txtattr_cast<SwTextField const*>(pHint)->GetTextNode());
            }
            break;
        case SwFieldIds::DocStat:
            if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields())
            {
                SwDocStatField* pDocStatField = static_cast<SwDocStatField*>(pField);
                sal_uInt16 nVirtPageCount = 0;
                if (pDocStatField->GetSubType() == SwDocStatSubType::PageRange)
                    nVirtPageCount = m_pFrame->GetVirtPageCount();
                pDocStatField->ChangeExpansion(m_pFrame, nVirtPageCount);
            }
            break;
        case SwFieldIds::PageNumber:
            if (!bName && pSh && pSh->GetLayout() && !pSh->Imp()->IsUpdateExpFields())
            {
                auto pPageNr = static_cast<SwPageNumberFieldType*>(pField->GetTyp());

                const SwRootFrame* pTmpRootFrame = pSh->GetLayout();
                const bool bVirt = pTmpRootFrame->IsVirtPageNum();

                sal_uInt16 nVirtNum = m_pFrame->GetVirtPageNum();
                sal_uInt16 nNumPages = pTmpRootFrame->GetPageNum();
                auto pPageNumberField = static_cast<SwPageNumberField*>(pField);
                SvxNumType nNumFormat = SvxNumType(-1);
                if (SVX_NUM_PAGEDESC == pPageNumberField->GetFormat())
                    nNumFormat
                        = m_pFrame->FindPageFrame()->GetPageDesc()->GetNumType().GetNumberingType();
                pPageNumberField->ChangeExpansion(nVirtNum, nNumPages);
                pPageNr->ChangeExpansion(pSh->GetDoc(), bVirt,
                                         nNumFormat != SvxNumType(-1) ? &nNumFormat : nullptr);
            }
            break;
        case SwFieldIds::GetExp:
            if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields())
            {
                auto pExpField = static_cast<SwGetExpField*>(pField);
                if (!::lcl_IsInBody(m_pFrame))
                {
                    pExpField->ChgBodyTextFlag(false);
                    pExpField->ChangeExpansion(*m_pFrame,
                                               *static_txtattr_cast<SwTextField const*>(pHint));
                }
                else if (!pExpField->IsInBodyText())
                {
                    // Was something else previously, thus: expand first, then convert it!
                    pExpField->ChangeExpansion(*m_pFrame,
                                               *static_txtattr_cast<SwTextField const*>(pHint));
                    pExpField->ChgBodyTextFlag(true);
                }
            }
            break;
        case SwFieldIds::Database:
            if (!bName)
            {
                static_cast<SwDBField*>(pField)->ChgBodyTextFlag(::lcl_IsInBody(m_pFrame));
            }
            break;
        case SwFieldIds::RefPageGet:
            if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields())
            {
                static_cast<SwRefPageGetField*>(pField)->ChangeExpansion(
                    *m_pFrame, static_txtattr_cast<SwTextField const*>(pHint));
            }
            break;
        case SwFieldIds::JumpEdit:
        {
            auto pJumpEditField = static_cast<SwJumpEditField*>(pField);
            std::unique_ptr<SwFont> pFont;
            if (!bName)
            {
                pFont = std::make_unique<SwFont>(*m_pFont);
                pFont->SetDiffFnt(
                    &pJumpEditField->GetCharFormat()->GetAttrSet(),
                    &m_pFrame->GetDoc().getIDocumentSettingAccess());
            }
            return new SwJumpFieldPortion(ExpandField(*pField, *this, rInf), pField->GetPar2(),
                                          std::move(pFont), pJumpEditField->GetFormat());
        }
        case SwFieldIds::GetRef:
            if (!bName)
            {
                auto pGetRef = static_cast<SwGetRefField*>(pField);
                if (pGetRef->GetSubType() == ReferencesSubtype::Style)
                    pGetRef->UpdateField(static_txtattr_cast<SwTextField const*>(pHint), m_pFrame);
            }
            break;
        default:
            break;
    }
    return new SwFieldPortion(ExpandField(*pField, *this, rInf));
}

static SwFieldPortion * lcl_NewMetaPortion(SwTextAttr & rHint, const bool bPrefix)
{
    ::sw::Meta *const pMeta(
        static_cast<SwFormatMeta &>(rHint.GetAttr()).GetMeta() );
    OUString fix;
    ::sw::MetaField *const pField( dynamic_cast< ::sw::MetaField * >(pMeta) );
    OSL_ENSURE(pField, "lcl_NewMetaPortion: no meta field?");
    if (pField)
    {
        OUString color;
        pField->GetPrefixAndSuffix(bPrefix ? &fix : nullptr, bPrefix ? nullptr : &fix, &color);
    }
    return new SwFieldPortion( fix );
}

/**
 * Try to create a new portion with zero length, for an end of a hint
 * (where there is no CH_TXTATR). Because there may be multiple hint ends at a
 * given index, m_pByEndIter is used to keep track of the already created
 * portions. But the portions created here may actually be deleted again,
 * due to Underflow. In that case, m_pByEndIter must be decremented,
 * so the portion will be created again on the next line.
 */
SwExpandPortion * SwTextFormatter::TryNewNoLengthPortion(SwTextFormatInfo const & rInfo)
{
    const TextFrameIndex nIdx(rInfo.GetIdx());

    // sw_redlinehide: because there is a dummy character at the start of these
    // hints, it's impossible to have ends of hints from different nodes at the
    // same view position, so it's sufficient to check the hints of the current
    // node.  However, m_pByEndIter exists for the whole text frame, so
    // it's necessary to iterate all hints for that purpose...
    if (!m_pByEndIter)
    {
        m_pByEndIter.reset(new sw::MergedAttrIterByEnd(*rInfo.GetTextFrame()));
    }
    SwTextNode const* pNode(nullptr);
    for (SwTextAttr const* pHint = m_pByEndIter->NextAttr(pNode); pHint;
         pHint = m_pByEndIter->NextAttr(pNode))
    {
        SwTextAttr & rHint(const_cast<SwTextAttr&>(*pHint));
        TextFrameIndex const nEnd(
            rInfo.GetTextFrame()->MapModelToView(pNode, rHint.GetAnyEnd()));
        if (nEnd > nIdx)
        {
            m_pByEndIter->PrevAttr();
            break;
        }
        if (nEnd == nIdx)
        {
            if (RES_TXTATR_METAFIELD == rHint.Which())
            {
                SwFieldPortion *const pPortion(
                        lcl_NewMetaPortion(rHint, false));
                pPortion->SetNoLength(); // no CH_TXTATR at hint end!
                return pPortion;
            }
        }
    }
    return nullptr;
}

SwLinePortion *SwTextFormatter::NewExtraPortion( SwTextFormatInfo &rInf )
{
    SwTextAttr *pHint = GetAttr( rInf.GetIdx() );
    SwLinePortion *pRet = nullptr;
    if( !pHint )
    {
        pRet = new SwTextPortion;
        pRet->SetLen(TextFrameIndex(1));
        rInf.SetLen(TextFrameIndex(1));
        return pRet;
    }

    switch( pHint->Which() )
    {
    case RES_TXTATR_FLYCNT :
        {
            pRet = NewFlyCntPortion( rInf, pHint );
            break;
        }
    case RES_TXTATR_FTN :
        {
            pRet = NewFootnotePortion( rInf, pHint );
            break;
        }
    case RES_TXTATR_FIELD :
    case RES_TXTATR_ANNOTATION :
        {
            pRet = NewFieldPortion( rInf, pHint );
            break;
        }
    case RES_TXTATR_REFMARK :
        {
            pRet = new SwIsoRefPortion;
            break;
        }
    case RES_TXTATR_TOXMARK :
        {
            pRet = new SwIsoToxPortion;
            break;
        }
    case RES_TXTATR_METAFIELD:
        {
            pRet = lcl_NewMetaPortion( *pHint, true );
            break;
        }
    default: ;
    }
    if( !pRet )
    {
        auto pFieldPortion = new SwFieldPortion( u""_ustr );
        if (pHint->Which() == RES_TXTATR_CONTENTCONTROL)
        {
            pFieldPortion->SetContentControl(true);
        }
        pRet = pFieldPortion;
        rInf.SetLen(TextFrameIndex(1));
    }
    return pRet;
}

/**
 * OOXML spec says that w:rPr inside w:pPr specifies formatting for the paragraph mark symbol (i.e. the control
 * character than can be configured to be shown). However, in practice MSO also uses it as direct formatting
 * for numbering in that paragraph. I don't know if the problem is in the spec or in MSWord.
 */
static void checkApplyParagraphMarkFormatToNumbering(SwFont* pNumFnt, SwTextFormatInfo& rInf,
                                                     const IDocumentSettingAccess* pIDSA,
                                                     const SwAttrSet* pFormat)
{
    if( !pIDSA->get(DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING ))
        return;

    SwFormatAutoFormat const& rListAutoFormat(rInf.GetTextFrame()->GetTextNodeForParaProps()->GetAttr(RES_PARATR_LIST_AUTOFMT));
    std::shared_ptr<SfxItemSet> pSet(rListAutoFormat.GetStyleHandle());

    // Check each item and in case it should be ignored, then clear it.
    if (!pSet)
        return;

    std::unique_ptr<SfxItemSet> const pCleanedSet = pSet->Clone();

    if (pCleanedSet->HasItem(RES_TXTATR_CHARFMT))
    {
        // Insert attributes of referenced char format into current set
        const SwFormatCharFormat& rCharFormat = pCleanedSet->Get(RES_TXTATR_CHARFMT);
        const SwAttrSet& rStyleAttrs = rCharFormat.GetCharFormat()->GetAttrSet();
        SfxWhichIter aIter(rStyleAttrs);
        sal_uInt16 nWhich = aIter.FirstWhich();
        while (nWhich)
        {
            const SfxPoolItem* pItem = nullptr;
            if (!SwTextNode::IsIgnoredCharFormatForNumbering(nWhich, /*bIsCharStyle=*/true)
                && !pCleanedSet->HasItem(nWhich)
                && !(pFormat && pFormat->HasItem(nWhich))
                && rStyleAttrs.GetItemState(nWhich, true, &pItem) > SfxItemState::DEFAULT)
            {
                // Copy from parent sets only allowed items which will not overwrite
                // values explicitly defined in current set (pCleanedSet) or in pFormat
                if (pItem)
                    pCleanedSet->Put(*pItem);
            }
            nWhich = aIter.NextWhich();
        }

        // It is not required here anymore, all referenced items are inserted
        pCleanedSet->ClearItem(RES_TXTATR_CHARFMT);
    };

    SfxItemIter aIter(*pSet);
    const SfxPoolItem* pItem = aIter.GetCurItem();
    while (pItem)
    {
        if (SwTextNode::IsIgnoredCharFormatForNumbering(pItem->Which()))
            pCleanedSet->ClearItem(pItem->Which());
        else if (pFormat && pFormat->HasItem(pItem->Which()))
            pCleanedSet->ClearItem(pItem->Which());
        else if (pItem->Which() == RES_CHRATR_BACKGROUND)
        {
            bool bShadingWasImported = false;
            // If Shading was imported, it should not be converted to a Highlight,
            // but remain as Shading which is ignored for numbering.
            if (pCleanedSet->HasItem(RES_CHRATR_GRABBAG))
            {
                SfxGrabBagItem aGrabBag = pCleanedSet->Get(RES_CHRATR_GRABBAG, /*bSrchInParent=*/false);
                const std::map<OUString, css::uno::Any>& rMap = aGrabBag.GetGrabBag();
                auto aIterator = rMap.find(u"CharShadingMarker"_ustr);
                if (aIterator != rMap.end())
                    aIterator->second >>= bShadingWasImported;
            }

            // If used, BACKGROUND is converted to HIGHLIGHT. So also ignore if a highlight already exists.
            if (bShadingWasImported
                || pCleanedSet->HasItem(RES_CHRATR_HIGHLIGHT)
                || (pFormat && pFormat->HasItem(RES_CHRATR_HIGHLIGHT)))
            {
                pCleanedSet->ClearItem(pItem->Which());
            }
        }
        else if (pItem->Which() == RES_CHRATR_CASEMAP)
        {
            SvxCaseMap eCaseMap = static_cast<const SvxCaseMapItem*>(pItem)->GetCaseMap();
            // MS only knows about "all caps" and "small caps". Small caps is not set on numbering
            if (eCaseMap == SvxCaseMap::SmallCaps)
                pCleanedSet->ClearItem(pItem->Which());
        }
        pItem = aIter.NextItem();
    };

    // SetDiffFnt resets the background color (why?), so capture it and re-apply if it had a value,
    // because an existing value should override anything inherited from the paragraph marker.
    const std::optional<Color> oFontBackColor = pNumFnt->GetBackColor();
    // The same is true for the highlight color.
    const Color aHighlight = pNumFnt->GetHighlightColor();

    pNumFnt->SetDiffFnt(pCleanedSet.get(), pIDSA);

    if (oFontBackColor)
        pNumFnt->SetBackColor(oFontBackColor);
    if (aHighlight != COL_TRANSPARENT && !pCleanedSet->HasItem(RES_CHRATR_HIGHLIGHT))
        pNumFnt->SetHighlightColor(aHighlight);
}

static const SwRangeRedline* lcl_GetRedlineAtNodeInsertionOrDeletion( const SwTextNode& rTextNode,
        bool& bIsMoved )
{
    const SwDoc& rDoc = rTextNode.GetDoc();
    SwRedlineTable::size_type nRedlPos = rDoc.getIDocumentRedlineAccess().GetRedlinePos( rTextNode, RedlineType::Any );

    if( SwRedlineTable::npos != nRedlPos )
    {
        const SwNodeOffset nNdIdx = rTextNode.GetIndex();
        const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
        for( ; nRedlPos < rTable.size() ; ++nRedlPos )
        {
            const SwRangeRedline* pTmp = rTable[ nRedlPos ];
            SwNodeOffset nStart = pTmp->GetPoint()->GetNodeIndex(),
                         nEnd = pTmp->GetMark()->GetNodeIndex();
            if( nStart > nEnd )
                std::swap(nStart, nEnd);
            if( RedlineType::Delete == pTmp->GetType() ||
                RedlineType::Insert == pTmp->GetType() )
            {
                if( nStart <= nNdIdx && nEnd > nNdIdx )
                {
                    bIsMoved = pTmp->IsMoved();
                    return pTmp;
                }
            }
            if( nStart > nNdIdx )
                break;
        }
    }
    return nullptr;
}

static bool lcl_setRedlineAttr( SwTextFormatInfo &rInf, const SwTextNode& rTextNode, const std::unique_ptr<SwFont>& pNumFnt )
{
    if ( rInf.GetVsh()->GetLayout()->IsHideRedlines() )
        return false;

    bool bIsMoved;
    const SwRangeRedline* pRedlineNum = lcl_GetRedlineAtNodeInsertionOrDeletion( rTextNode, bIsMoved );
    if (!pRedlineNum)
        return false;

    // moved text: dark green with double underline or strikethrough
    bool bDisplayMovedTextInGreen = officecfg::Office::Writer::Comparison::DisplayMovedTextInGreen::get();
    if ( bDisplayMovedTextInGreen && bIsMoved )
    {
        pNumFnt->SetColor(COL_GREEN);
        if ( RedlineType::Delete == pRedlineNum->GetType() )
            pNumFnt->SetStrikeout(STRIKEOUT_DOUBLE);
        else
            pNumFnt->SetUnderline(LINESTYLE_DOUBLE);
        return true;
    }

    SwAttrPool& rPool = rInf.GetVsh()->GetDoc()->GetAttrPool();
    SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END-1> aSet(rPool);

    std::size_t aAuthor = (1 < pRedlineNum->GetStackCount())
            ? pRedlineNum->GetAuthor( 1 )
            : pRedlineNum->GetAuthor();

    if ( RedlineType::Delete == pRedlineNum->GetType() )
        SwModule::get()->GetDeletedAuthorAttr(aAuthor, aSet, SwRedlineRenderMode::Standard);
    else
        SwModule::get()->GetInsertAuthorAttr(aAuthor, aSet, SwRedlineRenderMode::Standard);

    if (const SvxColorItem* pItem = aSet.GetItemIfSet(RES_CHRATR_COLOR))
        pNumFnt->SetColor(pItem->GetValue());
    if (const SvxUnderlineItem* pItem = aSet.GetItemIfSet(RES_CHRATR_UNDERLINE))
        pNumFnt->SetUnderline(pItem->GetLineStyle());
    if (const SvxCrossedOutItem* pItem = aSet.GetItemIfSet(RES_CHRATR_CROSSEDOUT))
        pNumFnt->SetStrikeout( pItem->GetStrikeout() );

    return true;
}

SwNumberPortion *SwTextFormatter::NewNumberPortion( SwTextFormatInfo &rInf ) const
{
    if( rInf.IsNumDone() || rInf.GetTextStart() != m_nStart
                || rInf.GetTextStart() != rInf.GetIdx() )
        return nullptr;

    SwNumberPortion *pRet = nullptr;
    // sw_redlinehide: at this point it's certain that pTextNd is the node with
    // the numbering of the frame; only the actual number-vector (GetNumString)
    // depends on the hide-mode in the layout so other calls don't need to care
    const SwTextNode *const pTextNd = GetTextFrame()->GetTextNodeForParaProps();
    const SwNumRule* pNumRule = pTextNd->GetNumRule();

    // Has a "valid" number?
    // sw_redlinehide: check that pParaPropsNode is the correct one
    assert(pTextNd->IsNumbered(m_pFrame->getRootFrame()) == pTextNd->IsNumbered(nullptr));
    if (pTextNd->IsNumbered(m_pFrame->getRootFrame()) && pTextNd->IsCountedInList())
    {
        int nLevel = pTextNd->GetActualListLevel();

        if (nLevel < 0)
            nLevel = 0;

        if (nLevel >= MAXLEVEL)
            nLevel = MAXLEVEL - 1;

        const SwNumFormat &rNumFormat = pNumRule->Get( nLevel );
        const bool bLeft = SvxAdjust::Left == rNumFormat.GetNumAdjust();
        const bool bCenter = SvxAdjust::Center == rNumFormat.GetNumAdjust();
        const bool bLabelAlignmentPosAndSpaceModeActive(
                rNumFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT );
        const sal_uInt16 nMinDist = bLabelAlignmentPosAndSpaceModeActive
                                ? 0 : rNumFormat.GetCharTextDistance();

        if( SVX_NUM_BITMAP == rNumFormat.GetNumberingType() )
        {
            OUString referer;
            if (auto const sh1 = rInf.GetVsh()) {
                if (auto const doc = sh1->GetDoc()) {
                    auto const sh2 = doc->GetPersist();
                    if (sh2 != nullptr && sh2->HasName()) {
                        referer = sh2->GetMedium()->GetName();
                    }
                }
            }
            pRet = new SwGrfNumPortion( pTextNd->GetLabelFollowedBy(),
                                        rNumFormat.GetBrush(), referer,
                                        rNumFormat.GetGraphicOrientation(),
                                        rNumFormat.GetGraphicSize(),
                                        bLeft, bCenter, nMinDist,
                                        bLabelAlignmentPosAndSpaceModeActive );
            tools::Long nTmpA = rInf.GetLast()->GetAscent();
            tools::Long nTmpD = rInf.GetLast()->Height() - nTmpA;
            if( !rInf.IsTest() )
                static_cast<SwGrfNumPortion*>(pRet)->SetBase( nTmpA, nTmpD, nTmpA, nTmpD );
        }
        else
        {
            // The SwFont is created dynamically and passed in the ctor,
            // as the CharFormat only returns an SV-Font.
            // In the dtor of SwNumberPortion, the SwFont is deleted.
            const SwAttrSet* pFormat = rNumFormat.GetCharFormat() ?
                                    &rNumFormat.GetCharFormat()->GetAttrSet() :
                                    nullptr;
            const IDocumentSettingAccess* pIDSA = pTextNd->getIDocumentSettingAccess();

            if( SVX_NUM_CHAR_SPECIAL == rNumFormat.GetNumberingType() )
            {
                const std::optional<vcl::Font> pFormatFnt = rNumFormat.GetBulletFont();

                // Build a new bullet font basing on the current paragraph font:
                std::unique_ptr<SwFont> pNumFnt(new SwFont( &rInf.GetCharAttr(), pIDSA ));

                // #i53199#
                if ( !pIDSA->get(DocumentSettingId::DO_NOT_RESET_PARA_ATTRS_FOR_NUM_FONT) )
                {
                    // i18463:
                    // Underline style of paragraph font should not be considered
                    // Overline style of paragraph font should not be considered
                    // Weight style of paragraph font should not be considered
                    // Posture style of paragraph font should not be considered
                    pNumFnt->SetUnderline( LINESTYLE_NONE );
                    pNumFnt->SetOverline( LINESTYLE_NONE );
                    pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::Latin );
                    pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::CJK );
                    pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::CTL );
                    pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::Latin );
                    pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CJK );
                    pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CTL );
                }

                // Apply the explicit attributes from the character style
                // associated with the numbering to the new bullet font.
                if( pFormat )
                    pNumFnt->SetDiffFnt( pFormat, pIDSA );

                checkApplyParagraphMarkFormatToNumbering(pNumFnt.get(), rInf, pIDSA, pFormat);

                if ( pFormatFnt )
                {
                    const SwFontScript nAct = pNumFnt->GetActual();
                    pNumFnt->SetFamily( pFormatFnt->GetFamilyType(), nAct );
                    pNumFnt->SetName( pFormatFnt->GetFamilyName(), nAct );
                    pNumFnt->SetStyleName( pFormatFnt->GetStyleName(), nAct );
                    pNumFnt->SetCharSet( pFormatFnt->GetCharSet(), nAct );
                    pNumFnt->SetPitch( pFormatFnt->GetPitch(), nAct );
                }

                // we do not allow a vertical font
                pNumFnt->SetVertical( pNumFnt->GetOrientation(),
                                      m_pFrame->IsVertical() );

                lcl_setRedlineAttr( rInf, *pTextNd, pNumFnt );

                // --> OD 2008-01-23 #newlistelevelattrs#
                if (rNumFormat.GetBulletChar())
                {
                    pRet = new SwBulletPortion(rNumFormat.GetBulletChar(),
                        pTextNd->GetLabelFollowedBy(),
                        std::move(pNumFnt),
                        bLeft, bCenter, nMinDist,
                        bLabelAlignmentPosAndSpaceModeActive);
                }
            }
            else
            {
                // Show Changes mode shows the actual numbering (SwListRedlineType::HIDDEN) and
                // the original one (SwListRedlineType::ORIGTEXT) instead of the fake numbering
                // (SwListRedlineType::SHOW, which counts removed and inserted numbered paragraphs
                // in a single list)
                bool bHasHiddenNum = false;
                OUString aTextNow( pTextNd->GetNumString(true, MAXLEVEL, m_pFrame->getRootFrame(), SwListRedlineType::HIDDEN) );
                const SwDoc& rDoc = pTextNd->GetDoc();
                const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
                if ( rTable.size() && !rInf.GetVsh()->GetLayout()->IsHideRedlines() )
                {
                    // previous (outdated) text
                    OUString aOriginalText( pTextNd->GetNumString(true, MAXLEVEL, m_pFrame->getRootFrame(), SwListRedlineType::ORIGTEXT) );

                    if ( !aTextNow.isEmpty() || !aOriginalText.isEmpty() )
                    {

                        bool bDisplayChangedParagraphNumbering = officecfg::Office::Writer::Comparison::DisplayChangedParagraphNumbering::get();
                        if (bDisplayChangedParagraphNumbering && aTextNow != aOriginalText && !aOriginalText.isEmpty())
                        {
                            bHasHiddenNum = true;
                            // show also original number after the actual one enclosed in [ and ],
                            // and replace tabulator with space to avoid messy indentation
                            // resulted by the longer numbering, e.g. "1.[2.]" instead of "1.".
                            aTextNow = aTextNow +  "[" + aOriginalText + "]"
                                     + pTextNd->GetLabelFollowedBy().replaceAll("\t", " ");
                        }
                        else if (!aTextNow.isEmpty())
                            aTextNow += pTextNd->GetLabelFollowedBy();
                    }
                }
                else if (pTextNd->getIDocumentSettingAccess()->get(DocumentSettingId::NO_NUMBERING_SHOW_FOLLOWBY)
                    || !aTextNow.isEmpty())
                    aTextNow += pTextNd->GetLabelFollowedBy();

                // Not just an optimization ...
                // A number portion without text will be assigned a width of 0.
                // The succeeding text portion will flow into the BreakCut in the BreakLine,
                // although  we have rInf.GetLast()->GetFlyPortion()!
                if( !aTextNow.isEmpty() )
                {

                    // Build a new numbering font basing on the current paragraph font:
                    std::unique_ptr<SwFont> pNumFnt(new SwFont( &rInf.GetCharAttr(), pIDSA ));

                    // #i53199#
                    if ( !pIDSA->get(DocumentSettingId::DO_NOT_RESET_PARA_ATTRS_FOR_NUM_FONT) )
                    {
                        // i18463:
                        // Underline style of paragraph font should not be considered
                        pNumFnt->SetUnderline( LINESTYLE_NONE );
                        // Overline style of paragraph font should not be considered
                        pNumFnt->SetOverline( LINESTYLE_NONE );
                    }

                    // Apply the explicit attributes from the character style
                    // associated with the numbering to the new bullet font.
                    if( pFormat )
                        pNumFnt->SetDiffFnt( pFormat, pIDSA );

                    checkApplyParagraphMarkFormatToNumbering(pNumFnt.get(), rInf, pIDSA, pFormat);

                    if ( !lcl_setRedlineAttr( rInf, *pTextNd, pNumFnt ) && bHasHiddenNum )
                        pNumFnt->SetColor(SwViewOption::GetCurrentViewOptions().GetNonPrintingCharacterColor());

                    // we do not allow a vertical font
                    pNumFnt->SetVertical( pNumFnt->GetOrientation(), m_pFrame->IsVertical() );

                    pRet = new SwNumberPortion( aTextNow, std::move(pNumFnt),
                                                bLeft, bCenter, nMinDist,
                                                bLabelAlignmentPosAndSpaceModeActive );
                }
            }
        }
    }
    return pRet;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
