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