/* -*- 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 <vcl/svapp.hxx>
#include <paratr.hxx>
#include <txtfrm.hxx>
#include <charfmt.hxx>
#include <viewopt.hxx>
#include <viewsh.hxx>
#include "pordrop.hxx"
#include "itrform2.hxx"
#include "txtpaint.hxx"
#include <breakit.hxx>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <com/sun/star/i18n/WordType.hpp>
#include <com/sun/star/i18n/XBreakIterator.hpp>
#include <editeng/langitem.hxx>
#include <charatr.hxx>
#include <editeng/fhgtitem.hxx>
#include <calbck.hxx>
#include <doc.hxx>
#include <IDocumentSettingAccess.hxx>

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

/**
 * Calculates if a drop caps portion intersects with a fly
 * The width and height of the drop caps portion are passed as arguments,
 * the position is calculated from the values in rInf
 */
static bool lcl_IsDropFlyInter(const SwTextFormatInfo& rInf, SwTwips nWidth, sal_uInt16 nHeight)
{
    const SwTextFly& rTextFly = rInf.GetTextFly();
    if( rTextFly.IsOn() )
    {
        SwRect aRect( rInf.GetTextFrame()->getFrameArea().Pos(), Size( nWidth, nHeight) );
        aRect.Pos() += rInf.GetTextFrame()->getFramePrintArea().Pos();
        aRect.Pos().AdjustX(rInf.X() );
        aRect.Pos().setY( rInf.Y() );
        aRect = rTextFly.GetFrame( aRect );
        return aRect.HasArea();
    }

    return false;
}

namespace {

class SwDropSave
{
    SwTextPaintInfo* pInf;
    sal_Int32 nIdx;
    sal_Int32 nLen;
    tools::Long nX;
    tools::Long nY;

public:
    explicit SwDropSave( const SwTextPaintInfo &rInf );
    ~SwDropSave();
};

}

SwDropSave::SwDropSave( const SwTextPaintInfo &rInf ) :
        pInf( const_cast<SwTextPaintInfo*>(&rInf) ), nIdx( rInf.GetIdx() ),
        nLen( rInf.GetLen() ), nX( rInf.X() ), nY( rInf.Y() )
{
}

SwDropSave::~SwDropSave()
{
    pInf->SetIdx(TextFrameIndex(nIdx));
    pInf->SetLen(TextFrameIndex(nLen));
    pInf->X( nX );
    pInf->Y( nY );
}

/// SwDropPortionPart DTor
SwDropPortionPart::~SwDropPortionPart()
{
    m_pFollow.reset();
    m_pFnt.reset();
}

/// SwDropPortion CTor, DTor
SwDropPortion::SwDropPortion( const sal_uInt16 nLineCnt,
                              const SwTwips nDrpHeight,
                              const SwTwips nDrpDescent,
                              const SwTwips nDist )
  : m_nLines( nLineCnt ),
    m_nDropHeight(nDrpHeight),
    m_nDropDescent(nDrpDescent),
    m_nDistance(nDist),
    m_nFix(0),
    m_nY(0)
{
    SetWhichPor( PortionType::Drop );
}

SwDropPortion::~SwDropPortion()
{
    m_pPart.reset();
}

/// nWishLen = 0 indicates that we want a whole word
sal_Int32 SwTextNode::GetDropLen( sal_Int32 nWishLen ) const
{
    sal_Int32 nEnd = GetText().getLength();
    if( nWishLen && nWishLen < nEnd )
        nEnd = nWishLen;

    if (! nWishLen)
    {
        // find first word
        const SwAttrSet& rAttrSet = GetSwAttrSet();
        const sal_uInt16 nTextScript = g_pBreakIt->GetRealScriptOfText( GetText(), 0 );

        LanguageType eLanguage;

        switch ( nTextScript )
        {
        case i18n::ScriptType::ASIAN :
            eLanguage = rAttrSet.GetCJKLanguage().GetLanguage();
            break;
        case i18n::ScriptType::COMPLEX :
            eLanguage = rAttrSet.GetCTLLanguage().GetLanguage();
            break;
        default :
            eLanguage = rAttrSet.GetLanguage().GetLanguage();
            break;
        }

        Boundary aBound =
            g_pBreakIt->GetBreakIter()->getWordBoundary( GetText(), 0,
            g_pBreakIt->GetLocale( eLanguage ), WordType::DICTIONARY_WORD, true );

        nEnd = aBound.endPos;
    }

    sal_Int32 i = 0;
    for( ; i < nEnd; ++i )
    {
        sal_Unicode const cChar = GetText()[i];
        if( CH_TAB == cChar || CH_BREAK == cChar ||
            (( CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar )
                && GetTextAttrForCharAt(i)) )
            break;
    }
    return i;
}

