/**************************************************************
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sw.hxx"


#include <hintids.hxx>
#include <vcl/metric.hxx>
#include <vcl/window.hxx>
#include <vcl/svapp.hxx>
#include <paratr.hxx>
#include <txtfrm.hxx>	// Format()
#include <charfmt.hxx>
#include <viewopt.hxx>	// SwViewOption
#include <viewsh.hxx>	// ViewShell
#include <pordrop.hxx>
#include <itrform2.hxx>
#include <txtpaint.hxx>	// SwSaveClip
#include <blink.hxx>	// pBlink
#include <breakit.hxx>
#include <com/sun/star/i18n/ScriptType.hdl>
#include <com/sun/star/i18n/WordType.hpp>
#include <editeng/langitem.hxx>
#include <charatr.hxx>
#include <editeng/fhgtitem.hxx>
#include <switerator.hxx>

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

/*************************************************************************
 * lcl_IsDropFlyInter
 *
 * 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
 *************************************************************************/

sal_Bool lcl_IsDropFlyInter( const SwTxtFormatInfo &rInf,
							 sal_uInt16 nWidth, sal_uInt16 nHeight )
{
	const SwTxtFly *pTxtFly = rInf.GetTxtFly();
	if( pTxtFly && pTxtFly->IsOn() )
	{
		SwRect aRect( rInf.GetTxtFrm()->Frm().Pos(), Size( nWidth, nHeight) );
		aRect.Pos() += rInf.GetTxtFrm()->Prt().Pos();
		aRect.Pos().X() += rInf.X();
		aRect.Pos().Y() = rInf.Y();
		aRect = pTxtFly->GetFrm( aRect );
		return aRect.HasArea();
	}

	return sal_False;
}

/*************************************************************************
 * class SwDropSave
 *************************************************************************/

class SwDropSave
{
	SwTxtPaintInfo* pInf;
	xub_StrLen nIdx;
	xub_StrLen nLen;
	long nX;
	long nY;

public:
	SwDropSave( const SwTxtPaintInfo &rInf );
	~SwDropSave();
};

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

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

/*************************************************************************
 * SwDropPortionPart DTor
 *************************************************************************/

SwDropPortionPart::~SwDropPortionPart()
{
	if ( pFollow )
		delete pFollow;
	delete pFnt;
}

/*************************************************************************
 * SwDropPortion CTor, DTor
 *************************************************************************/

SwDropPortion::SwDropPortion( const MSHORT nLineCnt,
							  const KSHORT nDrpHeight,
							  const KSHORT nDrpDescent,
							  const KSHORT nDist )
  : pPart( 0 ),
	nLines( nLineCnt ),
	nDropHeight(nDrpHeight),
	nDropDescent(nDrpDescent),
	nDistance(nDist),
	nFix(0),
	nX(0)
{
	SetWhichPor( POR_DROP );
}

SwDropPortion::~SwDropPortion()
{
	delete pPart;
	if( pBlink )
		pBlink->Delete( this );
}

sal_Bool SwTxtSizeInfo::_HasHint( const SwTxtNode* pTxtNode, xub_StrLen nPos )
{
	return 0 != pTxtNode->GetTxtAttrForCharAt(nPos);
}

/*************************************************************************
 * SwTxtNode::GetDropLen()
 *
 * nWishLen = 0 indicates that we want a whole word
 *************************************************************************/

