xref: /AOO41X/main/svx/source/svdraw/svdotextdecomposition.cxx (revision ff0525f24f03981d56b7579b645949f111420994)
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 {
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:
112         impTextBreakupHandler(SdrOutliner& rOutliner)
113         :   maTextPortionPrimitives(),
114             maLinePrimitives(),
115             maParagraphPrimitives(),
116             mrOutliner(rOutliner),
117             maNewTransformA(),
118             maNewTransformB(),
119             maScale(),
120             maClipRange()
121         {
122         }
123 
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 
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 
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 
165     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 
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 
451     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 
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 
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 
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 
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 
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 
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 
615     IMPL_LINK(impTextBreakupHandler, decomposeStretchTextPrimitive, DrawPortionInfo*, pInfo)
616     {
617         if(pInfo)
618         {
619             impHandleDrawPortionInfo(*pInfo);
620         }
621 
622         return 0;
623     }
624 
625     IMPL_LINK(impTextBreakupHandler, decomposeContourBulletPrimitive, DrawBulletInfo*, pInfo)
626     {
627         if(pInfo)
628         {
629             impHandleDrawBulletInfo(*pInfo);
630         }
631 
632         return 0;
633     }
634 
635     IMPL_LINK(impTextBreakupHandler, decomposeBlockBulletPrimitive, DrawBulletInfo*, pInfo)
636     {
637         if(pInfo)
638         {
639             impHandleDrawBulletInfo(*pInfo);
640         }
641 
642         return 0;
643     }
644 
645     IMPL_LINK(impTextBreakupHandler, decomposeStretchBulletPrimitive, DrawBulletInfo*, pInfo)
646     {
647         if(pInfo)
648         {
649             impHandleDrawBulletInfo(*pInfo);
650         }
651 
652         return 0;
653     }
654 
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 
676 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 
726 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 
937 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 
1021 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 
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 
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 
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 
1185 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