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