MSHORT SwTxtNode::GetDropLen( MSHORT nWishLen ) const
{
	xub_StrLen nEnd = GetTxt().Len();
	if( nWishLen && nWishLen < nEnd )
		nEnd = nWishLen;

	if ( ! nWishLen && pBreakIt->GetBreakIter().is() )
	{
		// find first word
		const SwAttrSet& rAttrSet = GetSwAttrSet();
		const sal_uInt16 nTxtScript = pBreakIt->GetRealScriptOfText( GetTxt(), 0 );

		LanguageType eLanguage;

		switch ( nTxtScript )
		{
		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 =
			pBreakIt->GetBreakIter()->getWordBoundary( GetTxt(), 0,
			pBreakIt->GetLocale( eLanguage ), WordType::DICTIONARY_WORD, sal_True );

		nEnd = (xub_StrLen)aBound.endPos;
	}

	xub_StrLen i = 0;
	for( ; i < nEnd; ++i )
	{
		xub_Unicode cChar = GetTxt().GetChar( i );
		if( CH_TAB == cChar || CH_BREAK == cChar ||
			(( CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar )
				&& SwTxtSizeInfo::_HasHint( this, i ) ) )
			break;
	}
	return i;
}

/*************************************************************************
 * SwTxtNode::GetDropSize()
 *
 * 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 SwTxtNode::GetDropSize(int& rFontHeight, int& rDropHeight, int& rDropDescent) const
{
	rFontHeight = 0;
	rDropHeight = 0;
	rDropDescent =0;

	const SwAttrSet& rSet = GetSwAttrSet();
	const SwFmtDrop& 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<SwTxtFrm,SwTxtNode> aIter( *this );
	for( SwTxtFrm* pLastFrm = aIter.First(); pLastFrm; pLastFrm = aIter.Next() )
	{
		// Only (master-) text frames can have a drop cap.
		if ( !pLastFrm->IsFollow() )
		{

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

			if ( !pLastFrm->IsEmpty() )
			{
				const SwParaPortion* pPara = pLastFrm->GetPara();
				ASSERT( 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 = (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 = (SvxFontHeightItem&)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 = (SvxFontHeightItem&)rSet.Get( RES_CHRATR_FONTSIZE );
		rFontHeight = rItem.GetHeight();
		rDropHeight = nLines * rFontHeight;
		rDropDescent = rFontHeight / 5;
		return false;
	}

	return true;
}

/*************************************************************************
 * SwDropPortion::PaintTxt()
 *************************************************************************/

// Die Breite manipulieren, sonst werden die Buchstaben gestretcht

void SwDropPortion::PaintTxt( const SwTxtPaintInfo &rInf ) const
{
	if ( rInf.OnWin() &&
		 !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && SwViewOption::IsFieldShadings() )
		 rInf.DrawBackground( *this );

	ASSERT( nDropHeight && pPart && nLines != 1, "Drop Portion painted twice" );

	const SwDropPortionPart* pCurrPart = GetPart();
	const xub_StrLen nOldLen = GetLen();

	const SwTwips nBasePosY = rInf.Y();
	((SwTxtPaintInfo&)rInf).Y( nBasePosY + nY );
	SwDropSave aSave( rInf );
	// for text inside drop portions we let vcl handle the text directions
	SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() );
	aLayoutModeModifier.SetAuto();

	while ( pCurrPart )
	{
		((SwDropPortion*)this)->SetLen( pCurrPart->GetLen() );
		((SwTxtPaintInfo&)rInf).SetLen( pCurrPart->GetLen() );
		SwFontSave aFontSave( rInf, &pCurrPart->GetFont() );

		SwTxtPortion::Paint( rInf );

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

	((SwTxtPaintInfo&)rInf).Y( nBasePosY );
	((SwDropPortion*)this)->SetLen( nOldLen );
}

/*************************************************************************
 * SwDropPortion::Paint()
 *************************************************************************/

void SwDropPortion::PaintDrop( const SwTxtPaintInfo &rInf ) const
{
	// ganz normale Ausgabe wird w?hrend des normalen Paints erledigt
	if( ! nDropHeight || ! pPart || nLines == 1 )
		return;

	// Luegenwerte einstellen!
	const KSHORT nOldHeight = Height();
	const KSHORT nOldWidth = Width();
	const KSHORT nOldAscent = GetAscent();
	const SwTwips nOldPosY = rInf.Y();
	const KSHORT nOldPosX = (KSHORT)rInf.X();
	const SwParaPortion *pPara = rInf.GetParaPortion();
	const Point aOutPos( nOldPosX + nX, nOldPosY - pPara->GetAscent()
						 - pPara->GetRealHeight() + pPara->Height() );
	// Retusche nachholen.

	// Set baseline
	((SwTxtPaintInfo&)rInf).Y( aOutPos.Y() + nDropHeight );

	// for background
	((SwDropPortion*)this)->Height( nDropHeight + nDropDescent );
	((SwDropPortion*)this)->Width( Width() - nX );
	((SwDropPortion*)this)->SetAscent( nDropHeight );

	// Clipregion auf uns einstellen!
	// Und zwar immer, und nie mit dem bestehenden ClipRect
	// verrechnen, weil dies auf die Zeile eingestellt sein koennte.

	SwRect aClipRect;
	if ( rInf.OnWin() )
	{
		aClipRect = SwRect( aOutPos, SvLSize() );
		aClipRect.Intersection( rInf.GetPaintRect() );
	}
	SwSaveClip aClip( (OutputDevice*)rInf.GetOut() );
	aClip.ChgClip( aClipRect, rInf.GetTxtFrm() );
	// Das machen, was man sonst nur macht ...
	PaintTxt( rInf );

	// Alte Werte sichern
	((SwDropPortion*)this)->Height( nOldHeight );
	((SwDropPortion*)this)->Width( nOldWidth );
	((SwDropPortion*)this)->SetAscent( nOldAscent );
	((SwTxtPaintInfo&)rInf).Y( nOldPosY );
}

/*************************************************************************
 * virtual SwDropPortion::Paint()
 *************************************************************************/

void SwDropPortion::Paint( const SwTxtPaintInfo &rInf ) const
{
	// ganz normale Ausgabe wird hier erledigt.
	if( ! nDropHeight || ! pPart || 1 == nLines )
	{
		if ( rInf.OnWin() &&
			 !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && SwViewOption::IsFieldShadings() )
			 rInf.DrawBackground( *this );

		// make sure that font is not rotated
		SwFont* pTmpFont = 0;
		if ( rInf.GetFont()->GetOrientation( rInf.GetTxtFrm()->IsVertical() ) )
		{
			pTmpFont = new SwFont( *rInf.GetFont() );
			pTmpFont->SetVertical( 0, rInf.GetTxtFrm()->IsVertical() );
		}

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

		SwTxtPortion::Paint( rInf );
		delete pTmpFont;
	}
}

/*************************************************************************
 * virtual Format()
 *************************************************************************/


sal_Bool SwDropPortion::FormatTxt( SwTxtFormatInfo &rInf )
{
	const xub_StrLen nOldLen = GetLen();
	const xub_StrLen nOldInfLen = rInf.GetLen();
	const sal_Bool bFull = SwTxtPortion::Format( rInf );
	if( bFull )
	{
		// sieht zwar nicht gut aus, aber was soll man schon machen?
		rInf.SetUnderFlow( 0 );
		Truncate();
		SetLen( nOldLen );
		rInf.SetLen( nOldInfLen );
	}
	return bFull;
}

/*************************************************************************
 * virtual GetTxtSize()
 *************************************************************************/


SwPosSize SwDropPortion::GetTxtSize( const SwTxtSizeInfo &rInf ) const
{
	sal_uInt16 nMyX = 0;
	xub_StrLen 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();
	}

	xub_StrLen nOldIdx = rInf.GetIdx();
	xub_StrLen nOldLen = rInf.GetLen();

	((SwTxtSizeInfo&)rInf).SetIdx( nIdx );
	((SwTxtSizeInfo&)rInf).SetLen( rInf.GetLen() - nIdx );

	// robust
	SwFontSave aFontSave( rInf, pCurrPart ? &pCurrPart->GetFont() : 0 );
	SwPosSize aPosSize( SwTxtPortion::GetTxtSize( rInf ) );
	aPosSize.Width( aPosSize.Width() + nMyX );

	((SwTxtSizeInfo&)rInf).SetIdx( nOldIdx );
	((SwTxtSizeInfo&)rInf).SetLen( nOldLen );

	return aPosSize;
}