/// nWishLen = 0 indicates that we want a whole word
TextFrameIndex SwTextFrame::GetDropLen(TextFrameIndex const nWishLen) const
{
    TextFrameIndex nEnd(GetText().getLength());
    if (nWishLen && nWishLen < nEnd)
        nEnd = nWishLen;

    if (! nWishLen)
    {
        // find first word
        const SwAttrSet& rAttrSet = GetTextNodeForParaProps()->GetSwAttrSet();
        const sal_uInt16 nTextScript = g_pBreakIt->GetRealScriptOfText(GetText(), 0);

        LanguageType eLanguage;

        switch ( nTextScript )
        {
        case i18n::ScriptType::ASIAN :
            eLanguage = rAttrSet.GetCJKLanguage().GetLanguage();
            break;
        case i18n::ScriptType::COMPLEX :
            eLanguage = rAttrSet.GetCTLLanguage().GetLanguage();
            break;
        default :
            eLanguage = rAttrSet.GetLanguage().GetLanguage();
            break;
        }

        Boundary aBound = g_pBreakIt->GetBreakIter()->getWordBoundary(
            GetText(), 0, g_pBreakIt->GetLocale(eLanguage),
            WordType::DICTIONARY_WORD, true );

        nEnd = TextFrameIndex(aBound.endPos);
    }

    TextFrameIndex i(0);
    for ( ; i < nEnd; ++i)
    {
        sal_Unicode const cChar = GetText()[sal_Int32(i)];
        if (CH_TAB == cChar || CH_BREAK == cChar ||
            CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar)
        {
#ifndef NDEBUG
            if (CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar)
            {
                std::pair<SwTextNode const*, sal_Int32> const pos(MapViewToModel(i));
                assert(pos.first->GetTextAttrForCharAt(pos.second) != nullptr);
            }
#endif
            break;
        }
    }
    return i;
}

/**
 * If a dropcap is found the return value is true otherwise false. The
 * drop cap sizes passed back by reference are font height, drop height
 * and drop descent.
 */
bool SwTextNode::GetDropSize(int& rFontHeight, int& rDropHeight, int& rDropDescent) const
{
    rFontHeight = 0;
    rDropHeight = 0;
    rDropDescent =0;

    const SwAttrSet& rSet = GetSwAttrSet();
    const SwFormatDrop& rDrop = rSet.GetDrop();

    // Return (0,0) if there is no drop cap at this paragraph
    if( 1 >= rDrop.GetLines() ||
        ( !rDrop.GetChars() && !rDrop.GetWholeWord() ) )
    {
        return false;
    }

    // get text frame
    SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this);
    for( SwTextFrame* pLastFrame = aIter.First(); pLastFrame; pLastFrame = aIter.Next() )
    {
        // Only (master-) text frames can have a drop cap.
        if (!pLastFrame->IsFollow() &&
            pLastFrame->GetTextNodeForFirstText() == this)
        {

            if( !pLastFrame->HasPara() )
                pLastFrame->GetFormatted();

            if ( !pLastFrame->IsEmpty() )
            {
                const SwParaPortion* pPara = pLastFrame->GetPara();
                OSL_ENSURE( pPara, "GetDropSize could not find the ParaPortion, I'll guess the drop cap size" );

                if ( pPara )
                {
                    const SwLinePortion* pFirstPor = pPara->GetFirstPortion();
                    if (pFirstPor && pFirstPor->IsDropPortion())
                    {
                        const SwDropPortion* pDrop = static_cast<const SwDropPortion*>(pFirstPor);
                        rDropHeight = pDrop->GetDropHeight();
                        rDropDescent = pDrop->GetDropDescent();
                        if (const SwFont *pFont = pDrop->GetFnt())
                            rFontHeight = pFont->GetSize(pFont->GetActual()).Height();
                        else
                        {
                            const SvxFontHeightItem& rItem = rSet.Get(RES_CHRATR_FONTSIZE);
                            rFontHeight = rItem.GetHeight();
                        }
                    }
                }
            }
            break;
        }
    }

    if (rFontHeight==0 && rDropHeight==0 && rDropDescent==0)
    {
        const sal_uInt16 nLines = rDrop.GetLines();

        const SvxFontHeightItem& rItem = rSet.Get( RES_CHRATR_FONTSIZE );
        rFontHeight = rItem.GetHeight();
        rDropHeight = nLines * rFontHeight;
        rDropDescent = rFontHeight / 5;
        return false;
    }

    return true;
}

