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 #include <sdr/primitive2d/sdrdecompositiontools.hxx>
21 #include <drawinglayer/primitive2d/baseprimitive2d.hxx>
22 #include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx>
23 #include <drawinglayer/primitive2d/PolyPolygonHatchPrimitive2D.hxx>
24 #include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
25 #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
26 #include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
27 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
28 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
29 #include <basegfx/polygon/b2dpolypolygontools.hxx>
30 #include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
31 #include <drawinglayer/attribute/strokeattribute.hxx>
32 #include <drawinglayer/attribute/linestartendattribute.hxx>
33 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
34 #include <drawinglayer/attribute/sdrfillgraphicattribute.hxx>
35 #include <basegfx/matrix/b2dhommatrix.hxx>
36 #include <drawinglayer/primitive2d/shadowprimitive2d.hxx>
37 #include <sdr/attribute/sdrtextattribute.hxx>
38 #include <drawinglayer/primitive2d/glowprimitive2d.hxx>
39 #include <sdr/primitive2d/sdrtextprimitive2d.hxx>
40 #include <svx/svdotext.hxx>
41 #include <basegfx/polygon/b2dpolygontools.hxx>
42 #include <drawinglayer/primitive2d/animatedprimitive2d.hxx>
43 #include <drawinglayer/animation/animationtiming.hxx>
44 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
45 #include <drawinglayer/geometry/viewinformation2d.hxx>
46 #include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
47 #include <drawinglayer/attribute/sdrfillattribute.hxx>
48 #include <drawinglayer/attribute/sdrlineattribute.hxx>
49 #include <drawinglayer/attribute/sdrlinestartendattribute.hxx>
50 #include <drawinglayer/attribute/sdrshadowattribute.hxx>
51 #include <drawinglayer/attribute/sdrglowattribute.hxx>
52 
53 
54 using namespace com::sun::star;
55 
56 
57 namespace drawinglayer::primitive2d
58 {
59 namespace
60 {
61 // See also: SdrTextObj::AdjustRectToTextDistance
getTextAnchorRange(const attribute::SdrTextAttribute & rText,const basegfx::B2DRange & rSnapRange)62 basegfx::B2DRange getTextAnchorRange(const attribute::SdrTextAttribute& rText,
63                                      const basegfx::B2DRange& rSnapRange)
64 {
65     // Take vertical text orientation into account when deciding
66     // which dimension is its width, and which is its height
67     const OutlinerParaObject& rOutlinerParaObj = rText.getOutlinerParaObject();
68     const bool bVerticalWriting(rOutlinerParaObj.IsVertical());
69     const double fWidthForText = bVerticalWriting ? rSnapRange.getHeight() : rSnapRange.getWidth();
70     // create a range describing the wanted text position and size (aTextAnchorRange). This
71     // means to use the text distance values here
72     // If the margin is larger than the entire width of the text area, then limit the
73     // margin.
74     const double fTextLeftDistance
75         = std::min(static_cast<double>(rText.getTextLeftDistance()), fWidthForText);
76     const double nTextRightDistance
77         = std::min(static_cast<double>(rText.getTextRightDistance()), fWidthForText);
78     double fDistanceForTextL, fDistanceForTextT, fDistanceForTextR, fDistanceForTextB;
79     if (!bVerticalWriting)
80     {
81         fDistanceForTextL = fTextLeftDistance;
82         fDistanceForTextT = rText.getTextUpperDistance();
83         fDistanceForTextR = nTextRightDistance;
84         fDistanceForTextB = rText.getTextLowerDistance();
85     }
86     else if (rOutlinerParaObj.IsTopToBottom())
87     {
88         fDistanceForTextL = rText.getTextLowerDistance();
89         fDistanceForTextT = fTextLeftDistance;
90         fDistanceForTextR = rText.getTextUpperDistance();
91         fDistanceForTextB = nTextRightDistance;
92     }
93     else
94     {
95         fDistanceForTextL = rText.getTextUpperDistance();
96         fDistanceForTextT = nTextRightDistance;
97         fDistanceForTextR = rText.getTextLowerDistance();
98         fDistanceForTextB = fTextLeftDistance;
99     }
100     const basegfx::B2DPoint aTopLeft(rSnapRange.getMinX() + fDistanceForTextL,
101                                      rSnapRange.getMinY() + fDistanceForTextT);
102     const basegfx::B2DPoint aBottomRight(rSnapRange.getMaxX() - fDistanceForTextR,
103                                          rSnapRange.getMaxY() - fDistanceForTextB);
104     basegfx::B2DRange aAnchorRange;
105     aAnchorRange.expand(aTopLeft);
106     aAnchorRange.expand(aBottomRight);
107 
108     // If the shape has no width, then don't attempt to break the text into multiple
109     // lines, not a single character would satisfy a zero width requirement.
110     // SdrTextObj::impDecomposeBlockTextPrimitive() uses the same constant to
111     // effectively set no limits.
112     if (!bVerticalWriting && aAnchorRange.getWidth() == 0)
113     {
114         aAnchorRange.expand(basegfx::B2DPoint(aTopLeft.getX() - 1000000, aTopLeft.getY()));
115         aAnchorRange.expand(basegfx::B2DPoint(aBottomRight.getX() + 1000000, aBottomRight.getY()));
116     }
117     else if (bVerticalWriting && aAnchorRange.getHeight() == 0)
118     {
119         aAnchorRange.expand(basegfx::B2DPoint(aTopLeft.getX(), aTopLeft.getY() - 1000000));
120         aAnchorRange.expand(basegfx::B2DPoint(aBottomRight.getX(), aBottomRight.getY() + 1000000));
121     }
122     return aAnchorRange;
123 }
124 };
125 
126         class TransparencePrimitive2D;
127 
createPolyPolygonFillPrimitive(const basegfx::B2DPolyPolygon & rPolyPolygon,const attribute::SdrFillAttribute & rFill,const attribute::FillGradientAttribute & rFillGradient)128         Primitive2DReference createPolyPolygonFillPrimitive(
129             const basegfx::B2DPolyPolygon& rPolyPolygon,
130             const attribute::SdrFillAttribute& rFill,
131             const attribute::FillGradientAttribute& rFillGradient)
132         {
133             // when we have no given definition range, use the range of the given geometry
134             // also for definition (simplest case)
135             const basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyPolygon));
136 
137             return createPolyPolygonFillPrimitive(
138                 rPolyPolygon,
139                 aRange,
140                 rFill,
141                 rFillGradient);
142         }
143 
createPolyPolygonFillPrimitive(const basegfx::B2DPolyPolygon & rPolyPolygon,const basegfx::B2DRange & rDefinitionRange,const attribute::SdrFillAttribute & rFill,const attribute::FillGradientAttribute & rFillGradient)144         Primitive2DReference createPolyPolygonFillPrimitive(
145             const basegfx::B2DPolyPolygon& rPolyPolygon,
146             const basegfx::B2DRange& rDefinitionRange,
147             const attribute::SdrFillAttribute& rFill,
148             const attribute::FillGradientAttribute& rFillGradient)
149         {
150             if(basegfx::fTools::moreOrEqual(rFill.getTransparence(), 1.0))
151             {
152                 return Primitive2DReference();
153             }
154 
155             // prepare fully scaled polygon
156             rtl::Reference<BasePrimitive2D> pNewFillPrimitive;
157 
158             if(!rFill.getGradient().isDefault())
159             {
160                 pNewFillPrimitive = new PolyPolygonGradientPrimitive2D(
161                     rPolyPolygon,
162                     rDefinitionRange,
163                     rFill.getGradient());
164             }
165             else if(!rFill.getHatch().isDefault())
166             {
167                 pNewFillPrimitive = new PolyPolygonHatchPrimitive2D(
168                     rPolyPolygon,
169                     rDefinitionRange,
170                     rFill.getColor(),
171                     rFill.getHatch());
172             }
173             else if(!rFill.getFillGraphic().isDefault())
174             {
175                 pNewFillPrimitive = new PolyPolygonGraphicPrimitive2D(
176                     rPolyPolygon,
177                     rDefinitionRange,
178                     rFill.getFillGraphic().createFillGraphicAttribute(rDefinitionRange));
179             }
180             else
181             {
182                 pNewFillPrimitive = new PolyPolygonColorPrimitive2D(
183                     rPolyPolygon,
184                     rFill.getColor());
185             }
186 
187             if(0.0 != rFill.getTransparence())
188             {
189                 // create simpleTransparencePrimitive, add created fill primitive
190                 const Primitive2DContainer aContent { pNewFillPrimitive };
191                 return Primitive2DReference(new UnifiedTransparencePrimitive2D(aContent, rFill.getTransparence()));
192             }
193             else if(!rFillGradient.isDefault())
194             {
195                 // create sequence with created fill primitive
196                 const Primitive2DContainer aContent { pNewFillPrimitive };
197 
198                 // create FillGradientPrimitive2D for transparence and add to new sequence
199                 // fillGradientPrimitive is enough here (compared to PolyPolygonGradientPrimitive2D) since float transparence will be masked anyways
200                 const basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyPolygon));
201                 const Primitive2DReference xRefB(
202                     new FillGradientPrimitive2D(
203                         aRange,
204                         rDefinitionRange,
205                         rFillGradient));
206                 const Primitive2DContainer aAlpha { xRefB };
207 
208                 // create TransparencePrimitive2D using alpha and content
209                 return Primitive2DReference(new TransparencePrimitive2D(aContent, aAlpha));
210             }
211             else
212             {
213                 // add to decomposition
214                 return Primitive2DReference(pNewFillPrimitive);
215             }
216         }
217 
createPolygonLinePrimitive(const basegfx::B2DPolygon & rPolygon,const attribute::SdrLineAttribute & rLine,const attribute::SdrLineStartEndAttribute & rStroke)218         Primitive2DReference createPolygonLinePrimitive(
219             const basegfx::B2DPolygon& rPolygon,
220             const attribute::SdrLineAttribute& rLine,
221             const attribute::SdrLineStartEndAttribute& rStroke)
222         {
223             // create line and stroke attribute
224             const attribute::LineAttribute aLineAttribute(rLine.getColor(), rLine.getWidth(), rLine.getJoin(), rLine.getCap());
225             const attribute::StrokeAttribute aStrokeAttribute(rLine.getDotDashArray(), rLine.getFullDotDashLen());
226             rtl::Reference<BasePrimitive2D> pNewLinePrimitive;
227 
228             if(!rPolygon.isClosed() && !rStroke.isDefault())
229             {
230                 attribute::LineStartEndAttribute aStart(rStroke.getStartWidth(), rStroke.getStartPolyPolygon(), rStroke.isStartCentered());
231                 attribute::LineStartEndAttribute aEnd(rStroke.getEndWidth(), rStroke.getEndPolyPolygon(), rStroke.isEndCentered());
232 
233                 // create data
234                 pNewLinePrimitive = new PolygonStrokeArrowPrimitive2D(rPolygon, aLineAttribute, aStrokeAttribute, aStart, aEnd);
235             }
236             else
237             {
238                 // create data
239                 pNewLinePrimitive = new PolygonStrokePrimitive2D(rPolygon, aLineAttribute, aStrokeAttribute);
240             }
241 
242             if(0.0 != rLine.getTransparence())
243             {
244                 // create simpleTransparencePrimitive, add created fill primitive
245                 const Primitive2DContainer aContent { pNewLinePrimitive };
246                 return Primitive2DReference(new UnifiedTransparencePrimitive2D(aContent, rLine.getTransparence()));
247             }
248             else
249             {
250                 // add to decomposition
251                 return Primitive2DReference(pNewLinePrimitive);
252             }
253         }
254 
createTextPrimitive(const basegfx::B2DPolyPolygon & rUnitPolyPolygon,const basegfx::B2DHomMatrix & rObjectTransform,const attribute::SdrTextAttribute & rText,const attribute::SdrLineAttribute & rStroke,bool bCellText,bool bWordWrap)255         Primitive2DReference createTextPrimitive(
256             const basegfx::B2DPolyPolygon& rUnitPolyPolygon,
257             const basegfx::B2DHomMatrix& rObjectTransform,
258             const attribute::SdrTextAttribute& rText,
259             const attribute::SdrLineAttribute& rStroke,
260             bool bCellText,
261             bool bWordWrap)
262         {
263             basegfx::B2DHomMatrix aAnchorTransform(rObjectTransform);
264             rtl::Reference<SdrTextPrimitive2D> pNew;
265 
266             if(rText.isContour())
267             {
268                 // contour text
269                 if(!rStroke.isDefault() && 0.0 != rStroke.getWidth())
270                 {
271                     // take line width into account and shrink contour polygon accordingly
272                     // decompose to get scale
273                     basegfx::B2DVector aScale, aTranslate;
274                     double fRotate, fShearX;
275                     rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX);
276 
277                     // scale outline to object's size to allow growing with value relative to that size
278                     // and also to keep aspect ratio
279                     basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon);
280                     aScaledUnitPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(
281                         fabs(aScale.getX()), fabs(aScale.getY())));
282 
283                     // grow the polygon. To shrink, use negative value (half width)
284                     aScaledUnitPolyPolygon = basegfx::utils::growInNormalDirection(aScaledUnitPolyPolygon, -(rStroke.getWidth() * 0.5));
285 
286                     // scale back to unit polygon
287                     aScaledUnitPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(
288                         0.0 != aScale.getX() ? 1.0 / aScale.getX() : 1.0,
289                         0.0 != aScale.getY() ? 1.0 / aScale.getY() : 1.0));
290 
291                     // create with unit polygon
292                     pNew = new SdrContourTextPrimitive2D(
293                         &rText.getSdrText(),
294                         rText.getOutlinerParaObject(),
295                         aScaledUnitPolyPolygon,
296                         rObjectTransform);
297                 }
298                 else
299                 {
300                     // create with unit polygon
301                     pNew = new SdrContourTextPrimitive2D(
302                         &rText.getSdrText(),
303                         rText.getOutlinerParaObject(),
304                         rUnitPolyPolygon,
305                         rObjectTransform);
306                 }
307             }
308             else if(!rText.getSdrFormTextAttribute().isDefault())
309             {
310                 // text on path, use scaled polygon
311                 basegfx::B2DPolyPolygon aScaledPolyPolygon(rUnitPolyPolygon);
312                 aScaledPolyPolygon.transform(rObjectTransform);
313                 pNew = new SdrPathTextPrimitive2D(
314                     &rText.getSdrText(),
315                     rText.getOutlinerParaObject(),
316                     aScaledPolyPolygon,
317                     rText.getSdrFormTextAttribute());
318             }
319             else
320             {
321                 // rObjectTransform is the whole SdrObject transformation from unit rectangle
322                 // to its size and position. Decompose to allow working with single values.
323                 basegfx::B2DVector aScale, aTranslate;
324                 double fRotate, fShearX;
325                 rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX);
326 
327                 // extract mirroring
328                 const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
329                 const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
330                 aScale = basegfx::absolute(aScale);
331 
332                 // Get the real size, since polygon outline and scale
333                 // from the object transformation may vary (e.g. ellipse segments)
334                 basegfx::B2DHomMatrix aJustScaleTransform;
335                 aJustScaleTransform.set(0, 0, aScale.getX());
336                 aJustScaleTransform.set(1, 1, aScale.getY());
337                 basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon);
338                 aScaledUnitPolyPolygon.transform(aJustScaleTransform);
339                 const basegfx::B2DRange aTextAnchorRange
340                     = getTextAnchorRange(rText, basegfx::utils::getRange(aScaledUnitPolyPolygon));
341 
342                 // now create a transformation from this basic range (aTextAnchorRange)
343                 // #i121494# if we have no scale use at least 1.0 to have a carrier e.g. for
344                 // mirror values, else these will get lost
345                 aAnchorTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(
346                     basegfx::fTools::equalZero(aTextAnchorRange.getWidth()) ? 1.0 : aTextAnchorRange.getWidth(),
347                     basegfx::fTools::equalZero(aTextAnchorRange.getHeight()) ? 1.0 : aTextAnchorRange.getHeight(),
348                     aTextAnchorRange.getMinX(), aTextAnchorRange.getMinY());
349 
350                 // apply mirroring
351                 aAnchorTransform.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0);
352 
353                 // apply object's other transforms
354                 aAnchorTransform = basegfx::utils::createShearXRotateTranslateB2DHomMatrix(fShearX, fRotate, aTranslate)
355                     * aAnchorTransform;
356 
357                 if(rText.isFitToSize())
358                 {
359                     // stretched text in range
360                     pNew = new SdrStretchTextPrimitive2D(
361                         &rText.getSdrText(),
362                         rText.getOutlinerParaObject(),
363                         aAnchorTransform,
364                         rText.isFixedCellHeight());
365                 }
366                 else if(rText.isAutoFit())
367                 {
368                     // isotropically scaled text in range
369                     pNew = new SdrAutoFitTextPrimitive2D(
370                                     &rText.getSdrText(),
371                                     rText.getOutlinerParaObject(),
372                                     aAnchorTransform,
373                                     bWordWrap);
374                 }
375                 else if( rText.isChainable() && !rText.isInEditMode() )
376                 {
377                     pNew = new SdrChainedTextPrimitive2D(
378                                     &rText.getSdrText(),
379                                     rText.getOutlinerParaObject(),
380                                     aAnchorTransform );
381                 }
382                 else // text in range
383                 {
384                     // build new primitive
385                     pNew = new SdrBlockTextPrimitive2D(
386                         &rText.getSdrText(),
387                         rText.getOutlinerParaObject(),
388                         aAnchorTransform,
389                         rText.getSdrTextHorzAdjust(),
390                         rText.getSdrTextVertAdjust(),
391                         rText.isFixedCellHeight(),
392                         rText.isScroll(),
393                         bCellText,
394                         bWordWrap);
395                 }
396             }
397 
398             OSL_ENSURE(pNew != nullptr, "createTextPrimitive: no text primitive created (!)");
399 
400             if(rText.isBlink())
401             {
402                 // prepare animation and primitive list
403                 drawinglayer::animation::AnimationEntryList aAnimationList;
404                 rText.getBlinkTextTiming(aAnimationList);
405 
406                 if(0.0 != aAnimationList.getDuration())
407                 {
408                     // create content sequence
409                     const Primitive2DReference xRefA(pNew);
410                     const Primitive2DContainer aContent { xRefA };
411 
412                     // create and add animated switch primitive
413                     return Primitive2DReference(new AnimatedBlinkPrimitive2D(aAnimationList, aContent));
414                 }
415                 else
416                 {
417                     // add to decomposition
418                     return Primitive2DReference(pNew);
419                 }
420             }
421 
422             if(rText.isScroll())
423             {
424                 // suppress scroll when FontWork
425                 if(rText.getSdrFormTextAttribute().isDefault())
426                 {
427                     // get scroll direction
428                     const SdrTextAniDirection eDirection(rText.getSdrText().GetObject().GetTextAniDirection());
429                     const bool bHorizontal(SdrTextAniDirection::Left == eDirection || SdrTextAniDirection::Right == eDirection);
430 
431                     // decompose to get separated values for the scroll box
432                     basegfx::B2DVector aScale, aTranslate;
433                     double fRotate, fShearX;
434                     aAnchorTransform.decompose(aScale, aTranslate, fRotate, fShearX);
435 
436                     // build transform from scaled only to full AnchorTransform and inverse
437                     const basegfx::B2DHomMatrix aSRT(basegfx::utils::createShearXRotateTranslateB2DHomMatrix(
438                         fShearX, fRotate, aTranslate));
439                     basegfx::B2DHomMatrix aISRT(aSRT);
440                     aISRT.invert();
441 
442                     // bring the primitive back to scaled only and get scaled range, create new clone for this
443                     rtl::Reference<SdrTextPrimitive2D> pNew2 = pNew->createTransformedClone(aISRT);
444                     OSL_ENSURE(pNew2, "createTextPrimitive: Could not create transformed clone of text primitive (!)");
445                     pNew = pNew2.get();
446 
447                     // create neutral geometry::ViewInformation2D for local range and decompose calls. This is okay
448                     // since the decompose is view-independent
449                     const uno::Sequence< beans::PropertyValue > xViewParameters;
450                     geometry::ViewInformation2D aViewInformation2D(xViewParameters);
451 
452                     // get range
453                     const basegfx::B2DRange aScaledRange(pNew->getB2DRange(aViewInformation2D));
454 
455                     // create left outside and right outside transformations. Also take care
456                     // of the clip rectangle
457                     basegfx::B2DHomMatrix aLeft, aRight;
458                     basegfx::B2DPoint aClipTopLeft(0.0, 0.0);
459                     basegfx::B2DPoint aClipBottomRight(aScale.getX(), aScale.getY());
460 
461                     if(bHorizontal)
462                     {
463                         aClipTopLeft.setY(aScaledRange.getMinY());
464                         aClipBottomRight.setY(aScaledRange.getMaxY());
465                         aLeft.translate(-aScaledRange.getMaxX(), 0.0);
466                         aRight.translate(aScale.getX() - aScaledRange.getMinX(), 0.0);
467                     }
468                     else
469                     {
470                         aClipTopLeft.setX(aScaledRange.getMinX());
471                         aClipBottomRight.setX(aScaledRange.getMaxX());
472                         aLeft.translate(0.0, -aScaledRange.getMaxY());
473                         aRight.translate(0.0, aScale.getY() - aScaledRange.getMinY());
474                     }
475 
476                     aLeft *= aSRT;
477                     aRight *= aSRT;
478 
479                     // prepare animation list
480                     drawinglayer::animation::AnimationEntryList aAnimationList;
481 
482                     if(bHorizontal)
483                     {
484                         rText.getScrollTextTiming(aAnimationList, aScale.getX(), aScaledRange.getWidth());
485                     }
486                     else
487                     {
488                         rText.getScrollTextTiming(aAnimationList, aScale.getY(), aScaledRange.getHeight());
489                     }
490 
491                     if(0.0 != aAnimationList.getDuration())
492                     {
493                         // create a new Primitive2DContainer containing the animated text in its scaled only state.
494                         // use the decomposition to force to simple text primitives, those will no longer
495                         // need the outliner for formatting (alternatively it is also possible to just add
496                         // pNew to aNewPrimitiveSequence)
497                         Primitive2DContainer aAnimSequence;
498                         pNew->get2DDecomposition(aAnimSequence, aViewInformation2D);
499                         pNew.clear();
500 
501                         // create a new animatedInterpolatePrimitive and add it
502                         std::vector< basegfx::B2DHomMatrix > aMatrixStack;
503                         aMatrixStack.push_back(aLeft);
504                         aMatrixStack.push_back(aRight);
505                         const Primitive2DReference xRefA(new AnimatedInterpolatePrimitive2D(aMatrixStack, aAnimationList, aAnimSequence));
506                         const Primitive2DContainer aContent { xRefA };
507 
508                         // scrolling needs an encapsulating clipping primitive
509                         const basegfx::B2DRange aClipRange(aClipTopLeft, aClipBottomRight);
510                         basegfx::B2DPolygon aClipPolygon(basegfx::utils::createPolygonFromRect(aClipRange));
511                         aClipPolygon.transform(aSRT);
512                         return Primitive2DReference(new MaskPrimitive2D(basegfx::B2DPolyPolygon(aClipPolygon), aContent));
513                     }
514                     else
515                     {
516                         // add to decomposition
517                         return Primitive2DReference(pNew);
518                     }
519                 }
520             }
521 
522             if(rText.isInEditMode())
523             {
524                 // #i97628#
525                 // encapsulate with TextHierarchyEditPrimitive2D to allow renderers
526                 // to suppress actively edited content if needed
527                 const Primitive2DReference xRefA(pNew);
528                 const Primitive2DContainer aContent { xRefA };
529 
530                 // create and add TextHierarchyEditPrimitive2D primitive
531                 return Primitive2DReference(new TextHierarchyEditPrimitive2D(aContent));
532             }
533             else
534             {
535                 // add to decomposition
536                 return pNew;
537             }
538         }
539 
createEmbeddedShadowPrimitive(const Primitive2DContainer & rContent,const attribute::SdrShadowAttribute & rShadow,const basegfx::B2DHomMatrix & rObjectMatrix,const Primitive2DContainer * pContentForShadow)540         Primitive2DContainer createEmbeddedShadowPrimitive(
541             const Primitive2DContainer& rContent,
542             const attribute::SdrShadowAttribute& rShadow,
543             const basegfx::B2DHomMatrix& rObjectMatrix,
544             const Primitive2DContainer* pContentForShadow)
545         {
546             if(!rContent.empty())
547             {
548                 basegfx::B2DHomMatrix aShadowOffset;
549 
550                 {
551                     if(rShadow.getSize().getX() != 100000)
552                     {
553                         basegfx::B2DTuple aScale;
554                         basegfx::B2DTuple aTranslate;
555                         double fRotate = 0;
556                         double fShearX = 0;
557                         rObjectMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
558                         // Scale the shadow
559                         double nTranslateX = aTranslate.getX();
560                         double nTranslateY = aTranslate.getY();
561 
562                         // The origin for scaling is the top left corner by default. A negative
563                         // shadow offset changes the origin.
564                         if (rShadow.getOffset().getX() < 0)
565                             nTranslateX += aScale.getX();
566                         if (rShadow.getOffset().getY() < 0)
567                             nTranslateY += aScale.getY();
568 
569                         aShadowOffset.translate(-nTranslateX, -nTranslateY);
570                         aShadowOffset.scale(rShadow.getSize().getX() * 0.00001, rShadow.getSize().getY() * 0.00001);
571                         aShadowOffset.translate(nTranslateX, nTranslateY);
572                     }
573 
574                     aShadowOffset.translate(rShadow.getOffset().getX(), rShadow.getOffset().getY());
575                 }
576 
577                 // create shadow primitive and add content
578                 const Primitive2DContainer& rContentForShadow
579                     = pContentForShadow ? *pContentForShadow : rContent;
580                 int nContentWithTransparence = std::count_if(
581                     rContentForShadow.begin(), rContentForShadow.end(),
582                     [](const Primitive2DReference& xChild) {
583                         auto pChild = dynamic_cast<BufferedDecompositionPrimitive2D*>(xChild.get());
584                         return pChild && pChild->getTransparenceForShadow() != 0;
585                     });
586                 if (nContentWithTransparence == 0)
587                 {
588                     Primitive2DContainer aRetval(2);
589                     aRetval[0] = Primitive2DReference(
590                         new ShadowPrimitive2D(
591                             aShadowOffset,
592                             rShadow.getColor(),
593                             rShadow.getBlur(),
594                             (pContentForShadow ? *pContentForShadow : rContent)));
595 
596                     if(0.0 != rShadow.getTransparence())
597                     {
598                         // create SimpleTransparencePrimitive2D
599                         const Primitive2DContainer aTempContent { aRetval[0] };
600 
601                         aRetval[0] = Primitive2DReference(
602                             new UnifiedTransparencePrimitive2D(
603                                 aTempContent,
604                                 rShadow.getTransparence()));
605                     }
606 
607                     aRetval[1] = Primitive2DReference(new GroupPrimitive2D(rContent));
608                     return aRetval;
609                 }
610 
611                 Primitive2DContainer aRetval;
612                 for (const auto& xChild : rContentForShadow)
613                 {
614                     double fChildTransparence = 0.0;
615                     auto pChild = dynamic_cast<BufferedDecompositionPrimitive2D*>(xChild.get());
616                     if (pChild)
617                     {
618                         fChildTransparence = pChild->getTransparenceForShadow();
619                         fChildTransparence /= 100;
620                     }
621                     aRetval.push_back(Primitive2DReference(
622                         new ShadowPrimitive2D(aShadowOffset, rShadow.getColor(), rShadow.getBlur(),
623                                               { xChild })));
624                     if (rShadow.getTransparence() != 0.0 || fChildTransparence != 0.0)
625                     {
626                         Primitive2DContainer aTempContent{ aRetval.back() };
627 
628                         double fChildAlpha = 1.0 - fChildTransparence;
629                         double fShadowAlpha = 1.0 - rShadow.getTransparence();
630                         double fTransparence = 1.0 - fChildAlpha * fShadowAlpha;
631                         aRetval.back() = Primitive2DReference(new UnifiedTransparencePrimitive2D(
632                             std::move(aTempContent), fTransparence));
633                     }
634                 }
635 
636                 aRetval.push_back(
637                     Primitive2DReference(new GroupPrimitive2D(rContent)));
638                 return aRetval;
639             }
640             else
641             {
642                 return rContent;
643             }
644         }
645 
createEmbeddedGlowPrimitive(const Primitive2DContainer & rContent,const attribute::SdrGlowAttribute & rGlow)646         Primitive2DContainer createEmbeddedGlowPrimitive(
647             const Primitive2DContainer& rContent,
648             const attribute::SdrGlowAttribute& rGlow)
649         {
650             if(rContent.empty())
651                 return rContent;
652             Primitive2DContainer aRetval(2);
653             aRetval[0] = Primitive2DReference(
654                 new GlowPrimitive2D(rGlow.getColor(), rGlow.getRadius(), rContent));
655             aRetval[1] = Primitive2DReference(new GroupPrimitive2D(rContent));
656             return aRetval;
657         }
658 
createEmbeddedSoftEdgePrimitive(const Primitive2DContainer & rContent,sal_Int32 nRadius)659         Primitive2DContainer createEmbeddedSoftEdgePrimitive(const Primitive2DContainer& rContent,
660                                                              sal_Int32 nRadius)
661         {
662             if (rContent.empty() || !nRadius)
663                 return rContent;
664             Primitive2DContainer aRetval(1);
665             aRetval[0] = Primitive2DReference(new SoftEdgePrimitive2D(nRadius, rContent));
666             return aRetval;
667         }
668 
669 } // end of namespace
670 
671 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
672