/**************************************************************
 *
 * 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_svx.hxx"

#include <svx/svdotext.hxx>
#include <svx/svdoutl.hxx>
#include <basegfx/vector/b2dvector.hxx>
#include <svx/sdr/primitive2d/sdrtextprimitive2d.hxx>
#include <drawinglayer/primitive2d/textprimitive2d.hxx>
#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
#include <basegfx/range/b2drange.hxx>
#include <editeng/editstat.hxx>
#include <vcl/salbtype.hxx>
#include <svx/sdtfchim.hxx>
#include <svl/itemset.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <drawinglayer/animation/animationtiming.hxx>
#include <basegfx/color/bcolor.hxx>
#include <vcl/svapp.hxx>
#include <editeng/eeitemid.hxx>
#include <editeng/escpitem.hxx>
#include <editeng/svxenum.hxx>
#include <editeng/flditem.hxx>
#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
#include <vcl/metaact.hxx>
#include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx>
#include <drawinglayer/primitive2d/graphicprimitive2d.hxx>
#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
#include <svx/unoapi.hxx>
#include <drawinglayer/geometry/viewinformation2d.hxx>
#include <editeng/outlobj.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>

//////////////////////////////////////////////////////////////////////////////
// helpers

namespace
{
	drawinglayer::primitive2d::Primitive2DSequence impConvertVectorToPrimitive2DSequence(const std::vector< drawinglayer::primitive2d::BasePrimitive2D* >& rPrimitiveVector)
	{
		const sal_Int32 nCount(rPrimitiveVector.size());
		drawinglayer::primitive2d::Primitive2DSequence aRetval(nCount);

		for(sal_Int32 a(0L); a < nCount; a++)
		{
			aRetval[a] = drawinglayer::primitive2d::Primitive2DReference(rPrimitiveVector[a]);
		}

		return aRetval;
	}

	class impTextBreakupHandler
	{
	private:
		std::vector< drawinglayer::primitive2d::BasePrimitive2D* >	maTextPortionPrimitives;
		std::vector< drawinglayer::primitive2d::BasePrimitive2D* >	maLinePrimitives;
		std::vector< drawinglayer::primitive2d::BasePrimitive2D* >	maParagraphPrimitives;

		SdrOutliner&												mrOutliner;
		basegfx::B2DHomMatrix										maNewTransformA;
		basegfx::B2DHomMatrix										maNewTransformB;

		// the visible area for contour text decomposition
		basegfx::B2DVector											maScale;

		// #SJ# ClipRange for BlockText decomposition; only text portions completely
		// inside are to be accepted, so this is different from geometric clipping
		// (which would allow e.g. upper parts of portions to remain). Only used for
		// BlockText (see there)
		basegfx::B2DRange											maClipRange;

		DECL_LINK(decomposeContourTextPrimitive, DrawPortionInfo* );
		DECL_LINK(decomposeBlockTextPrimitive, DrawPortionInfo* );
		DECL_LINK(decomposeStretchTextPrimitive, DrawPortionInfo* );

		DECL_LINK(decomposeContourBulletPrimitive, DrawBulletInfo* );
		DECL_LINK(decomposeBlockBulletPrimitive, DrawBulletInfo* );
		DECL_LINK(decomposeStretchBulletPrimitive, DrawBulletInfo* );

		bool impIsUnderlineAbove(const Font& rFont) const;
		void impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo);
		drawinglayer::primitive2d::BasePrimitive2D* impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo) const;
		void impFlushTextPortionPrimitivesToLinePrimitives();
		void impFlushLinePrimitivesToParagraphPrimitives();
		void impHandleDrawPortionInfo(const DrawPortionInfo& rInfo);
		void impHandleDrawBulletInfo(const DrawBulletInfo& rInfo);

	public:
		impTextBreakupHandler(SdrOutliner& rOutliner)
		:	maTextPortionPrimitives(),
			maLinePrimitives(),
			maParagraphPrimitives(),
			mrOutliner(rOutliner),
			maNewTransformA(),
			maNewTransformB(),
			maScale(),
			maClipRange()
		{
		}

		void decomposeContourTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB, const basegfx::B2DVector& rScale)
		{
			maScale = rScale;
			maNewTransformA = rNewTransformA;
			maNewTransformB = rNewTransformB;
			mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeContourTextPrimitive));
			mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeContourBulletPrimitive));
			mrOutliner.StripPortions();
			mrOutliner.SetDrawPortionHdl(Link());
			mrOutliner.SetDrawBulletHdl(Link());
		}

		void decomposeBlockTextPrimitive(
			const basegfx::B2DHomMatrix& rNewTransformA,
			const basegfx::B2DHomMatrix& rNewTransformB,
			const basegfx::B2DRange& rClipRange)
		{
			maNewTransformA = rNewTransformA;
			maNewTransformB = rNewTransformB;
			maClipRange = rClipRange;
			mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeBlockTextPrimitive));
			mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeBlockBulletPrimitive));
			mrOutliner.StripPortions();
			mrOutliner.SetDrawPortionHdl(Link());
			mrOutliner.SetDrawBulletHdl(Link());
		}

		void decomposeStretchTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB)
		{
			maNewTransformA = rNewTransformA;
			maNewTransformB = rNewTransformB;
			mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeStretchTextPrimitive));
			mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeStretchBulletPrimitive));
			mrOutliner.StripPortions();
			mrOutliner.SetDrawPortionHdl(Link());
			mrOutliner.SetDrawBulletHdl(Link());
		}

		drawinglayer::primitive2d::Primitive2DSequence getPrimitive2DSequence();
	};

	bool impTextBreakupHandler::impIsUnderlineAbove(const Font& rFont) const
	{
		if(!rFont.IsVertical())
		{
			return false;
		}

		if((LANGUAGE_JAPANESE == rFont.GetLanguage()) || (LANGUAGE_JAPANESE == rFont.GetCJKContextLanguage()))
		{
			// the underline is right for Japanese only
			return true;
		}

		return false;
	}

	void impTextBreakupHandler::impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo)
	{
		if(rInfo.mrText.Len() && rInfo.mnTextLen)
		{
			basegfx::B2DVector aFontScaling;
			drawinglayer::attribute::FontAttribute aFontAttribute(
				drawinglayer::primitive2d::getFontAttributeFromVclFont(
					aFontScaling,
					rInfo.mrFont,
					rInfo.IsRTL(),
					false));
			basegfx::B2DHomMatrix aNewTransform;

			// add font scale to new transform
			aNewTransform.scale(aFontScaling.getX(), aFontScaling.getY());

			// look for proportional font scaling, evtl scale accordingly
			if(100 != rInfo.mrFont.GetPropr())
			{
				const double fFactor(rInfo.mrFont.GetPropr() / 100.0);
				aNewTransform.scale(fFactor, fFactor);
			}

			// apply font rotate
			if(rInfo.mrFont.GetOrientation())
			{
				aNewTransform.rotate(-rInfo.mrFont.GetOrientation() * F_PI1800);
			}

			// look for escapement, evtl translate accordingly
			if(rInfo.mrFont.GetEscapement())
			{
				sal_Int16 nEsc(rInfo.mrFont.GetEscapement());

				if(DFLT_ESC_AUTO_SUPER == nEsc)
				{
					nEsc = 33;
				}
				else if(DFLT_ESC_AUTO_SUB == nEsc)
				{
					nEsc = -20;
				}

				if(nEsc > 100)
				{
					nEsc = 100;
				}
				else if(nEsc < -100)
				{
					nEsc = -100;
				}

				const double fEscapement(nEsc / -100.0);
				aNewTransform.translate(0.0, fEscapement * aFontScaling.getY());
			}

			// apply transformA
			aNewTransform *= maNewTransformA;

			// apply local offset
			aNewTransform.translate(rInfo.mrStartPos.X(), rInfo.mrStartPos.Y());

			// also apply embedding object's transform
			aNewTransform *= maNewTransformB;

			// prepare DXArray content. To make it independent from font size (and such from
			// the text transformation), scale it to unit coordinates
			::std::vector< double > aDXArray;
			static bool bDisableTextArray(false);

			if(!bDisableTextArray && rInfo.mpDXArray && rInfo.mnTextLen)
			{
				aDXArray.reserve(rInfo.mnTextLen);

				for(xub_StrLen a(0); a < rInfo.mnTextLen; a++)
				{
					aDXArray.push_back((double)rInfo.mpDXArray[a]);
				}
			}

			// create complex text primitive and append
			const Color aFontColor(rInfo.mrFont.GetColor());
			const basegfx::BColor aBFontColor(aFontColor.getBColor());

			// prepare wordLineMode (for underline and strikeout)
			// NOT for bullet texts. It is set (this may be an error by itself), but needs to be suppressed to hinder e.g. '1)'
			// to be split which would not look like the original
			const bool bWordLineMode(rInfo.mrFont.IsWordLineMode() && !rInfo.mbEndOfBullet);

			// prepare new primitive
			drawinglayer::primitive2d::BasePrimitive2D* pNewPrimitive = 0;
			const bool bDecoratedIsNeeded(
				   UNDERLINE_NONE != rInfo.mrFont.GetOverline()
				|| UNDERLINE_NONE != rInfo.mrFont.GetUnderline()
				|| STRIKEOUT_NONE != rInfo.mrFont.GetStrikeout()
				|| EMPHASISMARK_NONE != (rInfo.mrFont.GetEmphasisMark() & EMPHASISMARK_STYLE)
				|| RELIEF_NONE != rInfo.mrFont.GetRelief()
				|| rInfo.mrFont.IsShadow()
				|| bWordLineMode);

			if(bDecoratedIsNeeded)
			{
				// TextDecoratedPortionPrimitive2D needed, prepare some more data
				// get overline and underline color. If it's on automatic (0xffffffff) use FontColor instead
				const Color aUnderlineColor(rInfo.maTextLineColor);
				const basegfx::BColor aBUnderlineColor((0xffffffff == aUnderlineColor.GetColor()) ? aBFontColor : aUnderlineColor.getBColor());
				const Color aOverlineColor(rInfo.maOverlineColor);
				const basegfx::BColor aBOverlineColor((0xffffffff == aOverlineColor.GetColor()) ? aBFontColor : aOverlineColor.getBColor());

				// prepare overline and underline data
				const drawinglayer::primitive2d::TextLine eFontOverline(
					drawinglayer::primitive2d::mapFontUnderlineToTextLine(rInfo.mrFont.GetOverline()));
				const drawinglayer::primitive2d::TextLine eFontUnderline(
					drawinglayer::primitive2d::mapFontUnderlineToTextLine(rInfo.mrFont.GetUnderline()));

				// check UnderlineAbove
				const bool bUnderlineAbove(
					drawinglayer::primitive2d::TEXT_LINE_NONE != eFontUnderline && impIsUnderlineAbove(rInfo.mrFont));

				// prepare strikeout data
				const drawinglayer::primitive2d::TextStrikeout eTextStrikeout(
					drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rInfo.mrFont.GetStrikeout()));

				// prepare emphasis mark data
				drawinglayer::primitive2d::TextEmphasisMark eTextEmphasisMark(drawinglayer::primitive2d::TEXT_EMPHASISMARK_NONE);

				switch(rInfo.mrFont.GetEmphasisMark() & EMPHASISMARK_STYLE)
				{
					case EMPHASISMARK_DOT : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_EMPHASISMARK_DOT; break;
					case EMPHASISMARK_CIRCLE : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_EMPHASISMARK_CIRCLE; break;
					case EMPHASISMARK_DISC : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_EMPHASISMARK_DISC; break;
					case EMPHASISMARK_ACCENT : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_EMPHASISMARK_ACCENT; break;
				}

				const bool bEmphasisMarkAbove(rInfo.mrFont.GetEmphasisMark() & EMPHASISMARK_POS_ABOVE);
				const bool bEmphasisMarkBelow(rInfo.mrFont.GetEmphasisMark() & EMPHASISMARK_POS_BELOW);

				// prepare font relief data
				drawinglayer::primitive2d::TextRelief eTextRelief(drawinglayer::primitive2d::TEXT_RELIEF_NONE);

				switch(rInfo.mrFont.GetRelief())
				{
					case RELIEF_EMBOSSED : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_EMBOSSED; break;
					case RELIEF_ENGRAVED : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_ENGRAVED; break;
					default : break; // RELIEF_NONE, FontRelief_FORCE_EQUAL_SIZE
				}

				// prepare shadow/outline data
				const bool bShadow(rInfo.mrFont.IsShadow());

				// TextDecoratedPortionPrimitive2D is needed, create one
				pNewPrimitive = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(

					// attributes for TextSimplePortionPrimitive2D
					aNewTransform,
					rInfo.mrText,
					rInfo.mnTextStart,
					rInfo.mnTextLen,
					aDXArray,
					aFontAttribute,
					rInfo.mpLocale ? *rInfo.mpLocale : ::com::sun::star::lang::Locale(),
					aBFontColor,

					// attributes for TextDecoratedPortionPrimitive2D
					aBOverlineColor,
					aBUnderlineColor,
					eFontOverline,
					eFontUnderline,
					bUnderlineAbove,
					eTextStrikeout,
					bWordLineMode,
					eTextEmphasisMark,
					bEmphasisMarkAbove,
					bEmphasisMarkBelow,
					eTextRelief,
					bShadow);
			}
			else
			{
				// TextSimplePortionPrimitive2D is enough
				pNewPrimitive = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
					aNewTransform,
					rInfo.mrText,
					rInfo.mnTextStart,
					rInfo.mnTextLen,
					aDXArray,
					aFontAttribute,
					rInfo.mpLocale ? *rInfo.mpLocale : ::com::sun::star::lang::Locale(),
					aBFontColor);
			}

			if(rInfo.mbEndOfBullet)
			{
				// embed in TextHierarchyBulletPrimitive2D
				const drawinglayer::primitive2d::Primitive2DReference aNewReference(pNewPrimitive);
				const drawinglayer::primitive2d::Primitive2DSequence aNewSequence(&aNewReference, 1);
				pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(aNewSequence);
			}

			if(rInfo.mpFieldData)
			{
				pNewPrimitive = impCheckFieldPrimitive(pNewPrimitive, rInfo);
			}

			maTextPortionPrimitives.push_back(pNewPrimitive);

			// support for WrongSpellVector. Create WrongSpellPrimitives as needed
			if(rInfo.mpWrongSpellVector && !aDXArray.empty())
			{
				const sal_uInt32 nSize(rInfo.mpWrongSpellVector->size());
				const sal_uInt32 nDXCount(aDXArray.size());
				const basegfx::BColor aSpellColor(1.0, 0.0, 0.0); // red, hard coded

				for(sal_uInt32 a(0); a < nSize; a++)
				{
					const EEngineData::WrongSpellClass& rCandidate = (*rInfo.mpWrongSpellVector)[a];

					if(rCandidate.nStart >= rInfo.mnTextStart && rCandidate.nEnd >= rInfo.mnTextStart && rCandidate.nEnd > rCandidate.nStart)
					{
						const sal_uInt32 nStart(rCandidate.nStart - rInfo.mnTextStart);
						const sal_uInt32 nEnd(rCandidate.nEnd - rInfo.mnTextStart);
						double fStart(0.0);
						double fEnd(0.0);

						if(nStart > 0 && nStart - 1 < nDXCount)
						{
							fStart = aDXArray[nStart - 1];
						}

						if(nEnd > 0 && nEnd - 1 < nDXCount)
						{
							fEnd = aDXArray[nEnd - 1];
						}

						if(!basegfx::fTools::equal(fStart, fEnd))
						{
							if(rInfo.IsRTL())
							{
								// #i98523#
								// When the portion is RTL, mirror the redlining using the
								// full portion width
								const double fTextWidth(aDXArray[aDXArray.size() - 1]);

								fStart = fTextWidth - fStart;
								fEnd = fTextWidth - fEnd;
							}

							// need to take FontScaling out of values; it's already part of
							// aNewTransform and would be double applied
							const double fFontScaleX(aFontScaling.getX());

							if(!basegfx::fTools::equal(fFontScaleX, 1.0)
								&& !basegfx::fTools::equalZero(fFontScaleX))
							{
								fStart /= fFontScaleX;
								fEnd /= fFontScaleX;
							}

							maTextPortionPrimitives.push_back(new drawinglayer::primitive2d::WrongSpellPrimitive2D(
								aNewTransform,
								fStart,
								fEnd,
								aSpellColor));
						}
					}
				}
			}
		}
	}

	drawinglayer::primitive2d::BasePrimitive2D* impTextBreakupHandler::impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo) const
	{
		if(rInfo.mpFieldData)
		{
			// Support for FIELD_SEQ_BEGIN, FIELD_SEQ_END. If used, create a TextHierarchyFieldPrimitive2D
			// which holds the field type and evtl. the URL
			const SvxURLField* pURLField = dynamic_cast< const SvxURLField* >(rInfo.mpFieldData);
			const SvxPageField* pPageField = dynamic_cast< const SvxPageField* >(rInfo.mpFieldData);

			// embed current primitive to a sequence
			drawinglayer::primitive2d::Primitive2DSequence aSequence;

			if(pPrimitive)
			{
				aSequence.realloc(1);
				aSequence[0] = drawinglayer::primitive2d::Primitive2DReference(pPrimitive);
			}

			if(pURLField)
			{
				pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_URL, pURLField->GetURL());
			}
			else if(pPageField)
			{
				pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_PAGE, String());
			}
			else
			{
				pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_COMMON, String());
			}
		}

		return pPrimitive;
	}

	void impTextBreakupHandler::impFlushTextPortionPrimitivesToLinePrimitives()
	{
		// only create a line primitive when we had content; there is no need for
		// empty line primitives (contrary to paragraphs, see below).
		if(!maTextPortionPrimitives.empty())
		{
			drawinglayer::primitive2d::Primitive2DSequence aLineSequence(impConvertVectorToPrimitive2DSequence(maTextPortionPrimitives));
			maTextPortionPrimitives.clear();
			maLinePrimitives.push_back(new drawinglayer::primitive2d::TextHierarchyLinePrimitive2D(aLineSequence));
		}
	}

	void impTextBreakupHandler::impFlushLinePrimitivesToParagraphPrimitives()
	{
		// ALWAYS create a paragraph primitive, even when no content was added. This is done to
		// have the correct paragraph count even with empty paragraphs. Those paragraphs will
		// have an empty sub-PrimitiveSequence.
		drawinglayer::primitive2d::Primitive2DSequence aParagraphSequence(impConvertVectorToPrimitive2DSequence(maLinePrimitives));
		maLinePrimitives.clear();
		maParagraphPrimitives.push_back(new drawinglayer::primitive2d::TextHierarchyParagraphPrimitive2D(aParagraphSequence));
	}

	void impTextBreakupHandler::impHandleDrawPortionInfo(const DrawPortionInfo& rInfo)
	{
		impCreateTextPortionPrimitive(rInfo);

		if(rInfo.mbEndOfLine || rInfo.mbEndOfParagraph)
		{
			impFlushTextPortionPrimitivesToLinePrimitives();
		}

		if(rInfo.mbEndOfParagraph)
		{
			impFlushLinePrimitivesToParagraphPrimitives();
		}
	}

	void impTextBreakupHandler::impHandleDrawBulletInfo(const DrawBulletInfo& rInfo)
	{
		basegfx::B2DHomMatrix aNewTransform;

		// add size to new transform
		aNewTransform.scale(rInfo.maBulletSize.getWidth(), rInfo.maBulletSize.getHeight());

		// apply transformA
		aNewTransform *= maNewTransformA;

		// apply local offset
		aNewTransform.translate(rInfo.maBulletPosition.X(), rInfo.maBulletPosition.Y());

		// also apply embedding object's transform
		aNewTransform *= maNewTransformB;

		// prepare empty GraphicAttr
		const GraphicAttr aGraphicAttr;

		// create GraphicPrimitive2D
		const drawinglayer::primitive2d::Primitive2DReference aNewReference(new drawinglayer::primitive2d::GraphicPrimitive2D(
			aNewTransform,
			rInfo.maBulletGraphicObject,
			aGraphicAttr));

		// embed in TextHierarchyBulletPrimitive2D
		const drawinglayer::primitive2d::Primitive2DSequence aNewSequence(&aNewReference, 1);
		drawinglayer::primitive2d::BasePrimitive2D* pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(aNewSequence);

		// add to output
		maTextPortionPrimitives.push_back(pNewPrimitive);
	}

	IMPL_LINK(impTextBreakupHandler, decomposeContourTextPrimitive, DrawPortionInfo*, pInfo)
	{
		// for contour text, ignore (clip away) all portions which are below
		// the visible area given by maScale
		if(pInfo && (double)pInfo->mrStartPos.Y() < maScale.getY())
		{
			impHandleDrawPortionInfo(*pInfo);
		}

		return 0;
	}

	IMPL_LINK(impTextBreakupHandler, decomposeBlockTextPrimitive, DrawPortionInfo*, pInfo)
	{
		if(pInfo)
		{
			// #SJ# Is clipping wanted? This is text clipping; only accept a portion
			// if it's completely in the range
			if(!maClipRange.isEmpty())
			{
				// Test start position first; this allows to not get the text range at
				// all if text is far outside
				const basegfx::B2DPoint aStartPosition(pInfo->mrStartPos.X(), pInfo->mrStartPos.Y());

				if(!maClipRange.isInside(aStartPosition))
				{
					return 0;
				}

				// Start position is inside. Get TextBoundRect and TopLeft next
				drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
				aTextLayouterDevice.setFont(pInfo->mrFont);

				const basegfx::B2DRange aTextBoundRect(
					aTextLayouterDevice.getTextBoundRect(
						pInfo->mrText, pInfo->mnTextStart, pInfo->mnTextLen));
				const basegfx::B2DPoint aTopLeft(aTextBoundRect.getMinimum() + aStartPosition);

				if(!maClipRange.isInside(aTopLeft))
				{
					return 0;
				}

				// TopLeft is inside. Get BottomRight and check
				const basegfx::B2DPoint aBottomRight(aTextBoundRect.getMaximum() + aStartPosition);

				if(!maClipRange.isInside(aBottomRight))
				{
					return 0;
				}

				// all inside, clip was successful
			}
			impHandleDrawPortionInfo(*pInfo);
		}

		return 0;
	}

	IMPL_LINK(impTextBreakupHandler, decomposeStretchTextPrimitive, DrawPortionInfo*, pInfo)
	{
		if(pInfo)
		{
			impHandleDrawPortionInfo(*pInfo);
		}

		return 0;
	}

	IMPL_LINK(impTextBreakupHandler, decomposeContourBulletPrimitive, DrawBulletInfo*, pInfo)
	{
		if(pInfo)
		{
			impHandleDrawBulletInfo(*pInfo);
		}

		return 0;
	}

	IMPL_LINK(impTextBreakupHandler, decomposeBlockBulletPrimitive, DrawBulletInfo*, pInfo)
	{
		if(pInfo)
		{
			impHandleDrawBulletInfo(*pInfo);
		}

		return 0;
	}

	IMPL_LINK(impTextBreakupHandler, decomposeStretchBulletPrimitive, DrawBulletInfo*, pInfo)
	{
		if(pInfo)
		{
			impHandleDrawBulletInfo(*pInfo);
		}

		return 0;
	}

	drawinglayer::primitive2d::Primitive2DSequence impTextBreakupHandler::getPrimitive2DSequence()
	{
		if(!maTextPortionPrimitives.empty())
		{
			// collect non-closed lines
			impFlushTextPortionPrimitivesToLinePrimitives();
		}

		if(!maLinePrimitives.empty())
		{
			// collect non-closed paragraphs
			impFlushLinePrimitivesToParagraphPrimitives();
		}

		return impConvertVectorToPrimitive2DSequence(maParagraphPrimitives);
	}
} // end of anonymous namespace

//////////////////////////////////////////////////////////////////////////////
// primitive decompositions

void SdrTextObj::impDecomposeContourTextPrimitive(
	drawinglayer::primitive2d::Primitive2DSequence& rTarget,
	const drawinglayer::primitive2d::SdrContourTextPrimitive2D& rSdrContourTextPrimitive,
	const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
{
	// decompose matrix to have position and size of text
	basegfx::B2DVector aScale, aTranslate;
	double fRotate, fShearX;
	rSdrContourTextPrimitive.getObjectTransform().decompose(aScale, aTranslate, fRotate, fShearX);

	// prepare contour polygon, force to non-mirrored for layouting
	basegfx::B2DPolyPolygon aPolyPolygon(rSdrContourTextPrimitive.getUnitPolyPolygon());
	aPolyPolygon.transform(basegfx::tools::createScaleB2DHomMatrix(fabs(aScale.getX()), fabs(aScale.getY())));

	// prepare outliner
	SdrOutliner& rOutliner = ImpGetDrawOutliner();
	const Size aNullSize;
	rOutliner.SetPaperSize(aNullSize);
	rOutliner.SetPolygon(aPolyPolygon);
	rOutliner.SetUpdateMode(true);
	rOutliner.SetText(rSdrContourTextPrimitive.getOutlinerParaObject());

	// set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
	rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));

	// prepare matrices to apply to newly created primitives
	basegfx::B2DHomMatrix aNewTransformA;

	// mirroring. We are now in the polygon sizes. When mirroring in X and Y,
	// move the null point which was top left to bottom right.
	const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
	const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));

	// in-between the translations of the single primitives will take place. Afterwards,
	// the object's transformations need to be applied
	const basegfx::B2DHomMatrix aNewTransformB(basegfx::tools::createScaleShearXRotateTranslateB2DHomMatrix(
		bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
		fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));

	// now break up text primitives.
	impTextBreakupHandler aConverter(rOutliner);
	aConverter.decomposeContourTextPrimitive(aNewTransformA, aNewTransformB, aScale);

	// cleanup outliner
	rOutliner.Clear();
	rOutliner.setVisualizedPage(0);

	rTarget = aConverter.getPrimitive2DSequence();
}

void SdrTextObj::impDecomposeBlockTextPrimitive(
	drawinglayer::primitive2d::Primitive2DSequence& rTarget,
	const drawinglayer::primitive2d::SdrBlockTextPrimitive2D& rSdrBlockTextPrimitive,
	const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
{
	// decompose matrix to have position and size of text
	basegfx::B2DVector aScale, aTranslate;
	double fRotate, fShearX;
	rSdrBlockTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);

	// use B2DRange aAnchorTextRange for calculations
	basegfx::B2DRange aAnchorTextRange(aTranslate);
	aAnchorTextRange.expand(aTranslate + aScale);

	// prepare outliner
	const bool bIsCell(rSdrBlockTextPrimitive.getCellText());
	SdrOutliner& rOutliner = ImpGetDrawOutliner();
	SdrTextHorzAdjust eHAdj = rSdrBlockTextPrimitive.getSdrTextHorzAdjust();
	SdrTextVertAdjust eVAdj = rSdrBlockTextPrimitive.getSdrTextVertAdjust();
	const sal_uInt32 nOriginalControlWord(rOutliner.GetControlWord());
	const Size aNullSize;

	// set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
	rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
	rOutliner.SetFixedCellHeight(rSdrBlockTextPrimitive.isFixedCellHeight());
	rOutliner.SetControlWord(nOriginalControlWord|EE_CNTRL_AUTOPAGESIZE);
	rOutliner.SetMinAutoPaperSize(aNullSize);
	rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));

	// add one to rage sizes to get back to the old Rectangle and outliner measurements
	const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1L));
	const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1L));
	const bool bVerticalWritintg(rSdrBlockTextPrimitive.getOutlinerParaObject().IsVertical());
	const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight));

	if(bIsCell)
	{
		// cell text is formated neither like a text object nor like a object
		// text, so use a special setup here
		rOutliner.SetMaxAutoPaperSize(aAnchorTextSize);

		// #i106214# To work with an unchangeable PaperSize (CellSize in
		// this case) Set(Min|Max)AutoPaperSize and SetPaperSize have to be used.
		// #i106214# This was not completely correct; to still measure the real
		// text height to allow vertical adjust (and vice versa for VerticalWritintg)
		// only one aspect has to be set, but the other one to zero
		if(bVerticalWritintg)
		{
			// measure the horizontal text size
			rOutliner.SetMinAutoPaperSize(Size(0, aAnchorTextSize.Height()));
		}
		else
		{
			// measure the vertical text size
			rOutliner.SetMinAutoPaperSize(Size(aAnchorTextSize.Width(), 0));
		}

		rOutliner.SetPaperSize(aAnchorTextSize);
		rOutliner.SetUpdateMode(true);
		rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject());
	}
	else
	{
		// check if block text is used (only one of them can be true)
		const bool bHorizontalIsBlock(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWritintg);
		const bool bVerticalIsBlock(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWritintg);

		// set minimal paper size hor/ver if needed
		if(bHorizontalIsBlock)
		{
			rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0));
		}
		else if(bVerticalIsBlock)
		{
			rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight));
		}

		if((rSdrBlockTextPrimitive.getWordWrap() || IsTextFrame()) && !rSdrBlockTextPrimitive.getUnlimitedPage())
		{
			// #i103454# maximal paper size hor/ver needs to be limited to text
			// frame size. If it's block text, still allow the 'other' direction
			// to grow to get a correct real text size when using GetPaperSize().
			// When just using aAnchorTextSize as maximum, GetPaperSize()
			// would just return aAnchorTextSize again: this means, the wanted
			// 'measurement' of the real size of block text would not work
			Size aMaxAutoPaperSize(aAnchorTextSize);

			if(bHorizontalIsBlock)
			{
				// allow to grow vertical for horizontal blocks
				aMaxAutoPaperSize.setHeight(1000000);
			}
			else if(bVerticalIsBlock)
			{
				// allow to grow horizontal for vertical blocks
				aMaxAutoPaperSize.setWidth(1000000);
			}

			rOutliner.SetMaxAutoPaperSize(aMaxAutoPaperSize);
		}

		rOutliner.SetPaperSize(aNullSize);
		rOutliner.SetUpdateMode(true);
		rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject());
	}

	rOutliner.SetControlWord(nOriginalControlWord);

	// now get back the layouted text size from outliner
	const Size aOutlinerTextSiz(rOutliner.GetPaperSize());
	const basegfx::B2DVector aOutlinerScale(aOutlinerTextSiz.Width(), aOutlinerTextSiz.Height());
	basegfx::B2DVector aAdjustTranslate(0.0, 0.0);

	// For draw objects containing text correct hor/ver alignment if text is bigger
	// than the object itself. Without that correction, the text would always be
	// formatted to the left edge (or top edge when vertical) of the draw object.
	if(!IsTextFrame() && !bIsCell)
	{
		if(aAnchorTextRange.getWidth() < aOutlinerScale.getX() && !bVerticalWritintg)
		{
			// Horizontal case here. Correct only if eHAdj == SDRTEXTHORZADJUST_BLOCK,
			// else the alignment is wanted.
			if(SDRTEXTHORZADJUST_BLOCK == eHAdj)
			{
				eHAdj = SDRTEXTHORZADJUST_CENTER;
			}
		}

		if(aAnchorTextRange.getHeight() < aOutlinerScale.getY() && bVerticalWritintg)
		{
			// Vertical case here. Correct only if eHAdj == SDRTEXTVERTADJUST_BLOCK,
			// else the alignment is wanted.
			if(SDRTEXTVERTADJUST_BLOCK == eVAdj)
			{
				eVAdj = SDRTEXTVERTADJUST_CENTER;
			}
		}
	}

	// correct horizontal translation using the now known text size
	if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj)
	{
		const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX());

		if(SDRTEXTHORZADJUST_CENTER == eHAdj)
		{
			aAdjustTranslate.setX(fFree / 2.0);
		}

		if(SDRTEXTHORZADJUST_RIGHT == eHAdj)
		{
			aAdjustTranslate.setX(fFree);
		}
	}

	// correct vertical translation using the now known text size
	if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj)
	{
		const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY());

		if(SDRTEXTVERTADJUST_CENTER == eVAdj)
		{
			aAdjustTranslate.setY(fFree / 2.0);
		}

		if(SDRTEXTVERTADJUST_BOTTOM == eVAdj)
		{
			aAdjustTranslate.setY(fFree);
		}
	}

	// prepare matrices to apply to newly created primitives. aNewTransformA
	// will get coordinates in aOutlinerScale size and positive in X, Y.
	// Translate relative to given primitive to get same rotation and shear
	// as the master shape we are working on. For vertical, use the top-right
	// corner
	const double fStartInX(bVerticalWritintg ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX());
	const basegfx::B2DTuple aAdjOffset(fStartInX, aAdjustTranslate.getY());
	basegfx::B2DHomMatrix aNewTransformA(basegfx::tools::createTranslateB2DHomMatrix(aAdjOffset.getX(), aAdjOffset.getY()));

	// mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
	// move the null point which was top left to bottom right.
	const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
	const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));

	// in-between the translations of the single primitives will take place. Afterwards,
	// the object's transformations need to be applied
	const basegfx::B2DHomMatrix aNewTransformB(basegfx::tools::createScaleShearXRotateTranslateB2DHomMatrix(
		bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
		fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));

	// #SJ# create ClipRange (if needed)
	basegfx::B2DRange aClipRange;

	if(rSdrBlockTextPrimitive.getClipOnBounds())
	{
		aClipRange.expand(-aAdjOffset);
		aClipRange.expand(basegfx::B2DTuple(aAnchorTextSize.Width(), aAnchorTextSize.Height()) - aAdjOffset);
	}

	// now break up text primitives.
	impTextBreakupHandler aConverter(rOutliner);
	aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange);

	// cleanup outliner
	rOutliner.Clear();
	rOutliner.setVisualizedPage(0);

	rTarget = aConverter.getPrimitive2DSequence();
}

void SdrTextObj::impDecomposeStretchTextPrimitive(
	drawinglayer::primitive2d::Primitive2DSequence& rTarget,
	const drawinglayer::primitive2d::SdrStretchTextPrimitive2D& rSdrStretchTextPrimitive,
	const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
{
	// decompose matrix to have position and size of text
	basegfx::B2DVector aScale, aTranslate;
	double fRotate, fShearX;
	rSdrStretchTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);

	// use non-mirrored B2DRange aAnchorTextRange for calculations
	basegfx::B2DRange aAnchorTextRange(aTranslate);
	aAnchorTextRange.expand(aTranslate + aScale);

	// prepare outliner
	SdrOutliner& rOutliner = ImpGetDrawOutliner();
	const sal_uInt32 nOriginalControlWord(rOutliner.GetControlWord());
	const Size aNullSize;

	rOutliner.SetControlWord(nOriginalControlWord|EE_CNTRL_STRETCHING|EE_CNTRL_AUTOPAGESIZE);
	rOutliner.SetFixedCellHeight(rSdrStretchTextPrimitive.isFixedCellHeight());
	rOutliner.SetMinAutoPaperSize(aNullSize);
	rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
	rOutliner.SetPaperSize(aNullSize);
	rOutliner.SetUpdateMode(true);
	rOutliner.SetText(rSdrStretchTextPrimitive.getOutlinerParaObject());

	// set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
	rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));

	// now get back the layouted text size from outliner
	const Size aOutlinerTextSiz(rOutliner.CalcTextSize());
	const basegfx::B2DVector aOutlinerScale(
		basegfx::fTools::equalZero(aOutlinerTextSiz.Width()) ? 1.0 : aOutlinerTextSiz.Width(),
		basegfx::fTools::equalZero(aOutlinerTextSiz.Height()) ? 1.0 : aOutlinerTextSiz.Height());

	// prepare matrices to apply to newly created primitives
	basegfx::B2DHomMatrix aNewTransformA;

	// #i101957# Check for vertical text. If used, aNewTransformA
	// needs to translate the text initially around object width to orient
	// it relative to the topper right instead of the topper left
	const bool bVertical(rSdrStretchTextPrimitive.getOutlinerParaObject().IsVertical());

	if(bVertical)
	{
		aNewTransformA.translate(aScale.getX(), 0.0);
	}

	// calculate global char stretching scale parameters. Use non-mirrored sizes
	// to layout without mirroring
	const double fScaleX(fabs(aScale.getX()) / aOutlinerScale.getX());
	const double fScaleY(fabs(aScale.getY()) / aOutlinerScale.getY());
	rOutliner.SetGlobalCharStretching((sal_Int16)FRound(fScaleX * 100.0), (sal_Int16)FRound(fScaleY * 100.0));

	// mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
	// move the null point which was top left to bottom right.
	const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
	const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));

	// in-between the translations of the single primitives will take place. Afterwards,
	// the object's transformations need to be applied
	const basegfx::B2DHomMatrix aNewTransformB(basegfx::tools::createScaleShearXRotateTranslateB2DHomMatrix(
		bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
		fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));

	// now break up text primitives.
	impTextBreakupHandler aConverter(rOutliner);
	aConverter.decomposeStretchTextPrimitive(aNewTransformA, aNewTransformB);

	// cleanup outliner
	rOutliner.SetControlWord(nOriginalControlWord);
	rOutliner.Clear();
	rOutliner.setVisualizedPage(0);

	rTarget = aConverter.getPrimitive2DSequence();
}

//////////////////////////////////////////////////////////////////////////////
// timing generators
#define ENDLESS_LOOP	(0xffffffff)
#define ENDLESS_TIME	((double)0xffffffff)
#define PIXEL_DPI		(96.0)

void SdrTextObj::impGetBlinkTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList) const
{
	if(SDRTEXTANI_BLINK == GetTextAniKind())
	{
		// get values
		const SfxItemSet& rSet = GetObjectItemSet();
		const sal_uInt32 nRepeat((sal_uInt32)((SdrTextAniCountItem&)rSet.Get(SDRATTR_TEXT_ANICOUNT)).GetValue());
		bool bVisisbleWhenStopped(((SdrTextAniStopInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE)).GetValue());
		double fDelay((double)((SdrTextAniDelayItem&)rSet.Get(SDRATTR_TEXT_ANIDELAY)).GetValue());

		if(0.0 == fDelay)
		{
			// use default
			fDelay = 250.0;
		}

		// prepare loop and add
		drawinglayer::animation::AnimationEntryLoop  aLoop(nRepeat ? nRepeat : ENDLESS_LOOP);
		drawinglayer::animation::AnimationEntryFixed aStart(fDelay, 0.0);
		aLoop.append(aStart);
		drawinglayer::animation::AnimationEntryFixed aEnd(fDelay, 1.0);
		aLoop.append(aEnd);
		rAnimList.append(aLoop);

		// add stopped state if loop is not endless
		if(0L != nRepeat)
		{
			drawinglayer::animation::AnimationEntryFixed aStop(ENDLESS_TIME, bVisisbleWhenStopped ? 0.0 : 1.0);
			rAnimList.append(aStop);
		}
	}
}

void impCreateScrollTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency)
{
	bool bVisisbleWhenStopped(((SdrTextAniStopInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE)).GetValue());
	bool bVisisbleWhenStarted(((SdrTextAniStartInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE )).GetValue());
	const sal_uInt32 nRepeat(((SdrTextAniCountItem&)rSet.Get(SDRATTR_TEXT_ANICOUNT)).GetValue());

	if(bVisisbleWhenStarted)
	{
		// move from center to outside
		drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0);
		rAnimList.append(aInOut);
	}

	// loop. In loop, move through
	if(nRepeat || 0L == nRepeat)
	{
		drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat : ENDLESS_LOOP);
		drawinglayer::animation::AnimationEntryLinear aThrough(fTimeFullPath, fFrequency, bForward ? 0.0 : 1.0, bForward ? 1.0 : 0.0);
		aLoop.append(aThrough);
		rAnimList.append(aLoop);
	}

	if(0L != nRepeat && bVisisbleWhenStopped)
	{
		// move from outside to center
		drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5);
		rAnimList.append(aOutIn);

		// add timing for staying at the end
		drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
		rAnimList.append(aEnd);
	}
}

void impCreateAlternateTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, double fRelativeTextLength, bool bForward, double fTimeFullPath, double fFrequency)
{
	if(basegfx::fTools::more(fRelativeTextLength, 0.5))
	{
		// this is the case when fTextLength > fFrameLength, text is bigger than animation frame.
		// In that case, correct direction
		bForward = !bForward;
	}

	const double fStartPosition(bForward ? fRelativeTextLength : 1.0 - fRelativeTextLength);
	const double fEndPosition(bForward ? 1.0 - fRelativeTextLength : fRelativeTextLength);
	bool bVisisbleWhenStopped(((SdrTextAniStopInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE)).GetValue());
	bool bVisisbleWhenStarted(((SdrTextAniStartInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE )).GetValue());
	const sal_uInt32 nRepeat(((SdrTextAniCountItem&)rSet.Get(SDRATTR_TEXT_ANICOUNT)).GetValue());

	if(!bVisisbleWhenStarted)
	{
		// move from outside to center
		drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5);
		rAnimList.append(aOutIn);
	}

	// loop. In loop, move out and in again. fInnerMovePath may be negative when text is bigger then frame,
	// so use absolute value
	const double fInnerMovePath(fabs(1.0 - (fRelativeTextLength * 2.0)));
	const double fTimeForInnerPath(fTimeFullPath * fInnerMovePath);
	const double fHalfInnerPath(fTimeForInnerPath * 0.5);
	const sal_uInt32 nDoubleRepeat(nRepeat / 2L);

	if(nDoubleRepeat || 0L == nRepeat)
	{
		// double forth and back loop
		drawinglayer::animation::AnimationEntryLoop aLoop(nDoubleRepeat ? nDoubleRepeat : ENDLESS_LOOP);
		drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition);
		aLoop.append(aTime0);
		drawinglayer::animation::AnimationEntryLinear aTime1(fTimeForInnerPath, fFrequency, fEndPosition, fStartPosition);
		aLoop.append(aTime1);
		drawinglayer::animation::AnimationEntryLinear aTime2(fHalfInnerPath, fFrequency, fStartPosition, 0.5);
		aLoop.append(aTime2);
		rAnimList.append(aLoop);
	}

	if(nRepeat % 2L)
	{
		// repeat is uneven, so we need one more forth and back to center
		drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition);
		rAnimList.append(aTime0);
		drawinglayer::animation::AnimationEntryLinear aTime1(fHalfInnerPath, fFrequency, fEndPosition, 0.5);
		rAnimList.append(aTime1);
	}

	if(0L != nRepeat)
	{
		if(bVisisbleWhenStopped)
		{
			// add timing for staying at the end
			drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
			rAnimList.append(aEnd);
		}
		else
		{
			// move from center to outside
			drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0);
			rAnimList.append(aInOut);
		}
	}
}

void impCreateSlideTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency)
{
	// move in from outside, start outside
	const double fStartPosition(bForward ? 0.0 : 1.0);
	const sal_uInt32 nRepeat(((SdrTextAniCountItem&)rSet.Get(SDRATTR_TEXT_ANICOUNT)).GetValue());

	// move from outside to center
	drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5);
	rAnimList.append(aOutIn);

	// loop. In loop, move out and in again
	if(nRepeat > 1L || 0L == nRepeat)
	{
		drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat - 1L : ENDLESS_LOOP);
		drawinglayer::animation::AnimationEntryLinear aTime0(fTimeFullPath * 0.5, fFrequency, 0.5, fStartPosition);
		aLoop.append(aTime0);
		drawinglayer::animation::AnimationEntryLinear aTime1(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5);
		aLoop.append(aTime1);
		rAnimList.append(aLoop);
	}

	// always visible when stopped, so add timing for staying at the end when not endless
	if(0L != nRepeat)
	{
		drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
		rAnimList.append(aEnd);
	}
}

void SdrTextObj::impGetScrollTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList, double fFrameLength, double fTextLength) const
{
	const SdrTextAniKind eAniKind(GetTextAniKind());

	if(SDRTEXTANI_SCROLL == eAniKind || SDRTEXTANI_ALTERNATE == eAniKind || SDRTEXTANI_SLIDE == eAniKind)
	{
		// get data. Goal is to calculate fTimeFullPath which is the time needed to
		// move animation from (0.0) to (1.0) state
		const SfxItemSet& rSet = GetObjectItemSet();
		double fAnimationDelay((double)((SdrTextAniDelayItem&)rSet.Get(SDRATTR_TEXT_ANIDELAY)).GetValue());
		double fSingleStepWidth((double)((SdrTextAniAmountItem&)rSet.Get(SDRATTR_TEXT_ANIAMOUNT)).GetValue());
		const SdrTextAniDirection eDirection(GetTextAniDirection());
		const bool bForward(SDRTEXTANI_RIGHT == eDirection || SDRTEXTANI_DOWN == eDirection);

		if(basegfx::fTools::equalZero(fAnimationDelay))
		{
			// default to 1/20 second
			fAnimationDelay = 50.0;
		}

		if(basegfx::fTools::less(fSingleStepWidth, 0.0))
		{
			// data is in pixels, convert to logic. Imply PIXEL_DPI dpi.
			// It makes no sense to keep the view-transformation centered
			// definitions, so get rid of them here.
			fSingleStepWidth = (-fSingleStepWidth * (2540.0 / PIXEL_DPI));
		}

		if(basegfx::fTools::equalZero(fSingleStepWidth))
		{
			// default to 1 millimeter
			fSingleStepWidth = 100.0;
		}

		// use the length of the full animation path and the number of steps
		// to get the full path time
		const double fFullPathLength(fFrameLength + fTextLength);
		const double fNumberOfSteps(fFullPathLength / fSingleStepWidth);
		double fTimeFullPath(fNumberOfSteps * fAnimationDelay);

		if(fTimeFullPath < fAnimationDelay)
		{
			fTimeFullPath = fAnimationDelay;
		}

		switch(eAniKind)
		{
			case SDRTEXTANI_SCROLL :
			{
				impCreateScrollTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay);
				break;
			}
			case SDRTEXTANI_ALTERNATE :
			{
				double fRelativeTextLength(fTextLength / (fFrameLength + fTextLength));
				impCreateAlternateTiming(rSet, rAnimList, fRelativeTextLength, bForward, fTimeFullPath, fAnimationDelay);
				break;
			}
			case SDRTEXTANI_SLIDE :
			{
				impCreateSlideTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay);
				break;
			}
			default : break; // SDRTEXTANI_NONE, SDRTEXTANI_BLINK
		}
	}
}

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