/// Manipulate the width, otherwise the chars are being stretched
void SwDropPortion::PaintText( const SwTextPaintInfo &rInf ) const
{
    OSL_ENSURE( m_nDropHeight && m_pPart && m_nLines != 1, "Drop Portion painted twice" );

    const SwDropPortionPart* pCurrPart = GetPart();
    const TextFrameIndex nOldLen = GetLen();
    const SwTwips nOldWidth = Width();
    const SwTwips nOldAscent = GetAscent();

    const SwTwips nBasePosY  = rInf.Y();
    const_cast<SwTextPaintInfo&>(rInf).Y( nBasePosY + m_nY );
    const_cast<SwDropPortion*>(this)->SetAscent( nOldAscent + m_nY );
    SwDropSave aSave( rInf );
    // for text inside drop portions we let vcl handle the text directions
    SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() );
    aLayoutModeModifier.SetAuto();

    while ( pCurrPart )
    {
        const_cast<SwDropPortion*>(this)->SetLen( pCurrPart->GetLen() );
        const_cast<SwDropPortion*>(this)->Width( pCurrPart->GetWidth() );
        const_cast<SwTextPaintInfo&>(rInf).SetLen( pCurrPart->GetLen() );
        SwFontSave aFontSave( rInf, &pCurrPart->GetFont() );
        const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(pCurrPart->GetJoinBorderWithNext());
        const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(pCurrPart->GetJoinBorderWithPrev());

        if ( rInf.OnWin() &&
            !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && rInf.GetOpt().IsFieldShadings() &&
            (!pCurrPart->GetFont().GetBackColor() || *pCurrPart->GetFont().GetBackColor() == COL_TRANSPARENT) )
        {
            rInf.DrawBackground( *this );
        }

        SwTextPortion::Paint( rInf );

        const_cast<SwTextPaintInfo&>(rInf).SetIdx( rInf.GetIdx() + pCurrPart->GetLen() );
        const_cast<SwTextPaintInfo&>(rInf).X( rInf.X() + pCurrPart->GetWidth() );
        pCurrPart = pCurrPart->GetFollow();
    }

    const_cast<SwTextPaintInfo&>(rInf).Y( nBasePosY );
    const_cast<SwDropPortion*>(this)->Width( nOldWidth );
    const_cast<SwDropPortion*>(this)->SetLen( nOldLen );
    const_cast<SwDropPortion*>(this)->SetAscent( nOldAscent );
    const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(false);
    const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(false);
}

void SwDropPortion::PaintDrop( const SwTextPaintInfo &rInf ) const
{
    // normal output is being done during the normal painting
    if( ! m_nDropHeight || ! m_pPart || m_nLines == 1 )
        return;

    // set the lying values
    const SwTwips nOldHeight = Height();
    const SwTwips nOldWidth = Width();
    const SwTwips nOldAscent = GetAscent();
    const SwTwips nOldPosY  = rInf.Y();
    const SwTwips nOldPosX  = rInf.X();
    const SwParaPortion *pPara = rInf.GetParaPortion();
    const Point aOutPos( nOldPosX, nOldPosY - pPara->GetAscent()
                         - pPara->GetRealHeight() + pPara->Height() );
    // make good for retouching

    // Set baseline
    const_cast<SwTextPaintInfo&>(rInf).Y( aOutPos.Y() + m_nDropHeight );

    // for background
    const_cast<SwDropPortion*>(this)->Height( m_nDropHeight + m_nDropDescent );
    const_cast<SwDropPortion*>(this)->SetAscent( m_nDropHeight );

    // Always adapt Clipregion to us, never set it off using the existing ClipRect
    // as that could be set for the line
    SwRect aClipRect;
    if ( rInf.OnWin() )
    {
        aClipRect = SwRect( aOutPos, SvLSize() );
        aClipRect.Intersection( rInf.GetPaintRect() );
    }
    SwSaveClip aClip( const_cast<OutputDevice*>(rInf.GetOut()) );
    aClip.ChgClip( aClipRect, rInf.GetTextFrame() );

    // Just do, what we always do ...
    PaintText( rInf );

    // save old values
    const_cast<SwDropPortion*>(this)->Height( nOldHeight );
    const_cast<SwDropPortion*>(this)->Width( nOldWidth );
    const_cast<SwDropPortion*>(this)->SetAscent( nOldAscent );
    const_cast<SwTextPaintInfo&>(rInf).Y( nOldPosY );
}

void SwDropPortion::Paint( const SwTextPaintInfo &rInf ) const
{
    // normal output is being done here
    if( !(! m_nDropHeight || ! m_pPart || 1 == m_nLines) )
        return;

    if ( rInf.OnWin() &&
        !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && rInf.GetOpt().IsFieldShadings()       )
        rInf.DrawBackground( *this );

    // make sure that font is not rotated
    std::unique_ptr<SwFont> pTmpFont;
    if ( rInf.GetFont()->GetOrientation( rInf.GetTextFrame()->IsVertical() ) )
    {
        pTmpFont.reset(new SwFont( *rInf.GetFont() ));
        pTmpFont->SetVertical( 0_deg10, rInf.GetTextFrame()->IsVertical() );
    }

    SwFontSave aFontSave( rInf, pTmpFont.get() );
    // for text inside drop portions we let vcl handle the text directions
    SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() );
    aLayoutModeModifier.SetAuto();

    SwTextPortion::Paint( rInf );
}