/*************************************************************************
 * virtual GetCrsrOfst()
 *************************************************************************/

xub_StrLen SwDropPortion::GetCrsrOfst( const KSHORT ) const
{
	return 0;
}

/*************************************************************************
 * SwTxtFormatter::CalcDropHeight()
 *************************************************************************/

void SwTxtFormatter::CalcDropHeight( const MSHORT nLines )
{
	const SwLinePortion *const pOldCurr = GetCurr();
	KSHORT nDropHght = 0;
	KSHORT nAscent = 0;
	KSHORT nHeight = 0;
	KSHORT nDropLns = 0;
	sal_Bool bRegisterOld = IsRegisterOn();
	bRegisterOn = sal_False;

	Top();

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

	// Wenn wir nur eine Zeile haben returnen wir 0
	if( GetNext() || GetDropLines() == 1 )
	{
		for( ; nDropLns < nLines; nDropLns++ )
		{
			if ( GetCurr()->IsDummy() )
				break;
			else
			{
				CalcAscentAndHeight( nAscent, nHeight );
				nDropHght = nDropHght + nHeight;
				bRegisterOn = bRegisterOld;
			}
			if ( !Next() )
			{
				nDropLns++; // Fix: 11356
				break;
			}
		}

		// In der letzten Zeile plumpsen wir auf den Zeilenascent!
		nDropHght = nDropHght - nHeight;
		nDropHght = nDropHght + nAscent;
		Top();
	}
	bRegisterOn = bRegisterOld;
	SetDropDescent( nHeight - nAscent );
	SetDropHeight( nDropHght );
	SetDropLines( nDropLns );
	// Alte Stelle wiederfinden!
	while( pOldCurr != GetCurr() )
	{
		if( !Next() )
		{
			ASSERT( sal_False, "SwTxtFormatter::_CalcDropHeight: left Toulouse" );
			break;
		}
	}
}

