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