bool SwDropPortion::FormatText( SwTextFormatInfo &rInf )
{
    const TextFrameIndex nOldLen = GetLen();
    const TextFrameIndex nOldInfLen = rInf.GetLen();
    if (!SwTextPortion::Format( rInf ))
        return false;

    // looks like shit, but what can we do?
    rInf.SetUnderflow( nullptr );
    Truncate();
    SetLen( nOldLen );
    rInf.SetLen( nOldInfLen );

    return true;
}

SwPositiveSize SwDropPortion::GetTextSize( const SwTextSizeInfo &rInf ) const
{
    SwTwips nMyX = 0;
    TextFrameIndex nIdx(0);

    const SwDropPortionPart* pCurrPart = GetPart();

    // skip parts
    while ( pCurrPart && nIdx + pCurrPart->GetLen() < rInf.GetLen() )
    {
        nMyX = nMyX + pCurrPart->GetWidth();
        nIdx = nIdx + pCurrPart->GetLen();
        pCurrPart = pCurrPart->GetFollow();
    }

    TextFrameIndex const nOldIdx = rInf.GetIdx();
    TextFrameIndex const nOldLen = rInf.GetLen();

    const_cast<SwTextSizeInfo&>(rInf).SetIdx( nIdx );
    const_cast<SwTextSizeInfo&>(rInf).SetLen( rInf.GetLen() - nIdx );

    if( pCurrPart )
    {
        const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(pCurrPart->GetJoinBorderWithNext());
        const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(pCurrPart->GetJoinBorderWithPrev());
    }

    // robust
    SwFontSave aFontSave( rInf, pCurrPart ? &pCurrPart->GetFont() : nullptr );
    SwPositiveSize aPosSize( SwTextPortion::GetTextSize( rInf ) );
    aPosSize.Width( aPosSize.Width() + nMyX );

    const_cast<SwTextSizeInfo&>(rInf).SetIdx( nOldIdx );
    const_cast<SwTextSizeInfo&>(rInf).SetLen( nOldLen );
    if( pCurrPart )
    {
        const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(false);
        const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(false);
    }

    return aPosSize;
}

TextFrameIndex SwDropPortion::GetModelPositionForViewPoint(const SwTwips) const
{
    return TextFrameIndex(0);
}

void SwTextFormatter::CalcDropHeight( const sal_uInt16 nLines )
{
    const SwLinePortion *const pOldCurr = GetCurr();
    SwTwips nDropHght = 0;
    SwTwips nAscent = 0;
    SwTwips nHeight = 0;
    sal_uInt16 nDropLns = 0;
    const bool bRegisterOld = IsRegisterOn();
    m_bRegisterOn = false;

    Top();

    while( GetCurr()->IsDummy() )
    {
        if ( !Next() )
            break;
    }

    // If we have only one line we return 0
    if( GetNext() || GetDropLines() == 1 )
    {
        for( ; nDropLns < nLines; nDropLns++ )
        {
            if ( GetCurr()->IsDummy() )
                break;
            else
            {
                CalcAscentAndHeight( nAscent, nHeight );
                nDropHght = nDropHght + nHeight;
                m_bRegisterOn = bRegisterOld;
            }
            if ( !Next() )
            {
                nDropLns++;
                break;
            }
        }

        // We hit the line ascent when reaching the last line!
        nDropHght = nDropHght - nHeight;
        nDropHght = nDropHght + nAscent;
        Top();
    }
    m_bRegisterOn = bRegisterOld;
    SetDropDescent( nHeight - nAscent );
    SetDropHeight( nDropHght );
    SetDropLines( nDropLns );
    // Find old position!
    while( pOldCurr != GetCurr() )
    {
        if( !Next() )
        {
            OSL_ENSURE( false, "SwTextFormatter::_CalcDropHeight: left Toulouse" );
            break;
        }
    }
}

/**
 * We assume that the font height doesn't change and that at first there
 * are at least as many lines, as the DropCap-setting claims
 */
void SwTextFormatter::GuessDropHeight( const sal_uInt16 nLines )
{
    OSL_ENSURE( nLines, "GuessDropHeight: Give me more Lines!" );
    SwTwips nAscent = 0;
    SwTwips nHeight = 0;
    SetDropLines( nLines );
    if ( GetDropLines() > 1 )
    {
        CalcRealHeight();
        CalcAscentAndHeight( nAscent, nHeight );
    }
    SetDropDescent( nHeight - nAscent );
    SetDropHeight( nHeight * nLines - GetDropDescent() );
}