/*************************************************************************
 * SwTxtFormatter::GuessDropHeight()
 *
 * Wir schaetzen mal, dass die Fonthoehe sich nicht aendert und dass
 * erst mindestens soviele Zeilen gibt, wie die DropCap-Einstellung angibt.
 *
 *************************************************************************/



void SwTxtFormatter::GuessDropHeight( const MSHORT nLines )
{
	ASSERT( nLines, "GuessDropHeight: Give me more Lines!" );
	KSHORT nAscent = 0;
	KSHORT nHeight = 0;
	SetDropLines( nLines );
	if ( GetDropLines() > 1 )
	{
		CalcRealHeight();
		CalcAscentAndHeight( nAscent, nHeight );
	}
	SetDropDescent( nHeight - nAscent );
	SetDropHeight( nHeight * nLines - GetDropDescent() );
}

/*************************************************************************
 * SwTxtFormatter::NewDropPortion
 *************************************************************************/

SwDropPortion *SwTxtFormatter::NewDropPortion( SwTxtFormatInfo &rInf )
{
	if( !pDropFmt )
		return 0;

	xub_StrLen nPorLen = pDropFmt->GetWholeWord() ? 0 : pDropFmt->GetChars();
	nPorLen = pFrm->GetTxtNode()->GetDropLen( nPorLen );
	if( !nPorLen )
	{
		((SwTxtFormatter*)this)->ClearDropFmt();
		return 0;
	}

	SwDropPortion *pDropPor = 0;

	// erste oder zweite Runde?
	if ( !( GetDropHeight() || IsOnceMore() ) )
	{
		if ( GetNext() )
			CalcDropHeight( pDropFmt->GetLines() );
		else
			GuessDropHeight( pDropFmt->GetLines() );
	}

	// the DropPortion
	if( GetDropHeight() )
		pDropPor = new SwDropPortion( GetDropLines(), GetDropHeight(),
									  GetDropDescent(), pDropFmt->GetDistance() );
	else
		pDropPor = new SwDropPortion( 0,0,0,pDropFmt->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 )
	{
		((SwTxtFormatter*)this)->SetPaintDrop( sal_True );
		return pDropPor;
	}

	// build DropPortionParts:
	ASSERT( ! rInf.GetIdx(), "Drop Portion not at 0 position!" );
	xub_StrLen nNextChg = 0;
	const SwCharFmt* pFmt = pDropFmt->GetCharFmt();
	SwDropPortionPart* pCurrPart = 0;

	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 ( pFmt )
		{
			const SwAttrSet& rSet = pFmt->GetAttrSet();
			pTmpFnt->SetDiffFnt( &rSet, pFrm->GetTxtNode()->getIDocumentSettingAccess() );
		}

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

		// find next attribute change / script change
		const xub_StrLen nTmpIdx = nNextChg;
		xub_StrLen nNextAttr = Min( GetNextAttr(), rInf.GetTxt().Len() );
		nNextChg = pScriptInfo->NextScriptChg( nTmpIdx );
		if( nNextChg > nNextAttr )
			nNextChg = nNextAttr;
		if ( nNextChg > nPorLen )
			nNextChg = nPorLen;

		SwDropPortionPart* pPart =
				new SwDropPortionPart( *pTmpFnt, nNextChg - nTmpIdx );

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

		pCurrPart = pPart;
	}

	((SwTxtFormatter*)this)->SetPaintDrop( sal_True );
	return pDropPor;
}

