1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 
21 #include <svx/svdetc.hxx>
22 #include <svx/svdoutl.hxx>
23 #include <svx/svdpage.hxx>
24 #include <svx/svdotext.hxx>
25 #include <svx/svdmodel.hxx>
26 #include <svx/sdasitm.hxx>
27 #include <textchain.hxx>
28 #include <textchainflow.hxx>
29 #include <svx/sdtacitm.hxx>
30 #include <svx/sdtayitm.hxx>
31 #include <svx/sdtaiitm.hxx>
32 #include <svx/sdtaaitm.hxx>
33 #include <svx/xfillit0.hxx>
34 #include <svx/xbtmpit.hxx>
35 #include <basegfx/vector/b2dvector.hxx>
36 #include <sdr/primitive2d/sdrtextprimitive2d.hxx>
37 #include <drawinglayer/primitive2d/textprimitive2d.hxx>
38 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
39 #include <basegfx/range/b2drange.hxx>
40 #include <editeng/eeitem.hxx>
41 #include <editeng/editstat.hxx>
42 #include <tools/helpers.hxx>
43 #include <svl/itemset.hxx>
44 #include <drawinglayer/animation/animationtiming.hxx>
45 #include <basegfx/color/bcolor.hxx>
46 #include <vcl/svapp.hxx>
47 #include <editeng/escapementitem.hxx>
48 #include <editeng/svxenum.hxx>
49 #include <editeng/flditem.hxx>
50 #include <editeng/adjustitem.hxx>
51 #include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
52 #include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx>
53 #include <drawinglayer/primitive2d/graphicprimitive2d.hxx>
54 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
55 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
56 #include <svx/unoapi.hxx>
57 #include <drawinglayer/geometry/viewinformation2d.hxx>
58 #include <editeng/outlobj.hxx>
59 #include <basegfx/matrix/b2dhommatrixtools.hxx>
60 #include <sal/log.hxx>
61 
62 using namespace com::sun::star;
63 
64 // helpers
65 
66 namespace
67 {
68     class impTextBreakupHandler
69     {
70     private:
71         drawinglayer::primitive2d::Primitive2DContainer             maTextPortionPrimitives;
72         drawinglayer::primitive2d::Primitive2DContainer             maLinePrimitives;
73         drawinglayer::primitive2d::Primitive2DContainer             maParagraphPrimitives;
74 
75         SdrOutliner&                                                mrOutliner;
76         basegfx::B2DHomMatrix                                       maNewTransformA;
77         basegfx::B2DHomMatrix                                       maNewTransformB;
78 
79         // the visible area for contour text decomposition
80         basegfx::B2DVector                                          maScale;
81 
82         // ClipRange for BlockText decomposition; only text portions completely
83         // inside are to be accepted, so this is different from geometric clipping
84         // (which would allow e.g. upper parts of portions to remain). Only used for
85         // BlockText (see there)
86         basegfx::B2DRange                                           maClipRange;
87 
88         DECL_LINK(decomposeContourTextPrimitive, DrawPortionInfo*, void);
89         DECL_LINK(decomposeBlockTextPrimitive, DrawPortionInfo*, void);
90         DECL_LINK(decomposeStretchTextPrimitive, DrawPortionInfo*, void);
91 
92         DECL_LINK(decomposeContourBulletPrimitive, DrawBulletInfo*, void);
93         DECL_LINK(decomposeBlockBulletPrimitive, DrawBulletInfo*, void);
94         DECL_LINK(decomposeStretchBulletPrimitive, DrawBulletInfo*, void);
95 
96         void impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo);
97         static rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo);
98         void impFlushTextPortionPrimitivesToLinePrimitives();
99         void impFlushLinePrimitivesToParagraphPrimitives(sal_Int32 nPara);
100         void impHandleDrawPortionInfo(const DrawPortionInfo& rInfo);
101         void impHandleDrawBulletInfo(const DrawBulletInfo& rInfo);
102 
103     public:
impTextBreakupHandler(SdrOutliner & rOutliner)104         explicit impTextBreakupHandler(SdrOutliner& rOutliner)
105         :   maTextPortionPrimitives(),
106             maLinePrimitives(),
107             maParagraphPrimitives(),
108             mrOutliner(rOutliner),
109             maNewTransformA(),
110             maNewTransformB(),
111             maScale(),
112             maClipRange()
113         {
114         }
115 
decomposeContourTextPrimitive(const basegfx::B2DHomMatrix & rNewTransformA,const basegfx::B2DHomMatrix & rNewTransformB,const basegfx::B2DVector & rScale)116         void decomposeContourTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB, const basegfx::B2DVector& rScale)
117         {
118             maScale = rScale;
119             maNewTransformA = rNewTransformA;
120             maNewTransformB = rNewTransformB;
121             mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeContourTextPrimitive));
122             mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeContourBulletPrimitive));
123             mrOutliner.StripPortions();
124             mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>());
125             mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>());
126         }
127 
decomposeBlockTextPrimitive(const basegfx::B2DHomMatrix & rNewTransformA,const basegfx::B2DHomMatrix & rNewTransformB,const basegfx::B2DRange & rClipRange)128         void decomposeBlockTextPrimitive(
129             const basegfx::B2DHomMatrix& rNewTransformA,
130             const basegfx::B2DHomMatrix& rNewTransformB,
131             const basegfx::B2DRange& rClipRange)
132         {
133             maNewTransformA = rNewTransformA;
134             maNewTransformB = rNewTransformB;
135             maClipRange = rClipRange;
136             mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeBlockTextPrimitive));
137             mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeBlockBulletPrimitive));
138             mrOutliner.StripPortions();
139             mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>());
140             mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>());
141         }
142 
decomposeStretchTextPrimitive(const basegfx::B2DHomMatrix & rNewTransformA,const basegfx::B2DHomMatrix & rNewTransformB)143         void decomposeStretchTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB)
144         {
145             maNewTransformA = rNewTransformA;
146             maNewTransformB = rNewTransformB;
147             mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeStretchTextPrimitive));
148             mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeStretchBulletPrimitive));
149             mrOutliner.StripPortions();
150             mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>());
151             mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>());
152         }
153 
154         drawinglayer::primitive2d::Primitive2DContainer const & getPrimitive2DSequence();
155     };
156 
impCreateTextPortionPrimitive(const DrawPortionInfo & rInfo)157     void impTextBreakupHandler::impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo)
158     {
159         if(rInfo.maText.isEmpty() || !rInfo.mnTextLen)
160             return;
161 
162         OUString caseMappedText = rInfo.mrFont.CalcCaseMap( rInfo.maText );
163         basegfx::B2DVector aFontScaling;
164         drawinglayer::attribute::FontAttribute aFontAttribute(
165             drawinglayer::primitive2d::getFontAttributeFromVclFont(
166                 aFontScaling,
167                 rInfo.mrFont,
168                 rInfo.IsRTL(),
169                 false));
170         basegfx::B2DHomMatrix aNewTransform;
171 
172         // add font scale to new transform
173         aNewTransform.scale(aFontScaling.getX(), aFontScaling.getY());
174 
175         // look for proportional font scaling, if necessary, scale accordingly
176         sal_Int8 nPropr(rInfo.mrFont.GetPropr());
177         if(100 != nPropr)
178         {
179             const double fFactor(rInfo.mrFont.GetPropr() / 100.0);
180             aNewTransform.scale(fFactor, fFactor);
181         }
182 
183         // apply font rotate
184         if(rInfo.mrFont.GetOrientation())
185         {
186             aNewTransform.rotate(-rInfo.mrFont.GetOrientation().get() * F_PI1800);
187         }
188 
189         // look for escapement, if necessary, translate accordingly
190         if(rInfo.mrFont.GetEscapement())
191         {
192             sal_Int16 nEsc(rInfo.mrFont.GetEscapement());
193 
194             if(DFLT_ESC_AUTO_SUPER == nEsc)
195             {
196                 nEsc = .8 * (100 - nPropr);
197                 assert (nEsc == DFLT_ESC_SUPER && "I'm sure this formula needs to be changed, but how to confirm that???");
198                 nEsc = DFLT_ESC_SUPER;
199             }
200             else if(DFLT_ESC_AUTO_SUB == nEsc)
201             {
202                 nEsc = .2 * -(100 - nPropr);
203                 assert (nEsc == -20 && "I'm sure this formula needs to be changed, but how to confirm that???");
204                 nEsc = -20;
205             }
206 
207             if(nEsc > MAX_ESC_POS)
208             {
209                 nEsc = MAX_ESC_POS;
210             }
211             else if(nEsc < -MAX_ESC_POS)
212             {
213                 nEsc = -MAX_ESC_POS;
214             }
215 
216             const double fEscapement(nEsc / -100.0);
217             aNewTransform.translate(0.0, fEscapement * aFontScaling.getY());
218         }
219 
220         // apply transformA
221         aNewTransform *= maNewTransformA;
222 
223         // apply local offset
224         aNewTransform.translate(rInfo.mrStartPos.X(), rInfo.mrStartPos.Y());
225 
226         // also apply embedding object's transform
227         aNewTransform *= maNewTransformB;
228 
229         // prepare DXArray content. To make it independent from font size (and such from
230         // the text transformation), scale it to unit coordinates
231         ::std::vector< double > aDXArray;
232 
233         if(rInfo.mpDXArray && rInfo.mnTextLen)
234         {
235             aDXArray.reserve(rInfo.mnTextLen);
236 
237             for(sal_Int32 a=0; a < rInfo.mnTextLen; a++)
238             {
239                 aDXArray.push_back(static_cast<double>(rInfo.mpDXArray[a]));
240             }
241         }
242 
243         // create complex text primitive and append
244         const Color aFontColor(rInfo.mrFont.GetColor());
245         const basegfx::BColor aBFontColor(aFontColor.getBColor());
246 
247         const Color aTextFillColor(rInfo.mrFont.GetFillColor());
248 
249         // prepare wordLineMode (for underline and strikeout)
250         // NOT for bullet texts. It is set (this may be an error by itself), but needs to be suppressed to hinder e.g. '1)'
251         // to be split which would not look like the original
252         const bool bWordLineMode(rInfo.mrFont.IsWordLineMode() && !rInfo.mbEndOfBullet);
253 
254         // prepare new primitive
255         rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pNewPrimitive;
256         const bool bDecoratedIsNeeded(
257                LINESTYLE_NONE != rInfo.mrFont.GetOverline()
258             || LINESTYLE_NONE != rInfo.mrFont.GetUnderline()
259             || STRIKEOUT_NONE != rInfo.mrFont.GetStrikeout()
260             || FontEmphasisMark::NONE != (rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::Style)
261             || FontRelief::NONE != rInfo.mrFont.GetRelief()
262             || rInfo.mrFont.IsShadow()
263             || bWordLineMode);
264 
265         if(bDecoratedIsNeeded)
266         {
267             // TextDecoratedPortionPrimitive2D needed, prepare some more data
268             // get overline and underline color. If it's on automatic (0xffffffff) use FontColor instead
269             const Color aUnderlineColor(rInfo.maTextLineColor);
270             const basegfx::BColor aBUnderlineColor((aUnderlineColor == COL_AUTO) ? aBFontColor : aUnderlineColor.getBColor());
271             const Color aOverlineColor(rInfo.maOverlineColor);
272             const basegfx::BColor aBOverlineColor((aOverlineColor == COL_AUTO) ? aBFontColor : aOverlineColor.getBColor());
273 
274             // prepare overline and underline data
275             const drawinglayer::primitive2d::TextLine eFontOverline(
276                 drawinglayer::primitive2d::mapFontLineStyleToTextLine(rInfo.mrFont.GetOverline()));
277             const drawinglayer::primitive2d::TextLine eFontLineStyle(
278                 drawinglayer::primitive2d::mapFontLineStyleToTextLine(rInfo.mrFont.GetUnderline()));
279 
280             // check UnderlineAbove
281             const bool bUnderlineAbove(
282                 drawinglayer::primitive2d::TEXT_LINE_NONE != eFontLineStyle && rInfo.mrFont.IsUnderlineAbove());
283 
284             // prepare strikeout data
285             const drawinglayer::primitive2d::TextStrikeout eTextStrikeout(
286                 drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rInfo.mrFont.GetStrikeout()));
287 
288             // prepare emphasis mark data
289             drawinglayer::primitive2d::TextEmphasisMark eTextEmphasisMark(drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE);
290 
291             switch(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::Style)
292             {
293                 case FontEmphasisMark::Dot : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT; break;
294                 case FontEmphasisMark::Circle : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE; break;
295                 case FontEmphasisMark::Disc : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC; break;
296                 case FontEmphasisMark::Accent : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT; break;
297                 default: break;
298             }
299 
300             const bool bEmphasisMarkAbove(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::PosAbove);
301             const bool bEmphasisMarkBelow(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::PosBelow);
302 
303             // prepare font relief data
304             drawinglayer::primitive2d::TextRelief eTextRelief(drawinglayer::primitive2d::TEXT_RELIEF_NONE);
305 
306             switch(rInfo.mrFont.GetRelief())
307             {
308                 case FontRelief::Embossed : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_EMBOSSED; break;
309                 case FontRelief::Engraved : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_ENGRAVED; break;
310                 default : break; // RELIEF_NONE, FontRelief_FORCE_EQUAL_SIZE
311             }
312 
313             // prepare shadow/outline data
314             const bool bShadow(rInfo.mrFont.IsShadow());
315 
316             // TextDecoratedPortionPrimitive2D is needed, create one
317             pNewPrimitive = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
318 
319                 // attributes for TextSimplePortionPrimitive2D
320                 aNewTransform,
321                 caseMappedText,
322                 rInfo.mnTextStart,
323                 rInfo.mnTextLen,
324                 aDXArray,
325                 aFontAttribute,
326                 rInfo.mpLocale ? *rInfo.mpLocale : css::lang::Locale(),
327                 aBFontColor,
328                 aTextFillColor,
329 
330                 // attributes for TextDecoratedPortionPrimitive2D
331                 aBOverlineColor,
332                 aBUnderlineColor,
333                 eFontOverline,
334                 eFontLineStyle,
335                 bUnderlineAbove,
336                 eTextStrikeout,
337                 bWordLineMode,
338                 eTextEmphasisMark,
339                 bEmphasisMarkAbove,
340                 bEmphasisMarkBelow,
341                 eTextRelief,
342                 bShadow);
343         }
344         else
345         {
346             // TextSimplePortionPrimitive2D is enough
347             pNewPrimitive = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
348                 aNewTransform,
349                 caseMappedText,
350                 rInfo.mnTextStart,
351                 rInfo.mnTextLen,
352                 aDXArray,
353                 aFontAttribute,
354                 rInfo.mpLocale ? *rInfo.mpLocale : css::lang::Locale(),
355                 aBFontColor,
356                 rInfo.mbFilled,
357                 rInfo.mnWidthToFill,
358                 aTextFillColor);
359         }
360 
361         if (aFontColor.IsTransparent())
362         {
363             // Handle semi-transparent text for both the decorated and simple case here.
364             pNewPrimitive = new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
365                 drawinglayer::primitive2d::Primitive2DContainer{ pNewPrimitive },
366                 (255 - aFontColor.GetAlpha()) / 255.0);
367         }
368 
369         if(rInfo.mbEndOfBullet)
370         {
371             // embed in TextHierarchyBulletPrimitive2D
372             const drawinglayer::primitive2d::Primitive2DReference aNewReference(pNewPrimitive);
373             const drawinglayer::primitive2d::Primitive2DContainer aNewSequence { aNewReference } ;
374             pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(aNewSequence);
375         }
376 
377         if(rInfo.mpFieldData)
378         {
379             pNewPrimitive = impCheckFieldPrimitive(pNewPrimitive.get(), rInfo);
380         }
381 
382         maTextPortionPrimitives.push_back(pNewPrimitive);
383 
384         // support for WrongSpellVector. Create WrongSpellPrimitives as needed
385         if(!rInfo.mpWrongSpellVector || aDXArray.empty())
386             return;
387 
388         const sal_Int32 nSize(rInfo.mpWrongSpellVector->size());
389         const sal_Int32 nDXCount(aDXArray.size());
390         const basegfx::BColor aSpellColor(1.0, 0.0, 0.0); // red, hard coded
391 
392         for(sal_Int32 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_Int32 nStart(rCandidate.nStart - rInfo.mnTextStart);
399                 const sal_Int32 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 
impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D * pPrimitive,const DrawPortionInfo & rInfo)447     rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> impTextBreakupHandler::impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo)
448     {
449         rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> xRet = pPrimitive;
450         if(rInfo.mpFieldData)
451         {
452             // Support for FIELD_SEQ_BEGIN, FIELD_SEQ_END. If used, create a TextHierarchyFieldPrimitive2D
453             // which holds the field type and, if applicable, the URL
454             const SvxURLField* pURLField = dynamic_cast< const SvxURLField* >(rInfo.mpFieldData);
455             const SvxPageField* pPageField = dynamic_cast< const SvxPageField* >(rInfo.mpFieldData);
456 
457             // embed current primitive to a sequence
458             drawinglayer::primitive2d::Primitive2DContainer aSequence;
459 
460             if(pPrimitive)
461             {
462                 aSequence.resize(1);
463                 aSequence[0] = drawinglayer::primitive2d::Primitive2DReference(pPrimitive);
464             }
465 
466             if(pURLField)
467             {
468                 // extended this to hold more of the contents of the original
469                 // SvxURLField since that stuff is still used in HitTest and e.g. Calc
470                 std::vector< std::pair< OUString, OUString>> meValues;
471                 meValues.emplace_back("URL", pURLField->GetURL());
472                 meValues.emplace_back("Representation", pURLField->GetRepresentation());
473                 meValues.emplace_back("TargetFrame", pURLField->GetTargetFrame());
474                 meValues.emplace_back("SvxURLFormat", OUString::number(static_cast<sal_uInt16>(pURLField->GetFormat())));
475                 xRet = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_URL, &meValues);
476             }
477             else if(pPageField)
478             {
479                 xRet = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_PAGE);
480             }
481             else
482             {
483                 xRet = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_COMMON);
484             }
485         }
486 
487         return xRet;
488     }
489 
impFlushTextPortionPrimitivesToLinePrimitives()490     void impTextBreakupHandler::impFlushTextPortionPrimitivesToLinePrimitives()
491     {
492         // only create a line primitive when we had content; there is no need for
493         // empty line primitives (contrary to paragraphs, see below).
494         if(!maTextPortionPrimitives.empty())
495         {
496             maLinePrimitives.push_back(new drawinglayer::primitive2d::TextHierarchyLinePrimitive2D(maTextPortionPrimitives));
497             maTextPortionPrimitives.clear();
498         }
499     }
500 
impFlushLinePrimitivesToParagraphPrimitives(sal_Int32 nPara)501     void impTextBreakupHandler::impFlushLinePrimitivesToParagraphPrimitives(sal_Int32 nPara)
502     {
503         sal_Int16 nDepth = mrOutliner.GetDepth(nPara);
504         EBulletInfo eInfo = mrOutliner.GetBulletInfo(nPara);
505         // Pass -1 to signal VclMetafileProcessor2D that there is no active
506         // bullets/numbering in this paragraph (i.e. this is normal text)
507         const sal_Int16 nOutlineLevel( eInfo.bVisible ?  nDepth : -1);
508 
509         // ALWAYS create a paragraph primitive, even when no content was added. This is done to
510         // have the correct paragraph count even with empty paragraphs. Those paragraphs will
511         // have an empty sub-PrimitiveSequence.
512         maParagraphPrimitives.push_back(
513             new drawinglayer::primitive2d::TextHierarchyParagraphPrimitive2D(
514                 maLinePrimitives,
515                 nOutlineLevel));
516         maLinePrimitives.clear();
517     }
518 
impHandleDrawPortionInfo(const DrawPortionInfo & rInfo)519     void impTextBreakupHandler::impHandleDrawPortionInfo(const DrawPortionInfo& rInfo)
520     {
521         impCreateTextPortionPrimitive(rInfo);
522 
523         if(rInfo.mbEndOfLine || rInfo.mbEndOfParagraph)
524         {
525             impFlushTextPortionPrimitivesToLinePrimitives();
526         }
527 
528         if(rInfo.mbEndOfParagraph)
529         {
530             impFlushLinePrimitivesToParagraphPrimitives(rInfo.mnPara);
531         }
532     }
533 
impHandleDrawBulletInfo(const DrawBulletInfo & rInfo)534     void impTextBreakupHandler::impHandleDrawBulletInfo(const DrawBulletInfo& rInfo)
535     {
536         basegfx::B2DHomMatrix aNewTransform;
537 
538         // add size to new transform
539         aNewTransform.scale(rInfo.maBulletSize.getWidth(), rInfo.maBulletSize.getHeight());
540 
541         // apply transformA
542         aNewTransform *= maNewTransformA;
543 
544         // apply local offset
545         aNewTransform.translate(rInfo.maBulletPosition.X(), rInfo.maBulletPosition.Y());
546 
547         // also apply embedding object's transform
548         aNewTransform *= maNewTransformB;
549 
550         // prepare empty GraphicAttr
551         const GraphicAttr aGraphicAttr;
552 
553         // create GraphicPrimitive2D
554         const drawinglayer::primitive2d::Primitive2DReference aNewReference(new drawinglayer::primitive2d::GraphicPrimitive2D(
555             aNewTransform,
556             rInfo.maBulletGraphicObject,
557             aGraphicAttr));
558 
559         // embed in TextHierarchyBulletPrimitive2D
560         const drawinglayer::primitive2d::Primitive2DContainer aNewSequence { aNewReference };
561         rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(aNewSequence);
562 
563         // add to output
564         maTextPortionPrimitives.push_back(pNewPrimitive);
565     }
566 
IMPL_LINK(impTextBreakupHandler,decomposeContourTextPrimitive,DrawPortionInfo *,pInfo,void)567     IMPL_LINK(impTextBreakupHandler, decomposeContourTextPrimitive, DrawPortionInfo*, pInfo, void)
568     {
569         // for contour text, ignore (clip away) all portions which are below
570         // the visible area given by maScale
571         if(pInfo && static_cast<double>(pInfo->mrStartPos.Y()) < maScale.getY())
572         {
573             impHandleDrawPortionInfo(*pInfo);
574         }
575     }
576 
IMPL_LINK(impTextBreakupHandler,decomposeBlockTextPrimitive,DrawPortionInfo *,pInfo,void)577     IMPL_LINK(impTextBreakupHandler, decomposeBlockTextPrimitive, DrawPortionInfo*, pInfo, void)
578     {
579         if(!pInfo)
580             return;
581 
582         // Is clipping wanted? This is text clipping; only accept a portion
583         // if it's completely in the range
584         if(!maClipRange.isEmpty())
585         {
586             // Test start position first; this allows to not get the text range at
587             // all if text is far outside
588             const basegfx::B2DPoint aStartPosition(pInfo->mrStartPos.X(), pInfo->mrStartPos.Y());
589 
590             if(!maClipRange.isInside(aStartPosition))
591             {
592                 return;
593             }
594 
595             // Start position is inside. Get TextBoundRect and TopLeft next
596             drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
597             aTextLayouterDevice.setFont(pInfo->mrFont);
598 
599             const basegfx::B2DRange aTextBoundRect(
600                 aTextLayouterDevice.getTextBoundRect(
601                     pInfo->maText, pInfo->mnTextStart, pInfo->mnTextLen));
602             const basegfx::B2DPoint aTopLeft(aTextBoundRect.getMinimum() + aStartPosition);
603 
604             if(!maClipRange.isInside(aTopLeft))
605             {
606                 return;
607             }
608 
609             // TopLeft is inside. Get BottomRight and check
610             const basegfx::B2DPoint aBottomRight(aTextBoundRect.getMaximum() + aStartPosition);
611 
612             if(!maClipRange.isInside(aBottomRight))
613             {
614                 return;
615             }
616 
617             // all inside, clip was successful
618         }
619         impHandleDrawPortionInfo(*pInfo);
620     }
621 
IMPL_LINK(impTextBreakupHandler,decomposeStretchTextPrimitive,DrawPortionInfo *,pInfo,void)622     IMPL_LINK(impTextBreakupHandler, decomposeStretchTextPrimitive, DrawPortionInfo*, pInfo, void)
623     {
624         if(pInfo)
625         {
626             impHandleDrawPortionInfo(*pInfo);
627         }
628     }
629 
IMPL_LINK(impTextBreakupHandler,decomposeContourBulletPrimitive,DrawBulletInfo *,pInfo,void)630     IMPL_LINK(impTextBreakupHandler, decomposeContourBulletPrimitive, DrawBulletInfo*, pInfo, void)
631     {
632         if(pInfo)
633         {
634             impHandleDrawBulletInfo(*pInfo);
635         }
636     }
637 
IMPL_LINK(impTextBreakupHandler,decomposeBlockBulletPrimitive,DrawBulletInfo *,pInfo,void)638     IMPL_LINK(impTextBreakupHandler, decomposeBlockBulletPrimitive, DrawBulletInfo*, pInfo, void)
639     {
640         if(pInfo)
641         {
642             impHandleDrawBulletInfo(*pInfo);
643         }
644     }
645 
IMPL_LINK(impTextBreakupHandler,decomposeStretchBulletPrimitive,DrawBulletInfo *,pInfo,void)646     IMPL_LINK(impTextBreakupHandler, decomposeStretchBulletPrimitive, DrawBulletInfo*, pInfo, void)
647     {
648         if(pInfo)
649         {
650             impHandleDrawBulletInfo(*pInfo);
651         }
652     }
653 
getPrimitive2DSequence()654     drawinglayer::primitive2d::Primitive2DContainer const & impTextBreakupHandler::getPrimitive2DSequence()
655     {
656         if(!maTextPortionPrimitives.empty())
657         {
658             // collect non-closed lines
659             impFlushTextPortionPrimitivesToLinePrimitives();
660         }
661 
662         if(!maLinePrimitives.empty())
663         {
664             // collect non-closed paragraphs
665             impFlushLinePrimitivesToParagraphPrimitives(mrOutliner.GetParagraphCount() - 1);
666         }
667 
668         return maParagraphPrimitives;
669     }
670 } // end of anonymous namespace
671 
672 
673 // primitive decompositions
674 
impDecomposeContourTextPrimitive(drawinglayer::primitive2d::Primitive2DContainer & rTarget,const drawinglayer::primitive2d::SdrContourTextPrimitive2D & rSdrContourTextPrimitive,const drawinglayer::geometry::ViewInformation2D & aViewInformation) const675 void SdrTextObj::impDecomposeContourTextPrimitive(
676     drawinglayer::primitive2d::Primitive2DContainer& rTarget,
677     const drawinglayer::primitive2d::SdrContourTextPrimitive2D& rSdrContourTextPrimitive,
678     const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
679 {
680     // decompose matrix to have position and size of text
681     basegfx::B2DVector aScale, aTranslate;
682     double fRotate, fShearX;
683     rSdrContourTextPrimitive.getObjectTransform().decompose(aScale, aTranslate, fRotate, fShearX);
684 
685     // prepare contour polygon, force to non-mirrored for laying out
686     basegfx::B2DPolyPolygon aPolyPolygon(rSdrContourTextPrimitive.getUnitPolyPolygon());
687     aPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(fabs(aScale.getX()), fabs(aScale.getY())));
688 
689     // prepare outliner
690     SolarMutexGuard aSolarGuard;
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::utils::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(nullptr);
722 
723     rTarget = aConverter.getPrimitive2DSequence();
724 }
725 
impDecomposeAutoFitTextPrimitive(drawinglayer::primitive2d::Primitive2DContainer & rTarget,const drawinglayer::primitive2d::SdrAutoFitTextPrimitive2D & rSdrAutofitTextPrimitive,const drawinglayer::geometry::ViewInformation2D & aViewInformation) const726 void SdrTextObj::impDecomposeAutoFitTextPrimitive(
727     drawinglayer::primitive2d::Primitive2DContainer& rTarget,
728     const drawinglayer::primitive2d::SdrAutoFitTextPrimitive2D& rSdrAutofitTextPrimitive,
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     rSdrAutofitTextPrimitive.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 SfxItemSet& rTextItemSet = rSdrAutofitTextPrimitive.getSdrText()->GetItemSet();
742     SolarMutexGuard aSolarGuard;
743     SdrOutliner& rOutliner = ImpGetDrawOutliner();
744     SdrTextVertAdjust eVAdj = GetTextVerticalAdjust(rTextItemSet);
745     SdrTextHorzAdjust eHAdj = GetTextHorizontalAdjust(rTextItemSet);
746     const EEControlBits nOriginalControlWord(rOutliner.GetControlWord());
747     const Size aNullSize;
748 
749     // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
750     rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
751 
752     rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE|EEControlBits::STRETCHING);
753     rOutliner.SetMinAutoPaperSize(aNullSize);
754     rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
755 
756     // add one to rage sizes to get back to the old Rectangle and outliner measurements
757     const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1));
758     const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1));
759     const OutlinerParaObject* pOutlinerParaObject = rSdrAutofitTextPrimitive.getSdrText()->GetOutlinerParaObject();
760     OSL_ENSURE(pOutlinerParaObject, "impDecomposeBlockTextPrimitive used with no OutlinerParaObject (!)");
761     const bool bVerticalWriting(pOutlinerParaObject->IsVertical());
762     const bool bTopToBottom(pOutlinerParaObject->IsTopToBottom());
763     const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight));
764 
765     if(rSdrAutofitTextPrimitive.getWordWrap() || IsTextFrame())
766     {
767         rOutliner.SetMaxAutoPaperSize(aAnchorTextSize);
768     }
769 
770     if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting)
771     {
772         rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0));
773         rOutliner.SetMinColumnWrapHeight(nAnchorTextHeight);
774     }
775 
776     if(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting)
777     {
778         rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight));
779         rOutliner.SetMinColumnWrapHeight(nAnchorTextWidth);
780     }
781 
782     rOutliner.SetPaperSize(aNullSize);
783     rOutliner.SetUpdateMode(true);
784     rOutliner.SetText(*pOutlinerParaObject);
785     ImpAutoFitText(rOutliner,aAnchorTextSize,bVerticalWriting);
786 
787     // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
788     rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
789 
790     // now get back the layouted text size from outliner
791     const Size aOutlinerTextSize(rOutliner.GetPaperSize());
792     const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height());
793     basegfx::B2DVector aAdjustTranslate(0.0, 0.0);
794 
795     // correct horizontal translation using the now known text size
796     if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj)
797     {
798         const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX());
799 
800         if(SDRTEXTHORZADJUST_CENTER == eHAdj)
801         {
802             aAdjustTranslate.setX(fFree / 2.0);
803         }
804 
805         if(SDRTEXTHORZADJUST_RIGHT == eHAdj)
806         {
807             aAdjustTranslate.setX(fFree);
808         }
809     }
810 
811     // correct vertical translation using the now known text size
812     if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj)
813     {
814         const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY());
815 
816         if(SDRTEXTVERTADJUST_CENTER == eVAdj)
817         {
818             aAdjustTranslate.setY(fFree / 2.0);
819         }
820 
821         if(SDRTEXTVERTADJUST_BOTTOM == eVAdj)
822         {
823             aAdjustTranslate.setY(fFree);
824         }
825     }
826 
827     // prepare matrices to apply to newly created primitives. aNewTransformA
828     // will get coordinates in aOutlinerScale size and positive in X, Y.
829     basegfx::B2DHomMatrix aNewTransformA;
830     basegfx::B2DHomMatrix aNewTransformB;
831 
832     // translate relative to given primitive to get same rotation and shear
833     // as the master shape we are working on. For vertical, use the top-right
834     // corner
835     const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX());
836     const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY());
837     aNewTransformA.translate(fStartInX, fStartInY);
838 
839     // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
840     // move the null point which was top left to bottom right.
841     const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
842     const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
843     aNewTransformB.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0);
844 
845     // in-between the translations of the single primitives will take place. Afterwards,
846     // the object's transformations need to be applied
847     aNewTransformB.shearX(fShearX);
848     aNewTransformB.rotate(fRotate);
849     aNewTransformB.translate(aTranslate.getX(), aTranslate.getY());
850 
851     basegfx::B2DRange aClipRange;
852 
853     // now break up text primitives.
854     impTextBreakupHandler aConverter(rOutliner);
855     aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange);
856 
857     // cleanup outliner
858     rOutliner.Clear();
859     rOutliner.setVisualizedPage(nullptr);
860     rOutliner.SetControlWord(nOriginalControlWord);
861 
862     rTarget = aConverter.getPrimitive2DSequence();
863 }
864 
865 // Resolves: fdo#35779 set background color of this shape as the editeng background if there
866 // is one. Check the shape itself, then the host page, then that page's master page.
setSuitableOutlinerBg(::Outliner & rOutliner) const867 void SdrObject::setSuitableOutlinerBg(::Outliner& rOutliner) const
868 {
869     const SfxItemSet* pBackgroundFillSet = getBackgroundFillSet();
870     if (drawing::FillStyle_NONE != pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue())
871     {
872         Color aColor(rOutliner.GetBackgroundColor());
873         GetDraftFillColor(*pBackgroundFillSet, aColor);
874         rOutliner.SetBackgroundColor(aColor);
875     }
876 }
877 
getBackgroundFillSet() const878 const SfxItemSet* SdrObject::getBackgroundFillSet() const
879 {
880     const SfxItemSet* pBackgroundFillSet = &GetObjectItemSet();
881 
882     if (drawing::FillStyle_NONE == pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue())
883     {
884         SdrPage* pOwnerPage(getSdrPageFromSdrObject());
885         if (pOwnerPage)
886         {
887             pBackgroundFillSet = &pOwnerPage->getSdrPageProperties().GetItemSet();
888 
889             if (drawing::FillStyle_NONE == pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue())
890             {
891                 if (!pOwnerPage->IsMasterPage() && pOwnerPage->TRG_HasMasterPage())
892                 {
893                     pBackgroundFillSet = &pOwnerPage->TRG_GetMasterPage().getSdrPageProperties().GetItemSet();
894                 }
895             }
896         }
897     }
898     return pBackgroundFillSet;
899 }
900 
getFillGraphic() const901 const Graphic* SdrObject::getFillGraphic() const
902 {
903     if(IsGroupObject()) // Doesn't make sense, and GetObjectItemSet() asserts.
904         return nullptr;
905     const SfxItemSet* pBackgroundFillSet = getBackgroundFillSet();
906     if (drawing::FillStyle_BITMAP != pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue())
907         return nullptr;
908     return &pBackgroundFillSet->Get(XATTR_FILLBITMAP).GetGraphicObject().GetGraphic();
909 }
910 
impDecomposeBlockTextPrimitive(drawinglayer::primitive2d::Primitive2DContainer & rTarget,const drawinglayer::primitive2d::SdrBlockTextPrimitive2D & rSdrBlockTextPrimitive,const drawinglayer::geometry::ViewInformation2D & aViewInformation) const911 void SdrTextObj::impDecomposeBlockTextPrimitive(
912     drawinglayer::primitive2d::Primitive2DContainer& rTarget,
913     const drawinglayer::primitive2d::SdrBlockTextPrimitive2D& rSdrBlockTextPrimitive,
914     const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
915 {
916     // decompose matrix to have position and size of text
917     basegfx::B2DVector aScale, aTranslate;
918     double fRotate, fShearX;
919     rSdrBlockTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
920 
921     // use B2DRange aAnchorTextRange for calculations
922     basegfx::B2DRange aAnchorTextRange(aTranslate);
923     aAnchorTextRange.expand(aTranslate + aScale);
924 
925     // prepare outliner
926     const bool bIsCell(rSdrBlockTextPrimitive.getCellText());
927     SolarMutexGuard aSolarGuard;
928     SdrOutliner& rOutliner = ImpGetDrawOutliner();
929     SdrTextHorzAdjust eHAdj = rSdrBlockTextPrimitive.getSdrTextHorzAdjust();
930     SdrTextVertAdjust eVAdj = rSdrBlockTextPrimitive.getSdrTextVertAdjust();
931     const EEControlBits nOriginalControlWord(rOutliner.GetControlWord());
932     const Size aNullSize;
933 
934     // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
935     rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
936     rOutliner.SetFixedCellHeight(rSdrBlockTextPrimitive.isFixedCellHeight());
937     rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE);
938     rOutliner.SetMinAutoPaperSize(aNullSize);
939     rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
940 
941     // That color needs to be restored on leaving this method
942     Color aOriginalBackColor(rOutliner.GetBackgroundColor());
943     setSuitableOutlinerBg(rOutliner);
944 
945     // add one to rage sizes to get back to the old Rectangle and outliner measurements
946     const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1));
947     const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1));
948     const bool bVerticalWriting(rSdrBlockTextPrimitive.getOutlinerParaObject().IsVertical());
949     const bool bTopToBottom(rSdrBlockTextPrimitive.getOutlinerParaObject().IsTopToBottom());
950     const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight));
951 
952     if(bIsCell)
953     {
954         // cell text is formatted neither like a text object nor like an object
955         // text, so use a special setup here
956         rOutliner.SetMaxAutoPaperSize(aAnchorTextSize);
957 
958         // #i106214# To work with an unchangeable PaperSize (CellSize in
959         // this case) Set(Min|Max)AutoPaperSize and SetPaperSize have to be used.
960         // #i106214# This was not completely correct; to still measure the real
961         // text height to allow vertical adjust (and vice versa for VerticalWritintg)
962         // only one aspect has to be set, but the other one to zero
963         if(bVerticalWriting)
964         {
965             // measure the horizontal text size
966             rOutliner.SetMinAutoPaperSize(Size(0, aAnchorTextSize.Height()));
967         }
968         else
969         {
970             // measure the vertical text size
971             rOutliner.SetMinAutoPaperSize(Size(aAnchorTextSize.Width(), 0));
972         }
973 
974         rOutliner.SetPaperSize(aAnchorTextSize);
975         rOutliner.SetUpdateMode(true);
976         rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject());
977     }
978     else
979     {
980         // check if block text is used (only one of them can be true)
981         const bool bHorizontalIsBlock(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting);
982         const bool bVerticalIsBlock(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting);
983 
984         // set minimal paper size horizontally/vertically if needed
985         if(bHorizontalIsBlock)
986         {
987             rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0));
988             rOutliner.SetMinColumnWrapHeight(nAnchorTextHeight);
989         }
990         else if(bVerticalIsBlock)
991         {
992             rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight));
993             rOutliner.SetMinColumnWrapHeight(nAnchorTextWidth);
994         }
995 
996         if((rSdrBlockTextPrimitive.getWordWrap() || IsTextFrame()) && !rSdrBlockTextPrimitive.getUnlimitedPage())
997         {
998             // #i103454# maximal paper size hor/ver needs to be limited to text
999             // frame size. If it's block text, still allow the 'other' direction
1000             // to grow to get a correct real text size when using GetPaperSize().
1001             // When just using aAnchorTextSize as maximum, GetPaperSize()
1002             // would just return aAnchorTextSize again: this means, the wanted
1003             // 'measurement' of the real size of block text would not work
1004             Size aMaxAutoPaperSize(aAnchorTextSize);
1005 
1006             // Usual processing - always grow in one of directions
1007             bool bAllowGrowVertical = !bVerticalWriting;
1008             bool bAllowGrowHorizontal = bVerticalWriting;
1009 
1010             // Compatibility mode for tdf#99729
1011             if (getSdrModelFromSdrObject().IsAnchoredTextOverflowLegacy())
1012             {
1013                 bAllowGrowVertical = bHorizontalIsBlock;
1014                 bAllowGrowHorizontal = bVerticalIsBlock;
1015             }
1016 
1017             if (bAllowGrowVertical)
1018             {
1019                 // allow to grow vertical for horizontal texts
1020                 aMaxAutoPaperSize.setHeight(1000000);
1021             }
1022             else if (bAllowGrowHorizontal)
1023             {
1024                 // allow to grow horizontal for vertical texts
1025                 aMaxAutoPaperSize.setWidth(1000000);
1026             }
1027 
1028             rOutliner.SetMaxAutoPaperSize(aMaxAutoPaperSize);
1029         }
1030 
1031         rOutliner.SetPaperSize(aNullSize);
1032         rOutliner.SetUpdateMode(true);
1033         rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject());
1034     }
1035 
1036     rOutliner.SetControlWord(nOriginalControlWord);
1037 
1038     // now get back the layouted text size from outliner
1039     const Size aOutlinerTextSize(rOutliner.GetPaperSize());
1040     const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height());
1041     basegfx::B2DVector aAdjustTranslate(0.0, 0.0);
1042 
1043     // For draw objects containing text correct hor/ver alignment if text is bigger
1044     // than the object itself. Without that correction, the text would always be
1045     // formatted to the left edge (or top edge when vertical) of the draw object.
1046     if(!IsTextFrame() && !bIsCell)
1047     {
1048         if(aAnchorTextRange.getWidth() < aOutlinerScale.getX() && !bVerticalWriting)
1049         {
1050             // Horizontal case here. Correct only if eHAdj == SDRTEXTHORZADJUST_BLOCK,
1051             // else the alignment is wanted.
1052             if(SDRTEXTHORZADJUST_BLOCK == eHAdj)
1053             {
1054                 SvxAdjust eAdjust = GetObjectItemSet().Get(EE_PARA_JUST).GetAdjust();
1055                 switch(eAdjust)
1056                 {
1057                     case SvxAdjust::Left:   eHAdj = SDRTEXTHORZADJUST_LEFT; break;
1058                     case SvxAdjust::Right:  eHAdj = SDRTEXTHORZADJUST_RIGHT; break;
1059                     case SvxAdjust::Center: eHAdj = SDRTEXTHORZADJUST_CENTER; break;
1060                     default: break;
1061                 }
1062             }
1063         }
1064 
1065         if(aAnchorTextRange.getHeight() < aOutlinerScale.getY() && bVerticalWriting)
1066         {
1067             // Vertical case here. Correct only if eHAdj == SDRTEXTVERTADJUST_BLOCK,
1068             // else the alignment is wanted.
1069             if(SDRTEXTVERTADJUST_BLOCK == eVAdj)
1070             {
1071                 eVAdj = SDRTEXTVERTADJUST_CENTER;
1072             }
1073         }
1074     }
1075 
1076     // correct horizontal translation using the now known text size
1077     if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj)
1078     {
1079         const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX());
1080 
1081         if(SDRTEXTHORZADJUST_CENTER == eHAdj)
1082         {
1083             aAdjustTranslate.setX(fFree / 2.0);
1084         }
1085 
1086         if(SDRTEXTHORZADJUST_RIGHT == eHAdj)
1087         {
1088             aAdjustTranslate.setX(fFree);
1089         }
1090     }
1091 
1092     // correct vertical translation using the now known text size
1093     if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj)
1094     {
1095         const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY());
1096 
1097         if(SDRTEXTVERTADJUST_CENTER == eVAdj)
1098         {
1099             aAdjustTranslate.setY(fFree / 2.0);
1100         }
1101 
1102         if(SDRTEXTVERTADJUST_BOTTOM == eVAdj)
1103         {
1104             aAdjustTranslate.setY(fFree);
1105         }
1106     }
1107 
1108     // prepare matrices to apply to newly created primitives. aNewTransformA
1109     // will get coordinates in aOutlinerScale size and positive in X, Y.
1110     // Translate relative to given primitive to get same rotation and shear
1111     // as the master shape we are working on. For vertical, use the top-right
1112     // corner
1113     const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX());
1114     const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY());
1115     basegfx::B2DHomMatrix aNewTransformA(basegfx::utils::createTranslateB2DHomMatrix(fStartInX, fStartInY));
1116 
1117     // Apply the camera rotation. It have to be applied after adjustment of
1118     // the text (top, bottom, center, left, right).
1119     if(GetCameraZRotation() != 0)
1120     {
1121         // First find the text rect.
1122         basegfx::B2DRange aTextRectangle(/*x1=*/aTranslate.getX() + aAdjustTranslate.getX(),
1123                                          /*y1=*/aTranslate.getY() + aAdjustTranslate.getY(),
1124                                          /*x2=*/aTranslate.getX() + aOutlinerScale.getX() - aAdjustTranslate.getX(),
1125                                          /*y2=*/aTranslate.getY() + aOutlinerScale.getY() - aAdjustTranslate.getY());
1126 
1127         // Rotate the text from the center point.
1128         basegfx::B2DVector aTranslateToCenter(aTextRectangle.getWidth() / 2, aTextRectangle.getHeight() / 2);
1129         aNewTransformA.translate(-aTranslateToCenter.getX(), -aTranslateToCenter.getY());
1130         aNewTransformA.rotate(basegfx::deg2rad(360.0 - GetCameraZRotation() ));
1131         aNewTransformA.translate(aTranslateToCenter.getX(), aTranslateToCenter.getY());
1132     }
1133 
1134     // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
1135     // move the null point which was top left to bottom right.
1136     const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
1137     const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
1138 
1139     // in-between the translations of the single primitives will take place. Afterwards,
1140     // the object's transformations need to be applied
1141     const basegfx::B2DHomMatrix aNewTransformB(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
1142         bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
1143         fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
1144 
1145 
1146     // create ClipRange (if needed)
1147     basegfx::B2DRange aClipRange;
1148 
1149     // now break up text primitives.
1150     impTextBreakupHandler aConverter(rOutliner);
1151     aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange);
1152 
1153     // cleanup outliner
1154     rOutliner.SetBackgroundColor(aOriginalBackColor);
1155     rOutliner.Clear();
1156     rOutliner.setVisualizedPage(nullptr);
1157 
1158     rTarget = aConverter.getPrimitive2DSequence();
1159 }
1160 
impDecomposeStretchTextPrimitive(drawinglayer::primitive2d::Primitive2DContainer & rTarget,const drawinglayer::primitive2d::SdrStretchTextPrimitive2D & rSdrStretchTextPrimitive,const drawinglayer::geometry::ViewInformation2D & aViewInformation) const1161 void SdrTextObj::impDecomposeStretchTextPrimitive(
1162     drawinglayer::primitive2d::Primitive2DContainer& rTarget,
1163     const drawinglayer::primitive2d::SdrStretchTextPrimitive2D& rSdrStretchTextPrimitive,
1164     const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
1165 {
1166     // decompose matrix to have position and size of text
1167     basegfx::B2DVector aScale, aTranslate;
1168     double fRotate, fShearX;
1169     rSdrStretchTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
1170 
1171     // prepare outliner
1172     SolarMutexGuard aSolarGuard;
1173     SdrOutliner& rOutliner = ImpGetDrawOutliner();
1174     const EEControlBits nOriginalControlWord(rOutliner.GetControlWord());
1175     const Size aNullSize;
1176 
1177     rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::STRETCHING|EEControlBits::AUTOPAGESIZE);
1178     rOutliner.SetFixedCellHeight(rSdrStretchTextPrimitive.isFixedCellHeight());
1179     rOutliner.SetMinAutoPaperSize(aNullSize);
1180     rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
1181     rOutliner.SetPaperSize(aNullSize);
1182     rOutliner.SetUpdateMode(true);
1183     rOutliner.SetText(rSdrStretchTextPrimitive.getOutlinerParaObject());
1184 
1185     // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
1186     rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
1187 
1188     // now get back the laid out text size from outliner
1189     const Size aOutlinerTextSize(rOutliner.CalcTextSize());
1190     const basegfx::B2DVector aOutlinerScale(
1191         basegfx::fTools::equalZero(aOutlinerTextSize.Width()) ? 1.0 : aOutlinerTextSize.Width(),
1192         basegfx::fTools::equalZero(aOutlinerTextSize.Height()) ? 1.0 : aOutlinerTextSize.Height());
1193 
1194     // prepare matrices to apply to newly created primitives
1195     basegfx::B2DHomMatrix aNewTransformA;
1196 
1197     // #i101957# Check for vertical text. If used, aNewTransformA
1198     // needs to translate the text initially around object width to orient
1199     // it relative to the topper right instead of the topper left
1200     const bool bVertical(rSdrStretchTextPrimitive.getOutlinerParaObject().IsVertical());
1201     const bool bTopToBottom(rSdrStretchTextPrimitive.getOutlinerParaObject().IsTopToBottom());
1202 
1203     if(bVertical)
1204     {
1205         if(bTopToBottom)
1206             aNewTransformA.translate(aScale.getX(), 0.0);
1207         else
1208             aNewTransformA.translate(0.0, aScale.getY());
1209     }
1210 
1211     // calculate global char stretching scale parameters. Use non-mirrored sizes
1212     // to layout without mirroring
1213     const double fScaleX(fabs(aScale.getX()) / aOutlinerScale.getX());
1214     const double fScaleY(fabs(aScale.getY()) / aOutlinerScale.getY());
1215     rOutliner.SetGlobalCharStretching(static_cast<sal_Int16>(FRound(fScaleX * 100.0)), static_cast<sal_Int16>(FRound(fScaleY * 100.0)));
1216 
1217     // When mirroring in X and Y,
1218     // move the null point which was top left to bottom right.
1219     const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
1220     const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
1221 
1222     // in-between the translations of the single primitives will take place. Afterwards,
1223     // the object's transformations need to be applied
1224     const basegfx::B2DHomMatrix aNewTransformB(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
1225         bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
1226         fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
1227 
1228     // now break up text primitives.
1229     impTextBreakupHandler aConverter(rOutliner);
1230     aConverter.decomposeStretchTextPrimitive(aNewTransformA, aNewTransformB);
1231 
1232     // cleanup outliner
1233     rOutliner.SetControlWord(nOriginalControlWord);
1234     rOutliner.Clear();
1235     rOutliner.setVisualizedPage(nullptr);
1236 
1237     rTarget = aConverter.getPrimitive2DSequence();
1238 }
1239 
1240 
1241 // timing generators
1242 #define ENDLESS_LOOP    (0xffffffff)
1243 #define ENDLESS_TIME    (double(0xffffffff))
1244 #define PIXEL_DPI       (96.0)
1245 
impGetBlinkTextTiming(drawinglayer::animation::AnimationEntryList & rAnimList) const1246 void SdrTextObj::impGetBlinkTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList) const
1247 {
1248     if(SdrTextAniKind::Blink != GetTextAniKind())
1249         return;
1250 
1251     // get values
1252     const SfxItemSet& rSet = GetObjectItemSet();
1253     const sal_uInt32 nRepeat(static_cast<sal_uInt32>(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue()));
1254     double fDelay(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIDELAY).GetValue()));
1255 
1256     if(0.0 == fDelay)
1257     {
1258         // use default
1259         fDelay = 250.0;
1260     }
1261 
1262     // prepare loop and add
1263     drawinglayer::animation::AnimationEntryLoop  aLoop(nRepeat ? nRepeat : ENDLESS_LOOP);
1264     drawinglayer::animation::AnimationEntryFixed aStart(fDelay, 0.0);
1265     aLoop.append(aStart);
1266     drawinglayer::animation::AnimationEntryFixed aEnd(fDelay, 1.0);
1267     aLoop.append(aEnd);
1268     rAnimList.append(aLoop);
1269 
1270     // add stopped state if loop is not endless
1271     if(0 != nRepeat)
1272     {
1273         bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue());
1274         drawinglayer::animation::AnimationEntryFixed aStop(ENDLESS_TIME, bVisibleWhenStopped ? 0.0 : 1.0);
1275         rAnimList.append(aStop);
1276     }
1277 }
1278 
impCreateScrollTiming(const SfxItemSet & rSet,drawinglayer::animation::AnimationEntryList & rAnimList,bool bForward,double fTimeFullPath,double fFrequency)1279 static void impCreateScrollTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency)
1280 {
1281     bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue());
1282     bool bVisibleWhenStarted(rSet.Get(SDRATTR_TEXT_ANISTARTINSIDE).GetValue());
1283     const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue());
1284 
1285     if(bVisibleWhenStarted)
1286     {
1287         // move from center to outside
1288         drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0);
1289         rAnimList.append(aInOut);
1290     }
1291 
1292     // loop. In loop, move through
1293     drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat : ENDLESS_LOOP);
1294     drawinglayer::animation::AnimationEntryLinear aThrough(fTimeFullPath, fFrequency, bForward ? 0.0 : 1.0, bForward ? 1.0 : 0.0);
1295     aLoop.append(aThrough);
1296     rAnimList.append(aLoop);
1297 
1298     if(0 != nRepeat && bVisibleWhenStopped)
1299     {
1300         // move from outside to center
1301         drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5);
1302         rAnimList.append(aOutIn);
1303 
1304         // add timing for staying at the end
1305         drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1306         rAnimList.append(aEnd);
1307     }
1308 }
1309 
impCreateAlternateTiming(const SfxItemSet & rSet,drawinglayer::animation::AnimationEntryList & rAnimList,double fRelativeTextLength,bool bForward,double fTimeFullPath,double fFrequency)1310 static void impCreateAlternateTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, double fRelativeTextLength, bool bForward, double fTimeFullPath, double fFrequency)
1311 {
1312     if(basegfx::fTools::more(fRelativeTextLength, 0.5))
1313     {
1314         // this is the case when fTextLength > fFrameLength, text is bigger than animation frame.
1315         // In that case, correct direction
1316         bForward = !bForward;
1317     }
1318 
1319     const double fStartPosition(bForward ? fRelativeTextLength : 1.0 - fRelativeTextLength);
1320     const double fEndPosition(bForward ? 1.0 - fRelativeTextLength : fRelativeTextLength);
1321     bool bVisibleWhenStarted(rSet.Get(SDRATTR_TEXT_ANISTARTINSIDE).GetValue());
1322     const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue());
1323 
1324     if(!bVisibleWhenStarted)
1325     {
1326         // move from outside to center
1327         drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5);
1328         rAnimList.append(aOutIn);
1329     }
1330 
1331     // loop. In loop, move out and in again. fInnerMovePath may be negative when text is bigger then frame,
1332     // so use absolute value
1333     const double fInnerMovePath(fabs(1.0 - (fRelativeTextLength * 2.0)));
1334     const double fTimeForInnerPath(fTimeFullPath * fInnerMovePath);
1335     const double fHalfInnerPath(fTimeForInnerPath * 0.5);
1336     const sal_uInt32 nDoubleRepeat(nRepeat / 2L);
1337 
1338     if(nDoubleRepeat || 0 == nRepeat)
1339     {
1340         // double forth and back loop
1341         drawinglayer::animation::AnimationEntryLoop aLoop(nDoubleRepeat ? nDoubleRepeat : ENDLESS_LOOP);
1342         drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition);
1343         aLoop.append(aTime0);
1344         drawinglayer::animation::AnimationEntryLinear aTime1(fTimeForInnerPath, fFrequency, fEndPosition, fStartPosition);
1345         aLoop.append(aTime1);
1346         drawinglayer::animation::AnimationEntryLinear aTime2(fHalfInnerPath, fFrequency, fStartPosition, 0.5);
1347         aLoop.append(aTime2);
1348         rAnimList.append(aLoop);
1349     }
1350 
1351     if(nRepeat % 2L)
1352     {
1353         // repeat is uneven, so we need one more forth and back to center
1354         drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition);
1355         rAnimList.append(aTime0);
1356         drawinglayer::animation::AnimationEntryLinear aTime1(fHalfInnerPath, fFrequency, fEndPosition, 0.5);
1357         rAnimList.append(aTime1);
1358     }
1359 
1360     if(0 == nRepeat)
1361         return;
1362 
1363     bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue());
1364     if(bVisibleWhenStopped)
1365     {
1366         // add timing for staying at the end
1367         drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1368         rAnimList.append(aEnd);
1369     }
1370     else
1371     {
1372         // move from center to outside
1373         drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0);
1374         rAnimList.append(aInOut);
1375     }
1376 }
1377 
impCreateSlideTiming(const SfxItemSet & rSet,drawinglayer::animation::AnimationEntryList & rAnimList,bool bForward,double fTimeFullPath,double fFrequency)1378 static void impCreateSlideTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency)
1379 {
1380     // move in from outside, start outside
1381     const double fStartPosition(bForward ? 0.0 : 1.0);
1382     const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue());
1383 
1384     // move from outside to center
1385     drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5);
1386     rAnimList.append(aOutIn);
1387 
1388     // loop. In loop, move out and in again
1389     if(nRepeat > 1 || 0 == nRepeat)
1390     {
1391         drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat - 1 : ENDLESS_LOOP);
1392         drawinglayer::animation::AnimationEntryLinear aTime0(fTimeFullPath * 0.5, fFrequency, 0.5, fStartPosition);
1393         aLoop.append(aTime0);
1394         drawinglayer::animation::AnimationEntryLinear aTime1(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5);
1395         aLoop.append(aTime1);
1396         rAnimList.append(aLoop);
1397     }
1398 
1399     // always visible when stopped, so add timing for staying at the end when not endless
1400     if(0 != nRepeat)
1401     {
1402         drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1403         rAnimList.append(aEnd);
1404     }
1405 }
1406 
impGetScrollTextTiming(drawinglayer::animation::AnimationEntryList & rAnimList,double fFrameLength,double fTextLength) const1407 void SdrTextObj::impGetScrollTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList, double fFrameLength, double fTextLength) const
1408 {
1409     const SdrTextAniKind eAniKind(GetTextAniKind());
1410 
1411     if(SdrTextAniKind::Scroll != eAniKind && SdrTextAniKind::Alternate != eAniKind && SdrTextAniKind::Slide != eAniKind)
1412         return;
1413 
1414     // get data. Goal is to calculate fTimeFullPath which is the time needed to
1415     // move animation from (0.0) to (1.0) state
1416     const SfxItemSet& rSet = GetObjectItemSet();
1417     double fAnimationDelay(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIDELAY).GetValue()));
1418     double fSingleStepWidth(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIAMOUNT).GetValue()));
1419     const SdrTextAniDirection eDirection(GetTextAniDirection());
1420     const bool bForward(SdrTextAniDirection::Right == eDirection || SdrTextAniDirection::Down == eDirection);
1421 
1422     if(basegfx::fTools::equalZero(fAnimationDelay))
1423     {
1424         // default to 1/20 second
1425         fAnimationDelay = 50.0;
1426     }
1427 
1428     if(basegfx::fTools::less(fSingleStepWidth, 0.0))
1429     {
1430         // data is in pixels, convert to logic. Imply PIXEL_DPI dpi.
1431         // It makes no sense to keep the view-transformation centered
1432         // definitions, so get rid of them here.
1433         fSingleStepWidth = (-fSingleStepWidth * (2540.0 / PIXEL_DPI));
1434     }
1435 
1436     if(basegfx::fTools::equalZero(fSingleStepWidth))
1437     {
1438         // default to 1 millimeter
1439         fSingleStepWidth = 100.0;
1440     }
1441 
1442     // use the length of the full animation path and the number of steps
1443     // to get the full path time
1444     const double fFullPathLength(fFrameLength + fTextLength);
1445     const double fNumberOfSteps(fFullPathLength / fSingleStepWidth);
1446     double fTimeFullPath(fNumberOfSteps * fAnimationDelay);
1447 
1448     if(fTimeFullPath < fAnimationDelay)
1449     {
1450         fTimeFullPath = fAnimationDelay;
1451     }
1452 
1453     switch(eAniKind)
1454     {
1455         case SdrTextAniKind::Scroll :
1456         {
1457             impCreateScrollTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay);
1458             break;
1459         }
1460         case SdrTextAniKind::Alternate :
1461         {
1462             double fRelativeTextLength(fTextLength / (fFrameLength + fTextLength));
1463             impCreateAlternateTiming(rSet, rAnimList, fRelativeTextLength, bForward, fTimeFullPath, fAnimationDelay);
1464             break;
1465         }
1466         case SdrTextAniKind::Slide :
1467         {
1468             impCreateSlideTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay);
1469             break;
1470         }
1471         default : break; // SdrTextAniKind::NONE, SdrTextAniKind::Blink
1472     }
1473 }
1474 
impHandleChainingEventsDuringDecomposition(SdrOutliner & rOutliner) const1475 void SdrTextObj::impHandleChainingEventsDuringDecomposition(SdrOutliner &rOutliner) const
1476 {
1477     if (GetTextChain()->GetNilChainingEvent(this))
1478         return;
1479 
1480     GetTextChain()->SetNilChainingEvent(this, true);
1481 
1482     TextChainFlow aTxtChainFlow(const_cast<SdrTextObj*>(this));
1483     bool bIsOverflow;
1484 
1485 #ifdef DBG_UTIL
1486     // Some debug output
1487     size_t nObjCount(getSdrPageFromSdrObject()->GetObjCount());
1488     for (size_t i = 0; i < nObjCount; i++)
1489     {
1490         SdrTextObj* pCurObj(dynamic_cast< SdrTextObj* >(getSdrPageFromSdrObject()->GetObj(i)));
1491         if(pCurObj == this)
1492         {
1493             SAL_INFO("svx.chaining", "Working on TextBox " << i);
1494             break;
1495         }
1496     }
1497 #endif
1498 
1499     aTxtChainFlow.CheckForFlowEvents(&rOutliner);
1500 
1501     if (aTxtChainFlow.IsUnderflow() && !IsInEditMode())
1502     {
1503         // underflow-induced overflow
1504         aTxtChainFlow.ExecuteUnderflow(&rOutliner);
1505         bIsOverflow = aTxtChainFlow.IsOverflow();
1506     } else {
1507         // standard overflow (no underflow before)
1508         bIsOverflow = aTxtChainFlow.IsOverflow();
1509     }
1510 
1511     if (bIsOverflow && !IsInEditMode()) {
1512         // Initialize Chaining Outliner
1513         SdrOutliner &rChainingOutl(getSdrModelFromSdrObject().GetChainingOutliner(this));
1514         ImpInitDrawOutliner( rChainingOutl );
1515         rChainingOutl.SetUpdateMode(true);
1516         // We must pass the chaining outliner otherwise we would mess up decomposition
1517         aTxtChainFlow.ExecuteOverflow(&rOutliner, &rChainingOutl);
1518     }
1519 
1520     GetTextChain()->SetNilChainingEvent(this, false);
1521 }
1522 
impDecomposeChainedTextPrimitive(drawinglayer::primitive2d::Primitive2DContainer & rTarget,const drawinglayer::primitive2d::SdrChainedTextPrimitive2D & rSdrChainedTextPrimitive,const drawinglayer::geometry::ViewInformation2D & aViewInformation) const1523 void SdrTextObj::impDecomposeChainedTextPrimitive(
1524         drawinglayer::primitive2d::Primitive2DContainer& rTarget,
1525         const drawinglayer::primitive2d::SdrChainedTextPrimitive2D& rSdrChainedTextPrimitive,
1526         const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
1527 {
1528     // decompose matrix to have position and size of text
1529     basegfx::B2DVector aScale, aTranslate;
1530     double fRotate, fShearX;
1531     rSdrChainedTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
1532 
1533     // use B2DRange aAnchorTextRange for calculations
1534     basegfx::B2DRange aAnchorTextRange(aTranslate);
1535     aAnchorTextRange.expand(aTranslate + aScale);
1536 
1537     // prepare outliner
1538     const SfxItemSet& rTextItemSet = rSdrChainedTextPrimitive.getSdrText()->GetItemSet();
1539     SolarMutexGuard aSolarGuard;
1540     SdrOutliner& rOutliner = ImpGetDrawOutliner();
1541 
1542     SdrTextVertAdjust eVAdj = GetTextVerticalAdjust(rTextItemSet);
1543     SdrTextHorzAdjust eHAdj = GetTextHorizontalAdjust(rTextItemSet);
1544     const EEControlBits nOriginalControlWord(rOutliner.GetControlWord());
1545     const Size aNullSize;
1546 
1547     // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
1548     rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
1549 
1550     rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE|EEControlBits::STRETCHING);
1551     rOutliner.SetMinAutoPaperSize(aNullSize);
1552     rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
1553 
1554     // add one to rage sizes to get back to the old Rectangle and outliner measurements
1555     const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1));
1556     const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1));
1557 
1558     // Text
1559     const OutlinerParaObject* pOutlinerParaObject = rSdrChainedTextPrimitive.getSdrText()->GetOutlinerParaObject();
1560     OSL_ENSURE(pOutlinerParaObject, "impDecomposeBlockTextPrimitive used with no OutlinerParaObject (!)");
1561 
1562     const bool bVerticalWriting(pOutlinerParaObject->IsVertical());
1563     const bool bTopToBottom(pOutlinerParaObject->IsTopToBottom());
1564     const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight));
1565 
1566     if(IsTextFrame())
1567     {
1568         rOutliner.SetMaxAutoPaperSize(aAnchorTextSize);
1569     }
1570 
1571     if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting)
1572     {
1573         rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0));
1574     }
1575 
1576     if(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting)
1577     {
1578         rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight));
1579     }
1580 
1581     rOutliner.SetPaperSize(aNullSize);
1582     rOutliner.SetUpdateMode(true);
1583     // Sets original text
1584     rOutliner.SetText(*pOutlinerParaObject);
1585 
1586     /* Begin overflow/underflow handling */
1587 
1588     impHandleChainingEventsDuringDecomposition(rOutliner);
1589 
1590     /* End overflow/underflow handling */
1591 
1592     // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
1593     rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
1594 
1595     // now get back the layouted text size from outliner
1596     const Size aOutlinerTextSize(rOutliner.GetPaperSize());
1597     const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height());
1598     basegfx::B2DVector aAdjustTranslate(0.0, 0.0);
1599 
1600     // correct horizontal translation using the now known text size
1601     if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj)
1602     {
1603         const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX());
1604 
1605         if(SDRTEXTHORZADJUST_CENTER == eHAdj)
1606         {
1607             aAdjustTranslate.setX(fFree / 2.0);
1608         }
1609 
1610         if(SDRTEXTHORZADJUST_RIGHT == eHAdj)
1611         {
1612             aAdjustTranslate.setX(fFree);
1613         }
1614     }
1615 
1616     // correct vertical translation using the now known text size
1617     if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj)
1618     {
1619         const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY());
1620 
1621         if(SDRTEXTVERTADJUST_CENTER == eVAdj)
1622         {
1623             aAdjustTranslate.setY(fFree / 2.0);
1624         }
1625 
1626         if(SDRTEXTVERTADJUST_BOTTOM == eVAdj)
1627         {
1628             aAdjustTranslate.setY(fFree);
1629         }
1630     }
1631 
1632     // prepare matrices to apply to newly created primitives. aNewTransformA
1633     // will get coordinates in aOutlinerScale size and positive in X, Y.
1634     basegfx::B2DHomMatrix aNewTransformA;
1635     basegfx::B2DHomMatrix aNewTransformB;
1636 
1637     // translate relative to given primitive to get same rotation and shear
1638     // as the master shape we are working on. For vertical, use the top-right
1639     // corner
1640     const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX());
1641     const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY());
1642     aNewTransformA.translate(fStartInX, fStartInY);
1643 
1644     // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
1645     // move the null point which was top left to bottom right.
1646     const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
1647     const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
1648     aNewTransformB.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0);
1649 
1650     // in-between the translations of the single primitives will take place. Afterwards,
1651     // the object's transformations need to be applied
1652     aNewTransformB.shearX(fShearX);
1653     aNewTransformB.rotate(fRotate);
1654     aNewTransformB.translate(aTranslate.getX(), aTranslate.getY());
1655 
1656     basegfx::B2DRange aClipRange;
1657 
1658     // now break up text primitives.
1659     impTextBreakupHandler aConverter(rOutliner);
1660     aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange);
1661 
1662     // cleanup outliner
1663     rOutliner.Clear();
1664     rOutliner.setVisualizedPage(nullptr);
1665     rOutliner.SetControlWord(nOriginalControlWord);
1666 
1667     rTarget = aConverter.getPrimitive2DSequence();
1668 }
1669 
1670 // Direct decomposer for text visualization when you already have a prepared
1671 // Outliner containing all the needed information
impDecomposeBlockTextPrimitiveDirect(drawinglayer::primitive2d::Primitive2DContainer & rTarget,SdrOutliner & rOutliner,const basegfx::B2DHomMatrix & rNewTransformA,const basegfx::B2DHomMatrix & rNewTransformB,const basegfx::B2DRange & rClipRange)1672 void SdrTextObj::impDecomposeBlockTextPrimitiveDirect(
1673     drawinglayer::primitive2d::Primitive2DContainer& rTarget,
1674     SdrOutliner& rOutliner,
1675     const basegfx::B2DHomMatrix& rNewTransformA,
1676     const basegfx::B2DHomMatrix& rNewTransformB,
1677     const basegfx::B2DRange& rClipRange)
1678 {
1679     impTextBreakupHandler aConverter(rOutliner);
1680     aConverter.decomposeBlockTextPrimitive(rNewTransformA, rNewTransformB, rClipRange);
1681     rTarget.append(aConverter.getPrimitive2DSequence());
1682 }
1683 
GetCameraZRotation() const1684 double SdrTextObj::GetCameraZRotation() const
1685 {
1686     const css::uno::Any* pAny;
1687     double fTextCameraZRotateAngle = 0.0;
1688     const SfxItemSet& rSet = GetObjectItemSet();
1689     const SdrCustomShapeGeometryItem& rGeometryItem(rSet.Get(SDRATTR_CUSTOMSHAPE_GEOMETRY));
1690 
1691     pAny = rGeometryItem.GetPropertyValueByName("TextCameraZRotateAngle");
1692 
1693     if ( pAny )
1694         *pAny >>= fTextCameraZRotateAngle;
1695 
1696     return fTextCameraZRotateAngle;
1697 }
1698 
1699 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1700