SwDropPortion *SwTextFormatter::NewDropPortion( SwTextFormatInfo &rInf )
{
    if( !m_pDropFormat )
        return nullptr;

    TextFrameIndex nPorLen(m_pDropFormat->GetWholeWord() ? 0 : m_pDropFormat->GetChars());
    nPorLen = m_pFrame->GetDropLen( nPorLen );
    if( !nPorLen )
    {
        ClearDropFormat();
        return nullptr;
    }

    SwDropPortion *pDropPor = nullptr;

    // first or second round?
    if ( !( GetDropHeight() || IsOnceMore() ) )
    {
        if ( GetNext() )
            CalcDropHeight( m_pDropFormat->GetLines() );
        else
            GuessDropHeight( m_pDropFormat->GetLines() );
    }

    // the DropPortion
    if( GetDropHeight() )
        pDropPor = new SwDropPortion( GetDropLines(), GetDropHeight(),
                                      GetDropDescent(), m_pDropFormat->GetDistance() );
    else
       pDropPor = new SwDropPortion( 0,0,0,m_pDropFormat->GetDistance() );

    pDropPor->SetLen( nPorLen );

    // If it was not possible to create a proper drop cap portion
    // due to avoiding endless loops. We return a drop cap portion
    // with an empty SwDropCapPart. For these portions the current
    // font is used.
    if ( GetDropLines() < 2 )
    {
        SetPaintDrop( true );
        return pDropPor;
    }

    // build DropPortionParts:
    OSL_ENSURE( ! rInf.GetIdx(), "Drop Portion not at 0 position!" );
    TextFrameIndex nNextChg(0);
    const SwCharFormat* pFormat = m_pDropFormat->GetCharFormat();
    SwDropPortionPart* pCurrPart = nullptr;

    while ( nNextChg  < nPorLen )
    {
        // check for attribute changes and if the portion has to split:
        Seek( nNextChg );

        // the font is deleted in the destructor of the drop portion part
        SwFont* pTmpFnt = new SwFont( *rInf.GetFont() );
        if ( pFormat )
        {
            const SwAttrSet& rSet = pFormat->GetAttrSet();
            pTmpFnt->SetDiffFnt(&rSet, &m_pFrame->GetDoc().getIDocumentSettingAccess());
        }

        // we do not allow a vertical font for the drop portion
        pTmpFnt->SetVertical( 0_deg10, rInf.GetTextFrame()->IsVertical() );

        // find next attribute change / script change
        const TextFrameIndex nTmpIdx = nNextChg;
        TextFrameIndex nNextAttr = GetNextAttr();
        nNextChg = m_pScriptInfo->NextScriptChg( nTmpIdx );
        if( nNextChg > nNextAttr )
            nNextChg = nNextAttr;
        if ( nNextChg > nPorLen )
            nNextChg = nPorLen;

        std::unique_ptr<SwDropPortionPart> pPart(
                new SwDropPortionPart( *pTmpFnt, nNextChg - nTmpIdx ) );
        auto pPartTemp = pPart.get();

        if ( ! pCurrPart )
            pDropPor->SetPart( std::move(pPart) );
        else
            pCurrPart->SetFollow( std::move(pPart) );

        pCurrPart = pPartTemp;
    }

    SetPaintDrop( true );
    return pDropPor;
}

void SwTextPainter::PaintDropPortion()
{
    const SwDropPortion *pDrop = GetInfo().GetParaPortion()->FindDropPortion();
    OSL_ENSURE( pDrop, "DrapCop-Portion not available." );
    if( !pDrop )
        return;

    const SwTwips nOldY = GetInfo().Y();

    Top();

    GetInfo().SetpSpaceAdd( m_pCurr->GetpLLSpaceAdd() );
    GetInfo().ResetSpaceIdx();
    GetInfo().SetKanaComp( m_pCurr->GetpKanaComp() );
    GetInfo().ResetKanaIdx();

    // 8047: Drops and Dummies
    while( !m_pCurr->GetLen() && Next() )
        ;

    // MarginPortion and Adjustment!
    const SwLinePortion *pPor = m_pCurr->GetFirstPortion();
    tools::Long nX = 0;
    while( pPor && !pPor->IsDropPortion() )
    {
        nX = nX + pPor->Width();
        pPor = pPor->GetNextPortion();
    }
    Point aLineOrigin( GetTopLeft() );

    aLineOrigin.AdjustX(nX );
    SwTwips nTmpAscent, nTmpHeight;
    CalcAscentAndHeight( nTmpAscent, nTmpHeight );
    aLineOrigin.AdjustY(nTmpAscent );
    GetInfo().SetIdx( GetStart() );
    GetInfo().SetPos( aLineOrigin );
    GetInfo().SetLen( pDrop->GetLen() );

    pDrop->PaintDrop( GetInfo() );

    GetInfo().Y( nOldY );
}