/*************************************************************************
 * SwTxtPainter::PaintDropPortion()
 *************************************************************************/



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

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

	Top();

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

	// 8047: Drops und Dummies
	while( !pCurr->GetLen() && Next() )
		;

	// MarginPortion und Adjustment!
	const SwLinePortion *pPor = pCurr->GetFirstPortion();
	KSHORT nX = 0;
	while( pPor && !pPor->IsDropPortion() )
	{
		nX = nX + pPor->Width();
		pPor = pPor->GetPortion();
	}
	Point aLineOrigin( GetTopLeft() );

#ifdef NIE
	// Retusche nachholen...
	if( nX )
	{
		const Point aPoint( Left(), Y() );
		const Size aSize( nX - 1, GetDropHeight()+GetDropDescent() );
		SwRect aRetouche( aPoint, aSize );
		GetInfo().DrawRect( aRetouche );
	}
#endif

	aLineOrigin.X() += nX;
	KSHORT nTmpAscent, nTmpHeight;
	CalcAscentAndHeight( nTmpAscent, nTmpHeight );
	aLineOrigin.Y() += nTmpAscent;
	GetInfo().SetIdx( GetStart() );
	GetInfo().SetPos( aLineOrigin );
	GetInfo().SetLen( pDrop->GetLen() );

	pDrop->PaintDrop( GetInfo() );

	GetInfo().Y( nOldY );
}

/*************************************************************************
 * class SwDropCapCache
 *
 * Da die Berechnung der Fontgroesse der Initialen ein teures Geschaeft ist,
 * wird dies durch einen DropCapCache geschleust.
 *************************************************************************/

#define DROP_CACHE_SIZE 10

class SwDropCapCache
{
	long aMagicNo[ DROP_CACHE_SIZE ];
	XubString aTxt[ DROP_CACHE_SIZE ];
	sal_uInt16 aFactor[ DROP_CACHE_SIZE ];
	KSHORT aWishedHeight[ DROP_CACHE_SIZE ];
	short aDescent[ DROP_CACHE_SIZE ];
	MSHORT nIndex;
public:
	SwDropCapCache();
	~SwDropCapCache(){}
	void CalcFontSize( SwDropPortion* pDrop, SwTxtFormatInfo &rInf );
};

