xref: /AOO41X/main/svx/source/svdraw/svdotextdecomposition.cxx (revision bd1a492fb221f7df8374ef66fb89a1ec73de4776)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 // MARKER(update_precomp.py): autogen include statement, do not remove
23 #include "precompiled_svx.hxx"
24 
25 #include <svx/svdotext.hxx>
26 #include <svx/svdoutl.hxx>
27 #include <basegfx/vector/b2dvector.hxx>
28 #include <svx/sdr/primitive2d/sdrtextprimitive2d.hxx>
29 #include <drawinglayer/primitive2d/textprimitive2d.hxx>
30 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
31 #include <basegfx/range/b2drange.hxx>
32 #include <editeng/editstat.hxx>
33 #include <vcl/salbtype.hxx>
34 #include <svx/sdtfchim.hxx>
35 #include <svl/itemset.hxx>
36 #include <basegfx/polygon/b2dpolygontools.hxx>
37 #include <basegfx/polygon/b2dpolygon.hxx>
38 #include <drawinglayer/animation/animationtiming.hxx>
39 #include <basegfx/color/bcolor.hxx>
40 #include <vcl/svapp.hxx>
41 #include <editeng/eeitemid.hxx>
42 #include <editeng/escpitem.hxx>
43 #include <editeng/svxenum.hxx>
44 #include <editeng/flditem.hxx>
45 #include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
46 #include <vcl/metaact.hxx>
47 #include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx>
48 #include <drawinglayer/primitive2d/graphicprimitive2d.hxx>
49 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
50 #include <svx/unoapi.hxx>
51 #include <drawinglayer/geometry/viewinformation2d.hxx>
52 #include <editeng/outlobj.hxx>
53 #include <basegfx/matrix/b2dhommatrixtools.hxx>
54 
55 //////////////////////////////////////////////////////////////////////////////
56 // helpers
57 
58 namespace
59 {
60 	drawinglayer::primitive2d::Primitive2DSequence impConvertVectorToPrimitive2DSequence(const std::vector< drawinglayer::primitive2d::BasePrimitive2D* >& rPrimitiveVector)
61 	{
62 		const sal_Int32 nCount(rPrimitiveVector.size());
63 		drawinglayer::primitive2d::Primitive2DSequence aRetval(nCount);
64 
65 		for(sal_Int32 a(0L); a < nCount; a++)
66 		{
67 			aRetval[a] = drawinglayer::primitive2d::Primitive2DReference(rPrimitiveVector[a]);
68 		}
69 
70 		return aRetval;
71 	}
72 
73 	class impTextBreakupHandler
74 	{
75 	private:
76 		std::vector< drawinglayer::primitive2d::BasePrimitive2D* >	maTextPortionPrimitives;
77 		std::vector< drawinglayer::primitive2d::BasePrimitive2D* >	maLinePrimitives;
78 		std::vector< drawinglayer::primitive2d::BasePrimitive2D* >	maParagraphPrimitives;
79 
80 		SdrOutliner&												mrOutliner;
81 		basegfx::B2DHomMatrix										maNewTransformA;
82 		basegfx::B2DHomMatrix										maNewTransformB;
83 
84 		// the visible area for contour text decomposition
85 		basegfx::B2DVector											maScale;
86 
87 		// #SJ# ClipRange for BlockText decomposition; only text portions completely
88 		// inside are to be accepted, so this is different from geometric clipping
89 		// (which would allow e.g. upper parts of portions to remain). Only used for
90 		// BlockText (see there)
91 		basegfx::B2DRange											maClipRange;
92 
93 		DECL_LINK(decomposeContourTextPrimitive, DrawPortionInfo* );
94 		DECL_LINK(decomposeBlockTextPrimitive, DrawPortionInfo* );
95 		DECL_LINK(decomposeStretchTextPrimitive, DrawPortionInfo* );
96 
97 		DECL_LINK(decomposeContourBulletPrimitive, DrawBulletInfo* );
98 		DECL_LINK(decomposeBlockBulletPrimitive, DrawBulletInfo* );
99 		DECL_LINK(decomposeStretchBulletPrimitive, DrawBulletInfo* );
100 
101 		bool impIsUnderlineAbove(const Font& rFont) const;
102 		void impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo);
103 		drawinglayer::primitive2d::BasePrimitive2D* impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo) const;
104 		void impFlushTextPortionPrimitivesToLinePrimitives();
105 		void impFlushLinePrimitivesToParagraphPrimitives();
106 		void impHandleDrawPortionInfo(const DrawPortionInfo& rInfo);
107 		void impHandleDrawBulletInfo(const DrawBulletInfo& rInfo);
108 
109 	public:
110 		impTextBreakupHandler(SdrOutliner& rOutliner)
111 		:	maTextPortionPrimitives(),
112 			maLinePrimitives(),
113 			maParagraphPrimitives(),
114 			mrOutliner(rOutliner),
115 			maNewTransformA(),
116 			maNewTransformB(),
117 			maScale(),
118 			maClipRange()
119 		{
120 		}
121 
122 		void decomposeContourTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB, const basegfx::B2DVector& rScale)
123 		{
124 			maScale = rScale;
125 			maNewTransformA = rNewTransformA;
126 			maNewTransformB = rNewTransformB;
127 			mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeContourTextPrimitive));
128 			mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeContourBulletPrimitive));
129 			mrOutliner.StripPortions();
130 			mrOutliner.SetDrawPortionHdl(Link());
131 			mrOutliner.SetDrawBulletHdl(Link());
132 		}
133 
134 		void decomposeBlockTextPrimitive(
135 			const basegfx::B2DHomMatrix& rNewTransformA,
136 			const basegfx::B2DHomMatrix& rNewTransformB,
137 			const basegfx::B2DRange& rClipRange)
138 		{
139 			maNewTransformA = rNewTransformA;
140 			maNewTransformB = rNewTransformB;
141 			maClipRange = rClipRange;
142 			mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeBlockTextPrimitive));
143 			mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeBlockBulletPrimitive));
144 			mrOutliner.StripPortions();
145 			mrOutliner.SetDrawPortionHdl(Link());
146 			mrOutliner.SetDrawBulletHdl(Link());
147 		}
148 
149 		void decomposeStretchTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB)
150 		{
151 			maNewTransformA = rNewTransformA;
152 			maNewTransformB = rNewTransformB;
153 			mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeStretchTextPrimitive));
154 			mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeStretchBulletPrimitive));
155 			mrOutliner.StripPortions();
156 			mrOutliner.SetDrawPortionHdl(Link());
157 			mrOutliner.SetDrawBulletHdl(Link());
158 		}
159 
160 		drawinglayer::primitive2d::Primitive2DSequence getPrimitive2DSequence();
161 	};
162 
163 	bool impTextBreakupHandler::impIsUnderlineAbove(const Font& rFont) const
164 	{
165 		if(!rFont.IsVertical())
166 		{
167 			return false;
168 		}
169 
170 		if((LANGUAGE_JAPANESE == rFont.GetLanguage()) || (LANGUAGE_JAPANESE == rFont.GetCJKContextLanguage()))
171 		{
172 			// the underline is right for Japanese only
173 			return true;
174 		}
175 
176 		return false;
177 	}
178 
179 	void impTextBreakupHandler::impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo)
180 	{
181 		if(rInfo.mrText.Len() && rInfo.mnTextLen)
182 		{
183 			basegfx::B2DVector aFontScaling;
184 			drawinglayer::attribute::FontAttribute aFontAttribute(
185 				drawinglayer::primitive2d::getFontAttributeFromVclFont(
186 					aFontScaling,
187 					rInfo.mrFont,
188 					rInfo.IsRTL(),
189 					false));
190 			basegfx::B2DHomMatrix aNewTransform;
191 
192 			// add font scale to new transform
193 			aNewTransform.scale(aFontScaling.getX(), aFontScaling.getY());
194 
195 			// look for proportional font scaling, evtl scale accordingly
196 			if(100 != rInfo.mrFont.GetPropr())
197 			{
198 				const double fFactor(rInfo.mrFont.GetPropr() / 100.0);
199 				aNewTransform.scale(fFactor, fFactor);
200 			}
201 
202 			// apply font rotate
203 			if(rInfo.mrFont.GetOrientation())
204 			{
205 				aNewTransform.rotate(-rInfo.mrFont.GetOrientation() * F_PI1800);
206 			}
207 
208 			// look for escapement, evtl translate accordingly
209 			if(rInfo.mrFont.GetEscapement())
210 			{
211 				sal_Int16 nEsc(rInfo.mrFont.GetEscapement());
212 
213 				if(DFLT_ESC_AUTO_SUPER == nEsc)
214 				{
215 					nEsc = 33;
216 				}
217 				else if(DFLT_ESC_AUTO_SUB == nEsc)
218 				{
219 					nEsc = -20;
220 				}
221 
222 				if(nEsc > 100)
223 				{
224 					nEsc = 100;
225 				}
226 				else if(nEsc < -100)
227 				{
228 					nEsc = -100;
229 				}
230 
231 				const double fEscapement(nEsc / -100.0);
232 				aNewTransform.translate(0.0, fEscapement * aFontScaling.getY());
233 			}
234 
235 			// apply transformA
236 			aNewTransform *= maNewTransformA;
237 
238 			// apply local offset
239 			aNewTransform.translate(rInfo.mrStartPos.X(), rInfo.mrStartPos.Y());
240 
241 			// also apply embedding object's transform
242 			aNewTransform *= maNewTransformB;
243 
244 			// prepare DXArray content. To make it independent from font size (and such from
245 			// the text transformation), scale it to unit coordinates
246 			::std::vector< double > aDXArray;
247 			static bool bDisableTextArray(false);
248 
249 			if(!bDisableTextArray && rInfo.mpDXArray && rInfo.mnTextLen)
250 			{
251 				aDXArray.reserve(rInfo.mnTextLen);
252 
253 				for(xub_StrLen a(0); a < rInfo.mnTextLen; a++)
254 				{
255 					aDXArray.push_back((double)rInfo.mpDXArray[a]);
256 				}
257 			}
258 
259 			// create complex text primitive and append
260 			const Color aFontColor(rInfo.mrFont.GetColor());
261 			const basegfx::BColor aBFontColor(aFontColor.getBColor());
262 
263 			// prepare wordLineMode (for underline and strikeout)
264 			// NOT for bullet texts. It is set (this may be an error by itself), but needs to be suppressed to hinder e.g. '1)'
265 			// to be split which would not look like the original
266 			const bool bWordLineMode(rInfo.mrFont.IsWordLineMode() && !rInfo.mbEndOfBullet);
267 
268 			// prepare new primitive
269 			drawinglayer::primitive2d::BasePrimitive2D* pNewPrimitive = 0;
270 			const bool bDecoratedIsNeeded(
271 				   UNDERLINE_NONE != rInfo.mrFont.GetOverline()
272 				|| UNDERLINE_NONE != rInfo.mrFont.GetUnderline()
273 				|| STRIKEOUT_NONE != rInfo.mrFont.GetStrikeout()
274 				|| EMPHASISMARK_NONE != (rInfo.mrFont.GetEmphasisMark() & EMPHASISMARK_STYLE)
275 				|| RELIEF_NONE != rInfo.mrFont.GetRelief()
276 				|| rInfo.mrFont.IsShadow()
277 				|| bWordLineMode);
278 
279 			if(bDecoratedIsNeeded)
280 			{
281 				// TextDecoratedPortionPrimitive2D needed, prepare some more data
282 				// get overline and underline color. If it's on automatic (0xffffffff) use FontColor instead
283 				const Color aUnderlineColor(rInfo.maTextLineColor);
284 				const basegfx::BColor aBUnderlineColor((0xffffffff == aUnderlineColor.GetColor()) ? aBFontColor : aUnderlineColor.getBColor());
285 				const Color aOverlineColor(rInfo.maOverlineColor);
286 				const basegfx::BColor aBOverlineColor((0xffffffff == aOverlineColor.GetColor()) ? aBFontColor : aOverlineColor.getBColor());
287 
288 				// prepare overline and underline data
289 				const drawinglayer::primitive2d::TextLine eFontOverline(
290 					drawinglayer::primitive2d::mapFontUnderlineToTextLine(rInfo.mrFont.GetOverline()));
291 				const drawinglayer::primitive2d::TextLine eFontUnderline(
292 					drawinglayer::primitive2d::mapFontUnderlineToTextLine(rInfo.mrFont.GetUnderline()));
293 
294 				// check UnderlineAbove
295 				const bool bUnderlineAbove(
296 					drawinglayer::primitive2d::TEXT_LINE_NONE != eFontUnderline && impIsUnderlineAbove(rInfo.mrFont));
297 
298 				// prepare strikeout data
299 				const drawinglayer::primitive2d::TextStrikeout eTextStrikeout(
300 					drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rInfo.mrFont.GetStrikeout()));
301 
302 				// prepare emphasis mark data
303 				drawinglayer::primitive2d::TextEmphasisMark eTextEmphasisMark(drawinglayer::primitive2d::TEXT_EMPHASISMARK_NONE);
304 
305 				switch(rInfo.mrFont.GetEmphasisMark() & EMPHASISMARK_STYLE)
306 				{
307 					case EMPHASISMARK_DOT : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_EMPHASISMARK_DOT; break;
308 					case EMPHASISMARK_CIRCLE : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_EMPHASISMARK_CIRCLE; break;
309 					case EMPHASISMARK_DISC : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_EMPHASISMARK_DISC; break;
310 					case EMPHASISMARK_ACCENT : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_EMPHASISMARK_ACCENT; break;
311 				}
312 
313 				const bool bEmphasisMarkAbove(rInfo.mrFont.GetEmphasisMark() & EMPHASISMARK_POS_ABOVE);
314 				const bool bEmphasisMarkBelow(rInfo.mrFont.GetEmphasisMark() & EMPHASISMARK_POS_BELOW);
315 
316 				// prepare font relief data
317 				drawinglayer::primitive2d::TextRelief eTextRelief(drawinglayer::primitive2d::TEXT_RELIEF_NONE);
318 
319 				switch(rInfo.mrFont.GetRelief())
320 				{
321 					case RELIEF_EMBOSSED : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_EMBOSSED; break;
322 					case RELIEF_ENGRAVED : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_ENGRAVED; break;
323 					default : break; // RELIEF_NONE, FontRelief_FORCE_EQUAL_SIZE
324 				}
325 
326 				// prepare shadow/outline data
327 				const bool bShadow(rInfo.mrFont.IsShadow());
328 
329 				// TextDecoratedPortionPrimitive2D is needed, create one
330 				pNewPrimitive = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
331 
332 					// attributes for TextSimplePortionPrimitive2D
333 					aNewTransform,
334 					rInfo.mrText,
335 					rInfo.mnTextStart,
336 					rInfo.mnTextLen,
337 					aDXArray,
338 					aFontAttribute,
339 					rInfo.mpLocale ? *rInfo.mpLocale : ::com::sun::star::lang::Locale(),
340 					aBFontColor,
341 
342 					// attributes for TextDecoratedPortionPrimitive2D
343 					aBOverlineColor,
344 					aBUnderlineColor,
345 					eFontOverline,
346 					eFontUnderline,
347 					bUnderlineAbove,
348 					eTextStrikeout,
349 					bWordLineMode,
350 					eTextEmphasisMark,
351 					bEmphasisMarkAbove,
352 					bEmphasisMarkBelow,
353 					eTextRelief,
354 					bShadow);
355 			}
356 			else
357 			{
358 				// TextSimplePortionPrimitive2D is enough
359 				pNewPrimitive = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
360 					aNewTransform,
361 					rInfo.mrText,
362 					rInfo.mnTextStart,
363 					rInfo.mnTextLen,
364 					aDXArray,
365 					aFontAttribute,
366 					rInfo.mpLocale ? *rInfo.mpLocale : ::com::sun::star::lang::Locale(),
367 					aBFontColor);
368 			}
369 
370 			if(rInfo.mbEndOfBullet)
371 			{
372 				// embed in TextHierarchyBulletPrimitive2D
373 				const drawinglayer::primitive2d::Primitive2DReference aNewReference(pNewPrimitive);
374 				const drawinglayer::primitive2d::Primitive2DSequence aNewSequence(&aNewReference, 1);
375 				pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(aNewSequence);
376 			}
377 
378 			if(rInfo.mpFieldData)
379 			{
380 				pNewPrimitive = impCheckFieldPrimitive(pNewPrimitive, rInfo);
381 			}
382 
383 			maTextPortionPrimitives.push_back(pNewPrimitive);
384 
385 			// support for WrongSpellVector. Create WrongSpellPrimitives as needed
386 			if(rInfo.mpWrongSpellVector && !aDXArray.empty())
387 			{
388 				const sal_uInt32 nSize(rInfo.mpWrongSpellVector->size());
389 				const sal_uInt32 nDXCount(aDXArray.size());
390 				const basegfx::BColor aSpellColor(1.0, 0.0, 0.0); // red, hard coded
391 
392 				for(sal_uInt32 a(0); a < nSize; a++)
393 				{
394 					const EEngineData::WrongSpellClass& rCandidate = (*rInfo.mpWrongSpellVector)[a];
395 
396 					if(rCandidate.nStart >= rInfo.mnTextStart && rCandidate.nEnd >= rInfo.mnTextStart && rCandidate.nEnd > rCandidate.nStart)
397 					{
398 						const sal_uInt32 nStart(rCandidate.nStart - rInfo.mnTextStart);
399 						const sal_uInt32 nEnd(rCandidate.nEnd - rInfo.mnTextStart);
400 						double fStart(0.0);
401 						double fEnd(0.0);
402 
403 						if(nStart > 0 && nStart - 1 < nDXCount)
404 						{
405 							fStart = aDXArray[nStart - 1];
406 						}
407 
408 						if(nEnd > 0 && nEnd - 1 < nDXCount)
409 						{
410 							fEnd = aDXArray[nEnd - 1];
411 						}
412 
413 						if(!basegfx::fTools::equal(fStart, fEnd))
414 						{
415 							if(rInfo.IsRTL())
416 							{
417 								// #i98523#
418 								// When the portion is RTL, mirror the redlining using the
419 								// full portion width
420 								const double fTextWidth(aDXArray[aDXArray.size() - 1]);
421 
422 								fStart = fTextWidth - fStart;
423 								fEnd = fTextWidth - fEnd;
424 							}
425 
426 							// need to take FontScaling out of values; it's already part of
427 							// aNewTransform and would be double applied
428 							const double fFontScaleX(aFontScaling.getX());
429 
430 							if(!basegfx::fTools::equal(fFontScaleX, 1.0)
431 								&& !basegfx::fTools::equalZero(fFontScaleX))
432 							{
433 								fStart /= fFontScaleX;
434 								fEnd /= fFontScaleX;
435 							}
436 
437 							maTextPortionPrimitives.push_back(new drawinglayer::primitive2d::WrongSpellPrimitive2D(
438 								aNewTransform,
439 								fStart,
440 								fEnd,
441 								aSpellColor));
442 						}
443 					}
444 				}
445 			}
446 		}
447 	}
448 
449 	drawinglayer::primitive2d::BasePrimitive2D* impTextBreakupHandler::impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo) const
450 	{
451 		if(rInfo.mpFieldData)
452 		{
453 			// Support for FIELD_SEQ_BEGIN, FIELD_SEQ_END. If used, create a TextHierarchyFieldPrimitive2D
454 			// which holds the field type and evtl. the URL
455 			const SvxURLField* pURLField = dynamic_cast< const SvxURLField* >(rInfo.mpFieldData);
456 			const SvxPageField* pPageField = dynamic_cast< const SvxPageField* >(rInfo.mpFieldData);
457 
458 			// embed current primitive to a sequence
459 			drawinglayer::primitive2d::Primitive2DSequence aSequence;
460 
461 			if(pPrimitive)
462 			{
463 				aSequence.realloc(1);
464 				aSequence[0] = drawinglayer::primitive2d::Primitive2DReference(pPrimitive);
465 			}
466 
467 			if(pURLField)
468 			{
469 				pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_URL, pURLField->GetURL());
470 			}
471 			else if(pPageField)
472 			{
473 				pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_PAGE, String());
474 			}
475 			else
476 			{
477 				pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_COMMON, String());
478 			}
479 		}
480 
481 		return pPrimitive;
482 	}
483 
484 	void impTextBreakupHandler::impFlushTextPortionPrimitivesToLinePrimitives()
485 	{
486 		// only create a line primitive when we had content; there is no need for
487 		// empty line primitives (contrary to paragraphs, see below).
488 		if(!maTextPortionPrimitives.empty())
489 		{
490 			drawinglayer::primitive2d::Primitive2DSequence aLineSequence(impConvertVectorToPrimitive2DSequence(maTextPortionPrimitives));
491 			maTextPortionPrimitives.clear();
492 			maLinePrimitives.push_back(new drawinglayer::primitive2d::TextHierarchyLinePrimitive2D(aLineSequence));
493 		}
494 	}
495 
496 	void impTextBreakupHandler::impFlushLinePrimitivesToParagraphPrimitives()
497 	{
498 		// ALWAYS create a paragraph primitive, even when no content was added. This is done to
499 		// have the correct paragraph count even with empty paragraphs. Those paragraphs will
500 		// have an empty sub-PrimitiveSequence.
501 		drawinglayer::primitive2d::Primitive2DSequence aParagraphSequence(impConvertVectorToPrimitive2DSequence(maLinePrimitives));
502 		maLinePrimitives.clear();
503 		maParagraphPrimitives.push_back(new drawinglayer::primitive2d::TextHierarchyParagraphPrimitive2D(aParagraphSequence));
504 	}
505 
506 	void impTextBreakupHandler::impHandleDrawPortionInfo(const DrawPortionInfo& rInfo)
507 	{
508 		impCreateTextPortionPrimitive(rInfo);
509 
510 		if(rInfo.mbEndOfLine || rInfo.mbEndOfParagraph)
511 		{
512 			impFlushTextPortionPrimitivesToLinePrimitives();
513 		}
514 
515 		if(rInfo.mbEndOfParagraph)
516 		{
517 			impFlushLinePrimitivesToParagraphPrimitives();
518 		}
519 	}
520 
521 	void impTextBreakupHandler::impHandleDrawBulletInfo(const DrawBulletInfo& rInfo)
522 	{
523 		basegfx::B2DHomMatrix aNewTransform;
524 
525 		// add size to new transform
526 		aNewTransform.scale(rInfo.maBulletSize.getWidth(), rInfo.maBulletSize.getHeight());
527 
528 		// apply transformA
529 		aNewTransform *= maNewTransformA;
530 
531 		// apply local offset
532 		aNewTransform.translate(rInfo.maBulletPosition.X(), rInfo.maBulletPosition.Y());
533 
534 		// also apply embedding object's transform
535 		aNewTransform *= maNewTransformB;
536 
537 		// prepare empty GraphicAttr
538 		const GraphicAttr aGraphicAttr;
539 
540 		// create GraphicPrimitive2D
541 		const drawinglayer::primitive2d::Primitive2DReference aNewReference(new drawinglayer::primitive2d::GraphicPrimitive2D(
542 			aNewTransform,
543 			rInfo.maBulletGraphicObject,
544 			aGraphicAttr));
545 
546 		// embed in TextHierarchyBulletPrimitive2D
547 		const drawinglayer::primitive2d::Primitive2DSequence aNewSequence(&aNewReference, 1);
548 		drawinglayer::primitive2d::BasePrimitive2D* pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(aNewSequence);
549 
550 		// add to output
551 		maTextPortionPrimitives.push_back(pNewPrimitive);
552 	}
553 
554 	IMPL_LINK(impTextBreakupHandler, decomposeContourTextPrimitive, DrawPortionInfo*, pInfo)
555 	{
556 		// for contour text, ignore (clip away) all portions which are below
557 		// the visible area given by maScale
558 		if(pInfo && (double)pInfo->mrStartPos.Y() < maScale.getY())
559 		{
560 			impHandleDrawPortionInfo(*pInfo);
561 		}
562 
563 		return 0;
564 	}
565 
566 	IMPL_LINK(impTextBreakupHandler, decomposeBlockTextPrimitive, DrawPortionInfo*, pInfo)
567 	{
568 		if(pInfo)
569 		{
570 			// #SJ# Is clipping wanted? This is text clipping; only accept a portion
571 			// if it's completely in the range
572 			if(!maClipRange.isEmpty())
573 			{
574 				// Test start position first; this allows to not get the text range at
575 				// all if text is far outside
576 				const basegfx::B2DPoint aStartPosition(pInfo->mrStartPos.X(), pInfo->mrStartPos.Y());
577 
578 				if(!maClipRange.isInside(aStartPosition))
579 				{
580 					return 0;
581 				}
582 
583 				// Start position is inside. Get TextBoundRect and TopLeft next
584 				drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
585 				aTextLayouterDevice.setFont(pInfo->mrFont);
586 
587 				const basegfx::B2DRange aTextBoundRect(
588 					aTextLayouterDevice.getTextBoundRect(
589 						pInfo->mrText, pInfo->mnTextStart, pInfo->mnTextLen));
590 				const basegfx::B2DPoint aTopLeft(aTextBoundRect.getMinimum() + aStartPosition);
591 
592 				if(!maClipRange.isInside(aTopLeft))
593 				{
594 					return 0;
595 				}
596 
597 				// TopLeft is inside. Get BottomRight and check
598 				const basegfx::B2DPoint aBottomRight(aTextBoundRect.getMaximum() + aStartPosition);
599 
600 				if(!maClipRange.isInside(aBottomRight))
601 				{
602 					return 0;
603 				}
604 
605 				// all inside, clip was successful
606 			}
607 			impHandleDrawPortionInfo(*pInfo);
608 		}
609 
610 		return 0;
611 	}
612 
613 	IMPL_LINK(impTextBreakupHandler, decomposeStretchTextPrimitive, DrawPortionInfo*, pInfo)
614 	{
615 		if(pInfo)
616 		{
617 			impHandleDrawPortionInfo(*pInfo);
618 		}
619 
620 		return 0;
621 	}
622 
623 	IMPL_LINK(impTextBreakupHandler, decomposeContourBulletPrimitive, DrawBulletInfo*, pInfo)
624 	{
625 		if(pInfo)
626 		{
627 			impHandleDrawBulletInfo(*pInfo);
628 		}
629 
630 		return 0;
631 	}
632 
633 	IMPL_LINK(impTextBreakupHandler, decomposeBlockBulletPrimitive, DrawBulletInfo*, pInfo)
634 	{
635 		if(pInfo)
636 		{
637 			impHandleDrawBulletInfo(*pInfo);
638 		}
639 
640 		return 0;
641 	}
642 
643 	IMPL_LINK(impTextBreakupHandler, decomposeStretchBulletPrimitive, DrawBulletInfo*, pInfo)
644 	{
645 		if(pInfo)
646 		{
647 			impHandleDrawBulletInfo(*pInfo);
648 		}
649 
650 		return 0;
651 	}
652 
653 	drawinglayer::primitive2d::Primitive2DSequence impTextBreakupHandler::getPrimitive2DSequence()
654 	{
655 		if(!maTextPortionPrimitives.empty())
656 		{
657 			// collect non-closed lines
658 			impFlushTextPortionPrimitivesToLinePrimitives();
659 		}
660 
661 		if(!maLinePrimitives.empty())
662 		{
663 			// collect non-closed paragraphs
664 			impFlushLinePrimitivesToParagraphPrimitives();
665 		}
666 
667 		return impConvertVectorToPrimitive2DSequence(maParagraphPrimitives);
668 	}
669 } // end of anonymous namespace
670 
671 //////////////////////////////////////////////////////////////////////////////
672 // primitive decompositions
673 
674 void SdrTextObj::impDecomposeContourTextPrimitive(
675 	drawinglayer::primitive2d::Primitive2DSequence& rTarget,
676 	const drawinglayer::primitive2d::SdrContourTextPrimitive2D& rSdrContourTextPrimitive,
677 	const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
678 {
679 	// decompose matrix to have position and size of text
680 	basegfx::B2DVector aScale, aTranslate;
681 	double fRotate, fShearX;
682 	rSdrContourTextPrimitive.getObjectTransform().decompose(aScale, aTranslate, fRotate, fShearX);
683 
684 	// prepare contour polygon, force to non-mirrored for layouting
685 	basegfx::B2DPolyPolygon aPolyPolygon(rSdrContourTextPrimitive.getUnitPolyPolygon());
686 	aPolyPolygon.transform(basegfx::tools::createScaleB2DHomMatrix(fabs(aScale.getX()), fabs(aScale.getY())));
687 
688 	// prepare outliner
689 	SdrOutliner& rOutliner = ImpGetDrawOutliner();
690 	const Size aNullSize;
691 	rOutliner.SetPaperSize(aNullSize);
692 	rOutliner.SetPolygon(aPolyPolygon);
693 	rOutliner.SetUpdateMode(true);
694 	rOutliner.SetText(rSdrContourTextPrimitive.getOutlinerParaObject());
695 
696 	// set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
697 	rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
698 
699 	// prepare matrices to apply to newly created primitives
700 	basegfx::B2DHomMatrix aNewTransformA;
701 
702 	// mirroring. We are now in the polygon sizes. When mirroring in X and Y,
703 	// move the null point which was top left to bottom right.
704 	const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
705 	const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
706 
707 	// in-between the translations of the single primitives will take place. Afterwards,
708 	// the object's transformations need to be applied
709 	const basegfx::B2DHomMatrix aNewTransformB(basegfx::tools::createScaleShearXRotateTranslateB2DHomMatrix(
710 		bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
711 		fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
712 
713 	// now break up text primitives.
714 	impTextBreakupHandler aConverter(rOutliner);
715 	aConverter.decomposeContourTextPrimitive(aNewTransformA, aNewTransformB, aScale);
716 
717 	// cleanup outliner
718 	rOutliner.Clear();
719 	rOutliner.setVisualizedPage(0);
720 
721 	rTarget = aConverter.getPrimitive2DSequence();
722 }
723 
724 void SdrTextObj::impDecomposeBlockTextPrimitive(
725 	drawinglayer::primitive2d::Primitive2DSequence& rTarget,
726 	const drawinglayer::primitive2d::SdrBlockTextPrimitive2D& rSdrBlockTextPrimitive,
727 	const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
728 {
729 	// decompose matrix to have position and size of text
730 	basegfx::B2DVector aScale, aTranslate;
731 	double fRotate, fShearX;
732 	rSdrBlockTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
733 
734 	// use B2DRange aAnchorTextRange for calculations
735 	basegfx::B2DRange aAnchorTextRange(aTranslate);
736 	aAnchorTextRange.expand(aTranslate + aScale);
737 
738 	// prepare outliner
739 	const bool bIsCell(rSdrBlockTextPrimitive.getCellText());
740 	SdrOutliner& rOutliner = ImpGetDrawOutliner();
741 	SdrTextHorzAdjust eHAdj = rSdrBlockTextPrimitive.getSdrTextHorzAdjust();
742 	SdrTextVertAdjust eVAdj = rSdrBlockTextPrimitive.getSdrTextVertAdjust();
743 	const sal_uInt32 nOriginalControlWord(rOutliner.GetControlWord());
744 	const Size aNullSize;
745 
746 	// set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
747 	rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
748 	rOutliner.SetFixedCellHeight(rSdrBlockTextPrimitive.isFixedCellHeight());
749 	rOutliner.SetControlWord(nOriginalControlWord|EE_CNTRL_AUTOPAGESIZE);
750 	rOutliner.SetMinAutoPaperSize(aNullSize);
751 	rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
752 
753 	// add one to rage sizes to get back to the old Rectangle and outliner measurements
754 	const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1L));
755 	const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1L));
756 	const bool bVerticalWritintg(rSdrBlockTextPrimitive.getOutlinerParaObject().IsVertical());
757 	const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight));
758 
759 	if(bIsCell)
760 	{
761 		// cell text is formated neither like a text object nor like a object
762 		// text, so use a special setup here
763 		rOutliner.SetMaxAutoPaperSize(aAnchorTextSize);
764 
765 		// #i106214# To work with an unchangeable PaperSize (CellSize in
766 		// this case) Set(Min|Max)AutoPaperSize and SetPaperSize have to be used.
767 		// #i106214# This was not completely correct; to still measure the real
768 		// text height to allow vertical adjust (and vice versa for VerticalWritintg)
769 		// only one aspect has to be set, but the other one to zero
770 		if(bVerticalWritintg)
771 		{
772 			// measure the horizontal text size
773 			rOutliner.SetMinAutoPaperSize(Size(0, aAnchorTextSize.Height()));
774 		}
775 		else
776 		{
777 			// measure the vertical text size
778 			rOutliner.SetMinAutoPaperSize(Size(aAnchorTextSize.Width(), 0));
779 		}
780 
781 		rOutliner.SetPaperSize(aAnchorTextSize);
782 		rOutliner.SetUpdateMode(true);
783 		rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject());
784 	}
785 	else
786 	{
787 		// check if block text is used (only one of them can be true)
788 		const bool bHorizontalIsBlock(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWritintg);
789 		const bool bVerticalIsBlock(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWritintg);
790 
791 		// set minimal paper size hor/ver if needed
792 		if(bHorizontalIsBlock)
793 		{
794 			rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0));
795 		}
796 		else if(bVerticalIsBlock)
797 		{
798 			rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight));
799 		}
800 
801 		if((rSdrBlockTextPrimitive.getWordWrap() || IsTextFrame()) && !rSdrBlockTextPrimitive.getUnlimitedPage())
802 		{
803 			// #i103454# maximal paper size hor/ver needs to be limited to text
804 			// frame size. If it's block text, still allow the 'other' direction
805 			// to grow to get a correct real text size when using GetPaperSize().
806 			// When just using aAnchorTextSize as maximum, GetPaperSize()
807 			// would just return aAnchorTextSize again: this means, the wanted
808 			// 'measurement' of the real size of block text would not work
809 			Size aMaxAutoPaperSize(aAnchorTextSize);
810 
811 			if(bHorizontalIsBlock)
812 			{
813 				// allow to grow vertical for horizontal blocks
814 				aMaxAutoPaperSize.setHeight(1000000);
815 			}
816 			else if(bVerticalIsBlock)
817 			{
818 				// allow to grow horizontal for vertical blocks
819 				aMaxAutoPaperSize.setWidth(1000000);
820 			}
821 
822 			rOutliner.SetMaxAutoPaperSize(aMaxAutoPaperSize);
823 		}
824 
825 		rOutliner.SetPaperSize(aNullSize);
826 		rOutliner.SetUpdateMode(true);
827 		rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject());
828 	}
829 
830 	rOutliner.SetControlWord(nOriginalControlWord);
831 
832 	// now get back the layouted text size from outliner
833 	const Size aOutlinerTextSiz(rOutliner.GetPaperSize());
834 	const basegfx::B2DVector aOutlinerScale(aOutlinerTextSiz.Width(), aOutlinerTextSiz.Height());
835 	basegfx::B2DVector aAdjustTranslate(0.0, 0.0);
836 
837 	// For draw objects containing text correct hor/ver alignment if text is bigger
838 	// than the object itself. Without that correction, the text would always be
839 	// formatted to the left edge (or top edge when vertical) of the draw object.
840 	if(!IsTextFrame() && !bIsCell)
841 	{
842 		if(aAnchorTextRange.getWidth() < aOutlinerScale.getX() && !bVerticalWritintg)
843 		{
844 			// Horizontal case here. Correct only if eHAdj == SDRTEXTHORZADJUST_BLOCK,
845 			// else the alignment is wanted.
846 			if(SDRTEXTHORZADJUST_BLOCK == eHAdj)
847 			{
848 				eHAdj = SDRTEXTHORZADJUST_CENTER;
849 			}
850 		}
851 
852 		if(aAnchorTextRange.getHeight() < aOutlinerScale.getY() && bVerticalWritintg)
853 		{
854 			// Vertical case here. Correct only if eHAdj == SDRTEXTVERTADJUST_BLOCK,
855 			// else the alignment is wanted.
856 			if(SDRTEXTVERTADJUST_BLOCK == eVAdj)
857 			{
858 				eVAdj = SDRTEXTVERTADJUST_CENTER;
859 			}
860 		}
861 	}
862 
863 	// correct horizontal translation using the now known text size
864 	if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj)
865 	{
866 		const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX());
867 
868 		if(SDRTEXTHORZADJUST_CENTER == eHAdj)
869 		{
870 			aAdjustTranslate.setX(fFree / 2.0);
871 		}
872 
873 		if(SDRTEXTHORZADJUST_RIGHT == eHAdj)
874 		{
875 			aAdjustTranslate.setX(fFree);
876 		}
877 	}
878 
879 	// correct vertical translation using the now known text size
880 	if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj)
881 	{
882 		const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY());
883 
884 		if(SDRTEXTVERTADJUST_CENTER == eVAdj)
885 		{
886 			aAdjustTranslate.setY(fFree / 2.0);
887 		}
888 
889 		if(SDRTEXTVERTADJUST_BOTTOM == eVAdj)
890 		{
891 			aAdjustTranslate.setY(fFree);
892 		}
893 	}
894 
895 	// prepare matrices to apply to newly created primitives. aNewTransformA
896 	// will get coordinates in aOutlinerScale size and positive in X, Y.
897 	// Translate relative to given primitive to get same rotation and shear
898 	// as the master shape we are working on. For vertical, use the top-right
899 	// corner
900 	const double fStartInX(bVerticalWritintg ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX());
901 	const basegfx::B2DTuple aAdjOffset(fStartInX, aAdjustTranslate.getY());
902 	basegfx::B2DHomMatrix aNewTransformA(basegfx::tools::createTranslateB2DHomMatrix(aAdjOffset.getX(), aAdjOffset.getY()));
903 
904 	// mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
905 	// move the null point which was top left to bottom right.
906 	const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
907 	const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
908 
909 	// in-between the translations of the single primitives will take place. Afterwards,
910 	// the object's transformations need to be applied
911 	const basegfx::B2DHomMatrix aNewTransformB(basegfx::tools::createScaleShearXRotateTranslateB2DHomMatrix(
912 		bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
913 		fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
914 
915 	// #SJ# create ClipRange (if needed)
916 	basegfx::B2DRange aClipRange;
917 
918 	if(rSdrBlockTextPrimitive.getClipOnBounds())
919 	{
920 		aClipRange.expand(-aAdjOffset);
921 		aClipRange.expand(basegfx::B2DTuple(aAnchorTextSize.Width(), aAnchorTextSize.Height()) - aAdjOffset);
922 	}
923 
924 	// now break up text primitives.
925 	impTextBreakupHandler aConverter(rOutliner);
926 	aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange);
927 
928 	// cleanup outliner
929 	rOutliner.Clear();
930 	rOutliner.setVisualizedPage(0);
931 
932 	rTarget = aConverter.getPrimitive2DSequence();
933 }
934 
935 void SdrTextObj::impDecomposeStretchTextPrimitive(
936 	drawinglayer::primitive2d::Primitive2DSequence& rTarget,
937 	const drawinglayer::primitive2d::SdrStretchTextPrimitive2D& rSdrStretchTextPrimitive,
938 	const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
939 {
940 	// decompose matrix to have position and size of text
941 	basegfx::B2DVector aScale, aTranslate;
942 	double fRotate, fShearX;
943 	rSdrStretchTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
944 
945 	// use non-mirrored B2DRange aAnchorTextRange for calculations
946 	basegfx::B2DRange aAnchorTextRange(aTranslate);
947 	aAnchorTextRange.expand(aTranslate + aScale);
948 
949 	// prepare outliner
950 	SdrOutliner& rOutliner = ImpGetDrawOutliner();
951 	const sal_uInt32 nOriginalControlWord(rOutliner.GetControlWord());
952 	const Size aNullSize;
953 
954 	rOutliner.SetControlWord(nOriginalControlWord|EE_CNTRL_STRETCHING|EE_CNTRL_AUTOPAGESIZE);
955 	rOutliner.SetFixedCellHeight(rSdrStretchTextPrimitive.isFixedCellHeight());
956 	rOutliner.SetMinAutoPaperSize(aNullSize);
957 	rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
958 	rOutliner.SetPaperSize(aNullSize);
959 	rOutliner.SetUpdateMode(true);
960 	rOutliner.SetText(rSdrStretchTextPrimitive.getOutlinerParaObject());
961 
962 	// set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
963 	rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
964 
965 	// now get back the layouted text size from outliner
966 	const Size aOutlinerTextSiz(rOutliner.CalcTextSize());
967 	const basegfx::B2DVector aOutlinerScale(
968 		basegfx::fTools::equalZero(aOutlinerTextSiz.Width()) ? 1.0 : aOutlinerTextSiz.Width(),
969 		basegfx::fTools::equalZero(aOutlinerTextSiz.Height()) ? 1.0 : aOutlinerTextSiz.Height());
970 
971 	// prepare matrices to apply to newly created primitives
972 	basegfx::B2DHomMatrix aNewTransformA;
973 
974 	// #i101957# Check for vertical text. If used, aNewTransformA
975 	// needs to translate the text initially around object width to orient
976 	// it relative to the topper right instead of the topper left
977 	const bool bVertical(rSdrStretchTextPrimitive.getOutlinerParaObject().IsVertical());
978 
979 	if(bVertical)
980 	{
981 		aNewTransformA.translate(aScale.getX(), 0.0);
982 	}
983 
984 	// calculate global char stretching scale parameters. Use non-mirrored sizes
985 	// to layout without mirroring
986 	const double fScaleX(fabs(aScale.getX()) / aOutlinerScale.getX());
987 	const double fScaleY(fabs(aScale.getY()) / aOutlinerScale.getY());
988 	rOutliner.SetGlobalCharStretching((sal_Int16)FRound(fScaleX * 100.0), (sal_Int16)FRound(fScaleY * 100.0));
989 
990 	// mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
991 	// move the null point which was top left to bottom right.
992 	const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
993 	const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
994 
995 	// in-between the translations of the single primitives will take place. Afterwards,
996 	// the object's transformations need to be applied
997 	const basegfx::B2DHomMatrix aNewTransformB(basegfx::tools::createScaleShearXRotateTranslateB2DHomMatrix(
998 		bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
999 		fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
1000 
1001 	// now break up text primitives.
1002 	impTextBreakupHandler aConverter(rOutliner);
1003 	aConverter.decomposeStretchTextPrimitive(aNewTransformA, aNewTransformB);
1004 
1005 	// cleanup outliner
1006 	rOutliner.SetControlWord(nOriginalControlWord);
1007 	rOutliner.Clear();
1008 	rOutliner.setVisualizedPage(0);
1009 
1010 	rTarget = aConverter.getPrimitive2DSequence();
1011 }
1012 
1013 //////////////////////////////////////////////////////////////////////////////
1014 // timing generators
1015 #define ENDLESS_LOOP	(0xffffffff)
1016 #define ENDLESS_TIME	((double)0xffffffff)
1017 #define PIXEL_DPI		(96.0)
1018 
1019 void SdrTextObj::impGetBlinkTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList) const
1020 {
1021 	if(SDRTEXTANI_BLINK == GetTextAniKind())
1022 	{
1023 		// get values
1024 		const SfxItemSet& rSet = GetObjectItemSet();
1025 		const sal_uInt32 nRepeat((sal_uInt32)((SdrTextAniCountItem&)rSet.Get(SDRATTR_TEXT_ANICOUNT)).GetValue());
1026 		bool bVisisbleWhenStopped(((SdrTextAniStopInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE)).GetValue());
1027 		double fDelay((double)((SdrTextAniDelayItem&)rSet.Get(SDRATTR_TEXT_ANIDELAY)).GetValue());
1028 
1029 		if(0.0 == fDelay)
1030 		{
1031 			// use default
1032 			fDelay = 250.0;
1033 		}
1034 
1035 		// prepare loop and add
1036 		drawinglayer::animation::AnimationEntryLoop  aLoop(nRepeat ? nRepeat : ENDLESS_LOOP);
1037 		drawinglayer::animation::AnimationEntryFixed aStart(fDelay, 0.0);
1038 		aLoop.append(aStart);
1039 		drawinglayer::animation::AnimationEntryFixed aEnd(fDelay, 1.0);
1040 		aLoop.append(aEnd);
1041 		rAnimList.append(aLoop);
1042 
1043 		// add stopped state if loop is not endless
1044 		if(0L != nRepeat)
1045 		{
1046 			drawinglayer::animation::AnimationEntryFixed aStop(ENDLESS_TIME, bVisisbleWhenStopped ? 0.0 : 1.0);
1047 			rAnimList.append(aStop);
1048 		}
1049 	}
1050 }
1051 
1052 void impCreateScrollTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency)
1053 {
1054 	bool bVisisbleWhenStopped(((SdrTextAniStopInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE)).GetValue());
1055 	bool bVisisbleWhenStarted(((SdrTextAniStartInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE )).GetValue());
1056 	const sal_uInt32 nRepeat(((SdrTextAniCountItem&)rSet.Get(SDRATTR_TEXT_ANICOUNT)).GetValue());
1057 
1058 	if(bVisisbleWhenStarted)
1059 	{
1060 		// move from center to outside
1061 		drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0);
1062 		rAnimList.append(aInOut);
1063 	}
1064 
1065 	// loop. In loop, move through
1066 	if(nRepeat || 0L == nRepeat)
1067 	{
1068 		drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat : ENDLESS_LOOP);
1069 		drawinglayer::animation::AnimationEntryLinear aThrough(fTimeFullPath, fFrequency, bForward ? 0.0 : 1.0, bForward ? 1.0 : 0.0);
1070 		aLoop.append(aThrough);
1071 		rAnimList.append(aLoop);
1072 	}
1073 
1074 	if(0L != nRepeat && bVisisbleWhenStopped)
1075 	{
1076 		// move from outside to center
1077 		drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5);
1078 		rAnimList.append(aOutIn);
1079 
1080 		// add timing for staying at the end
1081 		drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1082 		rAnimList.append(aEnd);
1083 	}
1084 }
1085 
1086 void impCreateAlternateTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, double fRelativeTextLength, bool bForward, double fTimeFullPath, double fFrequency)
1087 {
1088 	if(basegfx::fTools::more(fRelativeTextLength, 0.5))
1089 	{
1090 		// this is the case when fTextLength > fFrameLength, text is bigger than animation frame.
1091 		// In that case, correct direction
1092 		bForward = !bForward;
1093 	}
1094 
1095 	const double fStartPosition(bForward ? fRelativeTextLength : 1.0 - fRelativeTextLength);
1096 	const double fEndPosition(bForward ? 1.0 - fRelativeTextLength : fRelativeTextLength);
1097 	bool bVisisbleWhenStopped(((SdrTextAniStopInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE)).GetValue());
1098 	bool bVisisbleWhenStarted(((SdrTextAniStartInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE )).GetValue());
1099 	const sal_uInt32 nRepeat(((SdrTextAniCountItem&)rSet.Get(SDRATTR_TEXT_ANICOUNT)).GetValue());
1100 
1101 	if(!bVisisbleWhenStarted)
1102 	{
1103 		// move from outside to center
1104 		drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5);
1105 		rAnimList.append(aOutIn);
1106 	}
1107 
1108 	// loop. In loop, move out and in again. fInnerMovePath may be negative when text is bigger then frame,
1109 	// so use absolute value
1110 	const double fInnerMovePath(fabs(1.0 - (fRelativeTextLength * 2.0)));
1111 	const double fTimeForInnerPath(fTimeFullPath * fInnerMovePath);
1112 	const double fHalfInnerPath(fTimeForInnerPath * 0.5);
1113 	const sal_uInt32 nDoubleRepeat(nRepeat / 2L);
1114 
1115 	if(nDoubleRepeat || 0L == nRepeat)
1116 	{
1117 		// double forth and back loop
1118 		drawinglayer::animation::AnimationEntryLoop aLoop(nDoubleRepeat ? nDoubleRepeat : ENDLESS_LOOP);
1119 		drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition);
1120 		aLoop.append(aTime0);
1121 		drawinglayer::animation::AnimationEntryLinear aTime1(fTimeForInnerPath, fFrequency, fEndPosition, fStartPosition);
1122 		aLoop.append(aTime1);
1123 		drawinglayer::animation::AnimationEntryLinear aTime2(fHalfInnerPath, fFrequency, fStartPosition, 0.5);
1124 		aLoop.append(aTime2);
1125 		rAnimList.append(aLoop);
1126 	}
1127 
1128 	if(nRepeat % 2L)
1129 	{
1130 		// repeat is uneven, so we need one more forth and back to center
1131 		drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition);
1132 		rAnimList.append(aTime0);
1133 		drawinglayer::animation::AnimationEntryLinear aTime1(fHalfInnerPath, fFrequency, fEndPosition, 0.5);
1134 		rAnimList.append(aTime1);
1135 	}
1136 
1137 	if(0L != nRepeat)
1138 	{
1139 		if(bVisisbleWhenStopped)
1140 		{
1141 			// add timing for staying at the end
1142 			drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1143 			rAnimList.append(aEnd);
1144 		}
1145 		else
1146 		{
1147 			// move from center to outside
1148 			drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0);
1149 			rAnimList.append(aInOut);
1150 		}
1151 	}
1152 }
1153 
1154 void impCreateSlideTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency)
1155 {
1156 	// move in from outside, start outside
1157 	const double fStartPosition(bForward ? 0.0 : 1.0);
1158 	const sal_uInt32 nRepeat(((SdrTextAniCountItem&)rSet.Get(SDRATTR_TEXT_ANICOUNT)).GetValue());
1159 
1160 	// move from outside to center
1161 	drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5);
1162 	rAnimList.append(aOutIn);
1163 
1164 	// loop. In loop, move out and in again
1165 	if(nRepeat > 1L || 0L == nRepeat)
1166 	{
1167 		drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat - 1L : ENDLESS_LOOP);
1168 		drawinglayer::animation::AnimationEntryLinear aTime0(fTimeFullPath * 0.5, fFrequency, 0.5, fStartPosition);
1169 		aLoop.append(aTime0);
1170 		drawinglayer::animation::AnimationEntryLinear aTime1(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5);
1171 		aLoop.append(aTime1);
1172 		rAnimList.append(aLoop);
1173 	}
1174 
1175 	// always visible when stopped, so add timing for staying at the end when not endless
1176 	if(0L != nRepeat)
1177 	{
1178 		drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1179 		rAnimList.append(aEnd);
1180 	}
1181 }
1182 
1183 void SdrTextObj::impGetScrollTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList, double fFrameLength, double fTextLength) const
1184 {
1185 	const SdrTextAniKind eAniKind(GetTextAniKind());
1186 
1187 	if(SDRTEXTANI_SCROLL == eAniKind || SDRTEXTANI_ALTERNATE == eAniKind || SDRTEXTANI_SLIDE == eAniKind)
1188 	{
1189 		// get data. Goal is to calculate fTimeFullPath which is the time needed to
1190 		// move animation from (0.0) to (1.0) state
1191 		const SfxItemSet& rSet = GetObjectItemSet();
1192 		double fAnimationDelay((double)((SdrTextAniDelayItem&)rSet.Get(SDRATTR_TEXT_ANIDELAY)).GetValue());
1193 		double fSingleStepWidth((double)((SdrTextAniAmountItem&)rSet.Get(SDRATTR_TEXT_ANIAMOUNT)).GetValue());
1194 		const SdrTextAniDirection eDirection(GetTextAniDirection());
1195 		const bool bForward(SDRTEXTANI_RIGHT == eDirection || SDRTEXTANI_DOWN == eDirection);
1196 
1197 		if(basegfx::fTools::equalZero(fAnimationDelay))
1198 		{
1199 			// default to 1/20 second
1200 			fAnimationDelay = 50.0;
1201 		}
1202 
1203 		if(basegfx::fTools::less(fSingleStepWidth, 0.0))
1204 		{
1205 			// data is in pixels, convert to logic. Imply PIXEL_DPI dpi.
1206 			// It makes no sense to keep the view-transformation centered
1207 			// definitions, so get rid of them here.
1208 			fSingleStepWidth = (-fSingleStepWidth * (2540.0 / PIXEL_DPI));
1209 		}
1210 
1211 		if(basegfx::fTools::equalZero(fSingleStepWidth))
1212 		{
1213 			// default to 1 millimeter
1214 			fSingleStepWidth = 100.0;
1215 		}
1216 
1217 		// use the length of the full animation path and the number of steps
1218 		// to get the full path time
1219 		const double fFullPathLength(fFrameLength + fTextLength);
1220 		const double fNumberOfSteps(fFullPathLength / fSingleStepWidth);
1221 		double fTimeFullPath(fNumberOfSteps * fAnimationDelay);
1222 
1223 		if(fTimeFullPath < fAnimationDelay)
1224 		{
1225 			fTimeFullPath = fAnimationDelay;
1226 		}
1227 
1228 		switch(eAniKind)
1229 		{
1230 			case SDRTEXTANI_SCROLL :
1231 			{
1232 				impCreateScrollTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay);
1233 				break;
1234 			}
1235 			case SDRTEXTANI_ALTERNATE :
1236 			{
1237 				double fRelativeTextLength(fTextLength / (fFrameLength + fTextLength));
1238 				impCreateAlternateTiming(rSet, rAnimList, fRelativeTextLength, bForward, fTimeFullPath, fAnimationDelay);
1239 				break;
1240 			}
1241 			case SDRTEXTANI_SLIDE :
1242 			{
1243 				impCreateSlideTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay);
1244 				break;
1245 			}
1246 			default : break; // SDRTEXTANI_NONE, SDRTEXTANI_BLINK
1247 		}
1248 	}
1249 }
1250 
1251 /* vim: set noet sw=4 ts=4: */
1252