// Since the calculation of the font size is expensive, this is being
// channeled through a DropCapCache
#define DROP_CACHE_SIZE 10

class SwDropCapCache
{
    const void* m_aFontCacheId[ DROP_CACHE_SIZE ] = {};
    OUString m_aText[ DROP_CACHE_SIZE ];
    tools::Long m_aFactor[ DROP_CACHE_SIZE ];
    SwTwips m_aWishedHeight[DROP_CACHE_SIZE] = {};
    SwTwips m_aDescent[DROP_CACHE_SIZE];
    sal_uInt16 m_nIndex = 0;
public:
    SwDropCapCache() = default;
    void CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf );
};

void SwDropPortion::DeleteDropCapCache()
{
    delete pDropCapCache;
}

void SwDropCapCache::CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf )
{
    const void* nFntCacheId = nullptr;
    sal_uInt16 nTmpIdx = 0;

    OSL_ENSURE( pDrop->GetPart(),"DropPortion without part during font calculation");

    SwDropPortionPart* pCurrPart = pDrop->GetPart();
    const bool bUseCache = ! pCurrPart->GetFollow() && !pCurrPart->GetFont().HasBorder();
    TextFrameIndex nIdx = rInf.GetIdx();
    OUString aStr(rInf.GetText().copy(sal_Int32(nIdx), sal_Int32(pCurrPart->GetLen())));

    tools::Long nDescent = 0;
    tools::Long nFactor = -1;

    if ( bUseCache )
    {
        SwFont& rFnt = pCurrPart->GetFont();
        rFnt.CheckFontCacheId( rInf.GetVsh(), rFnt.GetActual() );
        rFnt.GetFontCacheId( nFntCacheId, nTmpIdx, rFnt.GetActual() );

        nTmpIdx = 0;

        while( nTmpIdx < DROP_CACHE_SIZE &&
            ( m_aText[ nTmpIdx ] != aStr || m_aFontCacheId[ nTmpIdx ] != nFntCacheId ||
            m_aWishedHeight[ nTmpIdx ] != pDrop->GetDropHeight() ) )
            ++nTmpIdx;
    }

    // we have to calculate a new font scaling factor if
    // 1. we did not find a scaling factor in the cache or
    // 2. we are not allowed to use the cache because the drop portion
    //    consists of more than one part
    if( nTmpIdx >= DROP_CACHE_SIZE || ! bUseCache )
    {
        ++m_nIndex;
        m_nIndex %= DROP_CACHE_SIZE;
        nTmpIdx = m_nIndex;

        SwTwips nWishedHeight = pDrop->GetDropHeight();
        tools::Long nAscent = 0;

        // find out biggest font size for initial scaling factor
        tools::Long nMaxFontHeight = 1;
        while ( pCurrPart )
        {
            const SwFont& rFnt = pCurrPart->GetFont();
            const tools::Long nCurrHeight = rFnt.GetHeight( rFnt.GetActual() );
            if ( nCurrHeight > nMaxFontHeight )
                nMaxFontHeight = nCurrHeight;

            pCurrPart = pCurrPart->GetFollow();
        }

        nFactor = ( 1000 * nWishedHeight ) / nMaxFontHeight;

        if ( bUseCache )
        {
            // save keys for cache
            m_aFontCacheId[ nTmpIdx ] = nFntCacheId;
            m_aText[ nTmpIdx ] = aStr;
            m_aWishedHeight[ nTmpIdx ] = nWishedHeight;
            // save initial scaling factor
            m_aFactor[ nTmpIdx ] = nFactor;
        }

        bool bGrow = (pDrop->GetLen() != TextFrameIndex(0));

        // for growing control
        tools::Long nMax = std::numeric_limits<tools::Long>::max();
        tools::Long nMin = 0;
#if OSL_DEBUG_LEVEL > 1
        tools::Long nGrow = 0;
#endif

        bool bWinUsed = false;
        vcl::Font aOldFnt;
        MapMode aOldMap( MapUnit::MapTwip );
        OutputDevice* pOut = rInf.GetOut();
        OutputDevice* pWin;
        if( rInf.GetVsh() && rInf.GetVsh()->GetWin() )
            pWin = rInf.GetVsh()->GetWin()->GetOutDev();
        else
            pWin = Application::GetDefaultDevice();

        // adjust punctuation?
        bool bKeepBaseline = rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess()
            .get(DocumentSettingId::DROP_CAP_PUNCTUATION) &&
            !rInf.GetDropFormat()->GetWholeWord(); // && rInf.GetDropFormat()->GetChars() == 1;

        while( bGrow )
        {
            // reset pCurrPart to first part
            pCurrPart = pDrop->GetPart();
            bool bFirstGlyphRect = true;
            tools::Rectangle aCommonRect, aRect;

            while ( pCurrPart )
            {
                // current font
                SwFont& rFnt = pCurrPart->GetFont();

                // Get height including proportion
                const tools::Long nCurrHeight = rFnt.GetHeight( rFnt.GetActual() );

                // Get without proportion
                const sal_uInt8 nOldProp = rFnt.GetPropr();
                rFnt.SetProportion( 100 );
                Size aOldSize( 0, rFnt.GetHeight( rFnt.GetActual() ) );

                Size aNewSize( 0, ( nFactor * nCurrHeight ) / 1000 );
                rFnt.SetSize( aNewSize, rFnt.GetActual() );
                rFnt.ChgPhysFnt( rInf.GetVsh(), *pOut );

                nAscent = rFnt.GetAscent( rInf.GetVsh(), *pOut );

                // we get the rectangle that covers all chars
                bool bHaveGlyphRect = pOut->GetTextBoundRect( aRect, rInf.GetText(), 0,
                            sal_Int32(nIdx), sal_Int32(pCurrPart->GetLen()))
                    && ! aRect.IsEmpty();

                if ( ! bHaveGlyphRect )
                {
                    // getting glyph boundaries failed for some reason,
                    // we take the window for calculating sizes
                    if ( pWin )
                    {
                        if ( ! bWinUsed )
                        {
                            bWinUsed = true;
                            aOldMap = pWin->GetMapMode( );
                            pWin->SetMapMode( MapMode( MapUnit::MapTwip ) );
                            aOldFnt = pWin->GetFont();
                        }
                        pWin->SetFont( rFnt.GetActualFont() );

                        bHaveGlyphRect = pWin->GetTextBoundRect( aRect, rInf.GetText(), 0,
                                sal_Int32(nIdx), sal_Int32(pCurrPart->GetLen()))
                            && ! aRect.IsEmpty();
                    }
                    if (!bHaveGlyphRect)
                    {
                        // We do not have a window or our window could not
                        // give us glyph boundaries.
                        aRect = tools::Rectangle( Point( 0, 0 ), Size( 0, nAscent ) );
                    }
                }

                // extend rectangle to the baseline to avoid of giant dashes,
                // quotation marks, bullet, asterisks etc.
                if ( bKeepBaseline && aRect.Top() < 0 )
                {
                    aRect.SetBottom(0);
                    aRect.SetTop(aRect.Top() - nAscent/60);
                }

                // Now we (hopefully) have a bounding rectangle for the
                // glyphs of the current portion and the ascent of the current
                // font

                // reset font size and proportion
                rFnt.SetSize( aOldSize, rFnt.GetActual() );
                rFnt.SetProportion( nOldProp );

                // Modify the bounding rectangle with the borders
                // Robust: If the padding is so big as drop cap letter has no enough space than
                // remove all padding.
                if( rFnt.GetTopBorderSpace() + rFnt.GetBottomBorderSpace() >= nWishedHeight )
                {
                    rFnt.SetTopBorderDist(0);
                    rFnt.SetBottomBorderDist(0);
                    rFnt.SetRightBorderDist(0);
                    rFnt.SetLeftBorderDist(0);
                }

                if( rFnt.GetTopBorder() )
                {
                    aRect.setHeight(aRect.GetHeight() + rFnt.GetTopBorderSpace());
                    aRect.SetPosY(aRect.Top() - rFnt.GetTopBorderSpace());
                }

                if( rFnt.GetBottomBorder() )
                {
                    aRect.setHeight(aRect.GetHeight() + rFnt.GetBottomBorderSpace());
                }

                if ( bFirstGlyphRect )
                {
                    aCommonRect = aRect;
                    bFirstGlyphRect = false;
                }
                else
                    aCommonRect.Union( aRect );

                nIdx = nIdx + pCurrPart->GetLen();
                pCurrPart = pCurrPart->GetFollow();
            }

            // now we have a union ( aCommonRect ) of all glyphs with
            // respect to a common baseline : 0

            // get descent and ascent from union
            if ( rInf.GetTextFrame()->IsVertical() )
            {
                nDescent = aCommonRect.Left();
                nAscent = aCommonRect.Right();

                if ( nDescent < 0 )
                    nDescent = -nDescent;
            }
            else
            {
                nDescent = aCommonRect.Bottom();
                nAscent = aCommonRect.Top();
            }
            if ( nAscent < 0 )
                nAscent = -nAscent;

            const tools::Long nHght = nAscent + nDescent;
            if ( nHght )
            {
                if ( nHght > nWishedHeight )
                    nMax = nFactor;
                else
                {
                    if ( bUseCache )
                        m_aFactor[ nTmpIdx ] = nFactor;
                    nMin = nFactor;
                }

                nFactor = ( nFactor * nWishedHeight ) / nHght;
                bGrow = ( nFactor > nMin ) && ( nFactor < nMax );
#if OSL_DEBUG_LEVEL > 1
                if ( bGrow )
                    nGrow++;
#endif
                nIdx = rInf.GetIdx();
            }
            else
                bGrow = false;
        }

        if ( bWinUsed )
        {
            // reset window if it has been used
            pWin->SetMapMode( aOldMap );
            pWin->SetFont( aOldFnt );
        }

        if ( bUseCache )
            m_aDescent[ nTmpIdx ] = -short( nDescent );
    }

    pCurrPart = pDrop->GetPart();

    // did made any new calculations or did we use the cache?
    if ( -1 == nFactor )
    {
        nFactor = m_aFactor[ nTmpIdx ];
        nDescent = m_aDescent[ nTmpIdx ];
    }
    else
        nDescent = -nDescent;

    while ( pCurrPart )
    {
        // scale current font
        SwFont& rFnt = pCurrPart->GetFont();
        Size aNewSize( 0, ( nFactor * rFnt.GetHeight( rFnt.GetActual() ) ) / 1000 );

        const sal_uInt8 nOldProp = rFnt.GetPropr();
        rFnt.SetProportion( 100 );
        rFnt.SetSize( aNewSize, rFnt.GetActual() );
        rFnt.SetProportion( nOldProp );

        pCurrPart = pCurrPart->GetFollow();
    }
    pDrop->SetY( static_cast<short>(nDescent) );
}