/*************************************************************************
 * SwDropCapCache Ctor / Dtor
 *************************************************************************/

SwDropCapCache::SwDropCapCache() : nIndex( 0 )
{
	memset( &aMagicNo, 0, sizeof(aMagicNo) );
	memset( &aWishedHeight, 0, sizeof(aWishedHeight) );
}

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

/*************************************************************************
 * SwDropCapCache::CalcFontSize
 *************************************************************************/

void SwDropCapCache::CalcFontSize( SwDropPortion* pDrop, SwTxtFormatInfo &rInf )
{
	const void* pFntNo = 0;
	MSHORT nTmpIdx = 0;

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

	SwDropPortionPart* pCurrPart = pDrop->GetPart();
	const sal_Bool bUseCache = ! pCurrPart->GetFollow();
	xub_StrLen nIdx = rInf.GetIdx();
	XubString aStr( rInf.GetTxt(), nIdx, pCurrPart->GetLen() );

	long nAscent = 0;
	long nDescent = 0;
	long nFactor = -1;

	if ( bUseCache )
	{
		SwFont& rFnt = pCurrPart->GetFont();
		rFnt.ChkMagic( rInf.GetVsh(), rFnt.GetActual() );
		rFnt.GetMagic( pFntNo, nTmpIdx, rFnt.GetActual() );

		nTmpIdx = 0;

		while( nTmpIdx < DROP_CACHE_SIZE &&
			( aTxt[ nTmpIdx ] != aStr || aMagicNo[ nTmpIdx ] != long(pFntNo) ||
			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 )
	{
		++nIndex;
		nIndex %= DROP_CACHE_SIZE;
		nTmpIdx = nIndex;

		long nWishedHeight = pDrop->GetDropHeight();

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

			pCurrPart = pCurrPart->GetFollow();
		}

		nFactor = ( 1000 * nWishedHeight ) / nMaxFontHeight;

		if ( bUseCache )
		{
			// save keys for cache
			aMagicNo[ nTmpIdx ] = long(pFntNo);
			aTxt[ nTmpIdx ] = aStr;
			aWishedHeight[ nTmpIdx ] = KSHORT(nWishedHeight);
			// save initial scaling factor
			aFactor[ nTmpIdx ] = (sal_uInt16)nFactor;
		}

		sal_Bool bGrow = ( pDrop->GetLen() != 0 );

		// for growing control
		long nMax = KSHRT_MAX;
		long nMin = nFactor / 2;
#if OSL_DEBUG_LEVEL > 1
		long nGrow = 0;
#endif

		sal_Bool bWinUsed = sal_False;
		Font aOldFnt;
		MapMode aOldMap( MAP_TWIP );
		OutputDevice* pOut = rInf.GetOut();
		OutputDevice* pWin;
		if( rInf.GetVsh() && rInf.GetVsh()->GetWin() )
			pWin = rInf.GetVsh()->GetWin();
		else
			pWin = GetpApp()->GetDefaultDevice();

		while( bGrow )
		{
			// reset pCurrPart to first part
			pCurrPart = pDrop->GetPart();
			sal_Bool bFirstGlyphRect = sal_True;
			sal_Bool bHaveGlyphRect = sal_False;
			Rectangle aCommonRect, aRect;

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

				// Get height including proportion
				const sal_uInt16 nCurrHeight =
						(sal_uInt16)rFnt.GetHeight( rFnt.GetActual() );

				// Get without proportion
				const sal_uInt8 nOldProp = rFnt.GetPropr();
				rFnt.SetProportion( 100 );
				Size aOldSize = Size( 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 );

				// Wir besorgen uns das alle Buchstaben umfassende Rechteck:
				bHaveGlyphRect = pOut->GetTextBoundRect( aRect, rInf.GetTxt(), 0,
									nIdx, 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 = sal_True;
							aOldMap = pWin->GetMapMode( );
							pWin->SetMapMode( MapMode( MAP_TWIP ) );
							aOldFnt = pWin->GetFont();
						}
						pWin->SetFont( rFnt.GetActualFont() );

						bHaveGlyphRect = pWin->GetTextBoundRect( aRect, rInf.GetTxt(), 0,
											nIdx, pCurrPart->GetLen() ) &&
										 ! aRect.IsEmpty();
					}
					if ( bHaveGlyphRect )
					{
						FontMetric aWinMet( pWin->GetFontMetric() );
						nAscent = (KSHORT) aWinMet.GetAscent();
					}
					else
					// We do not have a window or our window could not
					// give us glyph boundaries.
						aRect = Rectangle( Point( 0, 0 ), Size( 0, nAscent ) );
				}

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

				if ( bFirstGlyphRect )
				{
					aCommonRect = aRect;
					bFirstGlyphRect = sal_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.GetTxtFrm()->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 long nHght = nAscent + nDescent;
			if ( nHght )
			{
				if ( nHght > nWishedHeight )
					nMax = nFactor;
				else
				{
					if ( bUseCache )
						aFactor[ nTmpIdx ] = (sal_uInt16)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 = sal_False;
		}

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

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

	pCurrPart = pDrop->GetPart();

	// did we made any new calculations or did we use the cache?
	if ( -1 == nFactor )
	{
		nFactor = aFactor[ nTmpIdx ];
		nDescent = 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( (short)nDescent );
}

/*************************************************************************
 * virtual Format()
 *************************************************************************/

sal_Bool SwDropPortion::Format( SwTxtFormatInfo &rInf )
{
	sal_Bool bFull = sal_False;
	Fix( (sal_uInt16)rInf.X() );

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

	if( nDropHeight && pPart && nLines!=1 )
	{
		if( !pDropCapCache )
			pDropCapCache = new SwDropCapCache();

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

		const long nOldX = rInf.X();
		{
			SwDropSave aSave( rInf );
			SwDropPortionPart* pCurrPart = pPart;

			while ( pCurrPart )
			{
				rInf.SetLen( pCurrPart->GetLen() );
				SwFont& rFnt = pCurrPart->GetFont();
				{
					SwFontSave aFontSave( rInf, &rFnt );
					bFull = FormatTxt( rInf );

					if ( bFull )
						break;
				}

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

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

				// Move
				rInf.SetIdx( rInf.GetIdx() + pCurrPart->GetLen() );
				rInf.X( rInf.X() + nTmpWidth );
				pCurrPart = pCurrPart->GetFollow();
			}

			Width( (sal_uInt16)(rInf.X() - nOldX) );
		}

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

		// 7631, 7633: bei Ueberlappungen mit Flys ist Schluss.
		if( ! bFull )
			bFull = lcl_IsDropFlyInter( rInf, Width(), nDropHeight );

		if( bFull )
		{
			// Durch FormatTxt kann nHeight auf 0 gesetzt worden sein
			if ( !Height() )
				Height( rInf.GetTxtHeight() );

			// Jetzt noch einmal der ganze Spass
			nDropHeight = nLines = 0;
			delete pPart;
			pPart = NULL;

			// meanwhile use normal formatting
			bFull = SwTxtPortion::Format( rInf );
		}
		else
			rInf.SetDropInit( sal_True );

		Height( rInf.GetTxtHeight() );
		SetAscent( rInf.GetAscent() );
	}
	else
		bFull = SwTxtPortion::Format( rInf );

	if( bFull )
		nDistance = 0;
	else
	{
		const KSHORT nWant = Width() + GetDistance();
		const KSHORT nRest = (sal_uInt16)(rInf.Width() - rInf.X());
		if( ( nWant > nRest ) ||
			lcl_IsDropFlyInter( rInf, Width() + GetDistance(), nDropHeight ) )
			nDistance = 0;

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

/* vim: set noet sw=4 ts=4: */