bool SwDropPortion::Format( SwTextFormatInfo &rInf )
{
    bool bFull = false;
    m_nFix = rInf.X();

    SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() );
    aLayoutModeModifier.SetAuto();

    if( m_nDropHeight && m_pPart && m_nLines!=1 )
    {
        if( !pDropCapCache )
            pDropCapCache = new SwDropCapCache;

        // adjust font sizes to fit into the rectangle
        pDropCapCache->CalcFontSize( this, rInf );

        const SwTwips nOldX = rInf.X();
        {
            SwDropSave aSave( rInf );
            SwDropPortionPart* pCurrPart = m_pPart.get();

            while ( pCurrPart )
            {
                rInf.SetLen( pCurrPart->GetLen() );
                SwFont& rFnt = pCurrPart->GetFont();
                {
                    SwFontSave aFontSave( rInf, &rFnt );
                    SetJoinBorderWithNext(pCurrPart->GetJoinBorderWithNext());
                    SetJoinBorderWithPrev(pCurrPart->GetJoinBorderWithPrev());
                    bFull = FormatText( rInf );

                    if ( bFull )
                        break;
                }

                const SwTwips nTmpWidth =
                        ( InSpaceGrp() && rInf.GetSpaceAdd() ) ?
                        Width() + CalcSpacing( rInf.GetSpaceAdd(), rInf ) :
                        Width();

                // set values
                pCurrPart->SetWidth(nTmpWidth);

                // Move
                rInf.SetIdx( rInf.GetIdx() + pCurrPart->GetLen() );
                rInf.X( rInf.X() + nTmpWidth );
                pCurrPart = pCurrPart->GetFollow();
            }
            SetJoinBorderWithNext(false);
            SetJoinBorderWithPrev(false);
            Width(rInf.X() - nOldX);
        }

        // reset my length
        SetLen( rInf.GetLen() );

        // Quit when Flys are overlapping
        if( ! bFull )
            bFull = lcl_IsDropFlyInter( rInf, Width(), m_nDropHeight );

        if( bFull )
        {
            // FormatText could have caused nHeight to be 0
            if ( !Height() )
                Height( rInf.GetTextHeight() );

            // And now for another round
            m_nDropHeight = m_nLines = 0;
            m_pPart.reset();

            // Meanwhile use normal formatting
            bFull = SwTextPortion::Format( rInf );
        }
        else
            rInf.SetDropInit( true );

        Height( rInf.GetTextHeight() );
        SetAscent( rInf.GetAscent() );
    }
    else
        bFull = SwTextPortion::Format( rInf );

    if( bFull )
        m_nDistance = 0;
    else
    {
        const SwTwips nWant = Width() + GetDistance();
        const SwTwips nRest = rInf.Width() - rInf.X();
        if( ( nWant > nRest ) ||
            lcl_IsDropFlyInter( rInf, nWant, m_nDropHeight ) )
            m_nDistance = 0;

        Width( Width() + m_nDistance );
    }
    return bFull;
}

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