xref: /AOO41X/main/svx/source/sdr/primitive2d/sdrdecompositiontools.cxx (revision 54628ca40d27d15cc98fe861da7fff7e60c2f7d6)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 #include "precompiled_svx.hxx"
25 #include <svx/sdr/primitive2d/sdrdecompositiontools.hxx>
26 #include <drawinglayer/primitive2d/baseprimitive2d.hxx>
27 #include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
28 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
29 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
30 #include <basegfx/polygon/b2dpolypolygontools.hxx>
31 #include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
32 #include <drawinglayer/attribute/strokeattribute.hxx>
33 #include <drawinglayer/attribute/linestartendattribute.hxx>
34 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
35 #include <drawinglayer/attribute/sdrfillgraphicattribute.hxx>
36 #include <basegfx/matrix/b2dhommatrix.hxx>
37 #include <drawinglayer/primitive2d/shadowprimitive2d.hxx>
38 #include <svx/sdr/attribute/sdrtextattribute.hxx>
39 #include <svx/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 <basegfx/tools/canvastools.hxx>
46 #include <drawinglayer/geometry/viewinformation2d.hxx>
47 #include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
48 #include <drawinglayer/attribute/sdrfillattribute.hxx>
49 #include <drawinglayer/attribute/sdrlineattribute.hxx>
50 #include <drawinglayer/attribute/sdrlinestartendattribute.hxx>
51 #include <drawinglayer/attribute/sdrshadowattribute.hxx>
52 
53 //////////////////////////////////////////////////////////////////////////////
54 
55 using namespace com::sun::star;
56 
57 //////////////////////////////////////////////////////////////////////////////
58 
59 namespace drawinglayer
60 {
61     namespace primitive2d
62     {
63         Primitive2DReference createPolyPolygonFillPrimitive(
64             const basegfx::B2DPolyPolygon& rUnitPolyPolygon,
65             const basegfx::B2DHomMatrix& rObjectTransform,
66             const attribute::SdrFillAttribute& rFill,
67             const attribute::FillGradientAttribute& rFillGradient)
68         {
69             // prepare fully scaled polygon
70             basegfx::B2DPolyPolygon aScaledPolyPolygon(rUnitPolyPolygon);
71             aScaledPolyPolygon.transform(rObjectTransform);
72             BasePrimitive2D* pNewFillPrimitive = 0;
73 
74             if(!rFill.getGradient().isDefault())
75             {
76                 pNewFillPrimitive = new PolyPolygonGradientPrimitive2D(aScaledPolyPolygon, rFill.getGradient());
77             }
78             else if(!rFill.getHatch().isDefault())
79             {
80                 pNewFillPrimitive = new PolyPolygonHatchPrimitive2D(aScaledPolyPolygon, rFill.getColor(), rFill.getHatch());
81             }
82             else if(!rFill.getFillGraphic().isDefault())
83             {
84                 const basegfx::B2DRange aRange(basegfx::tools::getRange(aScaledPolyPolygon));
85                 pNewFillPrimitive = new PolyPolygonGraphicPrimitive2D(aScaledPolyPolygon, rFill.getFillGraphic().createFillGraphicAttribute(aRange));
86             }
87             else
88             {
89                 pNewFillPrimitive = new PolyPolygonColorPrimitive2D(aScaledPolyPolygon, rFill.getColor());
90             }
91 
92             if(0.0 != rFill.getTransparence())
93             {
94                 // create simpleTransparencePrimitive, add created fill primitive
95                 const Primitive2DReference xRefA(pNewFillPrimitive);
96                 const Primitive2DSequence aContent(&xRefA, 1L);
97                 return Primitive2DReference(new UnifiedTransparencePrimitive2D(aContent, rFill.getTransparence()));
98             }
99             else if(!rFillGradient.isDefault())
100             {
101                 // create sequence with created fill primitive
102                 const Primitive2DReference xRefA(pNewFillPrimitive);
103                 const Primitive2DSequence aContent(&xRefA, 1L);
104 
105                 // create FillGradientPrimitive2D for transparence and add to new sequence
106                 // fillGradientPrimitive is enough here (compared to PolyPolygonGradientPrimitive2D) since float transparence will be masked anyways
107                 const basegfx::B2DRange aRange(basegfx::tools::getRange(aScaledPolyPolygon));
108                 const Primitive2DReference xRefB(new FillGradientPrimitive2D(aRange, rFillGradient));
109                 const Primitive2DSequence aAlpha(&xRefB, 1L);
110 
111                 // create TransparencePrimitive2D using alpha and content
112                 return Primitive2DReference(new TransparencePrimitive2D(aContent, aAlpha));
113             }
114             else
115             {
116                 // add to decomposition
117                 return Primitive2DReference(pNewFillPrimitive);
118             }
119         }
120 
121         Primitive2DReference createPolygonLinePrimitive(
122             const basegfx::B2DPolygon& rUnitPolygon,
123             const basegfx::B2DHomMatrix& rObjectTransform,
124             const attribute::SdrLineAttribute& rLine,
125             const attribute::SdrLineStartEndAttribute& rStroke)
126         {
127             // prepare fully scaled polygon
128             basegfx::B2DPolygon aScaledPolygon(rUnitPolygon);
129             aScaledPolygon.transform(rObjectTransform);
130 
131             // create line and stroke attribute
132             const attribute::LineAttribute aLineAttribute(rLine.getColor(), rLine.getWidth(), rLine.getJoin(), rLine.getCap());
133             const attribute::StrokeAttribute aStrokeAttribute(rLine.getDotDashArray(), rLine.getFullDotDashLen());
134             BasePrimitive2D* pNewLinePrimitive = 0L;
135 
136             if(!rUnitPolygon.isClosed() && !rStroke.isDefault())
137             {
138                 attribute::LineStartEndAttribute aStart(rStroke.getStartWidth(), rStroke.getStartPolyPolygon(), rStroke.isStartCentered());
139                 attribute::LineStartEndAttribute aEnd(rStroke.getEndWidth(), rStroke.getEndPolyPolygon(), rStroke.isEndCentered());
140 
141                 // create data
142                 pNewLinePrimitive = new PolygonStrokeArrowPrimitive2D(aScaledPolygon, aLineAttribute, aStrokeAttribute, aStart, aEnd);
143             }
144             else
145             {
146                 // create data
147                 pNewLinePrimitive = new PolygonStrokePrimitive2D(aScaledPolygon, aLineAttribute, aStrokeAttribute);
148             }
149 
150             if(0.0 != rLine.getTransparence())
151             {
152                 // create simpleTransparencePrimitive, add created fill primitive
153                 const Primitive2DReference xRefA(pNewLinePrimitive);
154                 const Primitive2DSequence aContent(&xRefA, 1L);
155                 return Primitive2DReference(new UnifiedTransparencePrimitive2D(aContent, rLine.getTransparence()));
156             }
157             else
158             {
159                 // add to decomposition
160                 return Primitive2DReference(pNewLinePrimitive);
161             }
162         }
163 
164         Primitive2DReference createTextPrimitive(
165             const basegfx::B2DPolyPolygon& rUnitPolyPolygon,
166             const basegfx::B2DHomMatrix& rObjectTransform,
167             const attribute::SdrTextAttribute& rText,
168             const attribute::SdrLineAttribute& rStroke,
169             bool bCellText,
170             bool bWordWrap,
171             bool bClipOnBounds)
172         {
173             basegfx::B2DHomMatrix aAnchorTransform(rObjectTransform);
174             SdrTextPrimitive2D* pNew = 0;
175 
176             if(rText.isContour())
177             {
178                 // contour text
179                 if(!rStroke.isDefault() && 0.0 != rStroke.getWidth())
180                 {
181                     // take line width into account and shrink contour polygon accordingly
182                     // decompose to get scale
183                     basegfx::B2DVector aScale, aTranslate;
184                     double fRotate, fShearX;
185                     rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX);
186 
187                     // scale outline to object's size to allow growing with value relative to that size
188                     // and also to keep aspect ratio
189                     basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon);
190                     aScaledUnitPolyPolygon.transform(basegfx::tools::createScaleB2DHomMatrix(
191                         fabs(aScale.getX()), fabs(aScale.getY())));
192 
193                     // grow the polygon. To shrink, use negative value (half width)
194                     aScaledUnitPolyPolygon = basegfx::tools::growInNormalDirection(aScaledUnitPolyPolygon, -(rStroke.getWidth() * 0.5));
195 
196                     // scale back to unit polygon
197                     aScaledUnitPolyPolygon.transform(basegfx::tools::createScaleB2DHomMatrix(
198                         0.0 != aScale.getX() ? 1.0 / aScale.getX() : 1.0,
199                         0.0 != aScale.getY() ? 1.0 / aScale.getY() : 1.0));
200 
201                     // create with unit polygon
202                     pNew = new SdrContourTextPrimitive2D(
203                         &rText.getSdrText(),
204                         rText.getOutlinerParaObject(),
205                         aScaledUnitPolyPolygon,
206                         rObjectTransform);
207                 }
208                 else
209                 {
210                     // create with unit polygon
211                     pNew = new SdrContourTextPrimitive2D(
212                         &rText.getSdrText(),
213                         rText.getOutlinerParaObject(),
214                         rUnitPolyPolygon,
215                         rObjectTransform);
216                 }
217             }
218             else if(!rText.getSdrFormTextAttribute().isDefault())
219             {
220                 // text on path, use scaled polygon
221                 basegfx::B2DPolyPolygon aScaledPolyPolygon(rUnitPolyPolygon);
222                 aScaledPolyPolygon.transform(rObjectTransform);
223                 pNew = new SdrPathTextPrimitive2D(
224                     &rText.getSdrText(),
225                     rText.getOutlinerParaObject(),
226                     aScaledPolyPolygon,
227                     rText.getSdrFormTextAttribute());
228             }
229             else
230             {
231                 // rObjectTransform is the whole SdrObject transformation from unit rectangle
232                 // to it's size and position. Decompose to allow working with single values.
233                 basegfx::B2DVector aScale, aTranslate;
234                 double fRotate, fShearX;
235                 rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX);
236 
237                 // extract mirroring
238                 const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
239                 const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
240                 aScale = basegfx::absolute(aScale);
241 
242                 // Get the real size, since polygon ountline and scale
243                 // from the object transformation may vary (e.g. ellipse segments)
244                 basegfx::B2DHomMatrix aJustScaleTransform;
245                 aJustScaleTransform.set(0, 0, aScale.getX());
246                 aJustScaleTransform.set(1, 1, aScale.getY());
247                 basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon);
248                 aScaledUnitPolyPolygon.transform(aJustScaleTransform);
249                 const basegfx::B2DRange aSnapRange(basegfx::tools::getRange(aScaledUnitPolyPolygon));
250 
251                 // create a range describing the wanted text position and size (aTextAnchorRange). This
252                 // means to use the text distance values here
253                 const basegfx::B2DPoint aTopLeft(aSnapRange.getMinX() + rText.getTextLeftDistance(), aSnapRange.getMinY() + rText.getTextUpperDistance());
254                 const basegfx::B2DPoint aBottomRight(aSnapRange.getMaxX() - rText.getTextRightDistance(), aSnapRange.getMaxY() - rText.getTextLowerDistance());
255                 basegfx::B2DRange aTextAnchorRange;
256                 aTextAnchorRange.expand(aTopLeft);
257                 aTextAnchorRange.expand(aBottomRight);
258 
259                 // now create a transformation from this basic range (aTextAnchorRange)
260                 aAnchorTransform = basegfx::tools::createScaleTranslateB2DHomMatrix(
261                     aTextAnchorRange.getWidth(), aTextAnchorRange.getHeight(),
262                     aTextAnchorRange.getMinX(), aTextAnchorRange.getMinY());
263 
264                 // apply mirroring
265                 aAnchorTransform.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0);
266 
267                 // apply object's other transforms
268                 aAnchorTransform = basegfx::tools::createShearXRotateTranslateB2DHomMatrix(fShearX, fRotate, aTranslate)
269                     * aAnchorTransform;
270 
271                 if(rText.isFitToSize())
272                 {
273                     // streched text in range
274                     pNew = new SdrStretchTextPrimitive2D(
275                         &rText.getSdrText(),
276                         rText.getOutlinerParaObject(),
277                         aAnchorTransform,
278                         rText.isFixedCellHeight());
279                 }
280                 else // text in range
281                 {
282                     // build new primitive
283                     pNew = new SdrBlockTextPrimitive2D(
284                         &rText.getSdrText(),
285                         rText.getOutlinerParaObject(),
286                         aAnchorTransform,
287                         rText.getSdrTextHorzAdjust(),
288                         rText.getSdrTextVertAdjust(),
289                         rText.isFixedCellHeight(),
290                         rText.isScroll(),
291                         bCellText,
292                         bWordWrap,
293                         bClipOnBounds);
294                 }
295             }
296 
297             OSL_ENSURE(pNew != 0, "createTextPrimitive: no text primitive created (!)");
298 
299             if(rText.isBlink())
300             {
301                 // prepare animation and primitive list
302                 drawinglayer::animation::AnimationEntryList aAnimationList;
303                 rText.getBlinkTextTiming(aAnimationList);
304 
305                 if(0.0 != aAnimationList.getDuration())
306                 {
307                     // create content sequence
308                     const Primitive2DReference xRefA(pNew);
309                     const Primitive2DSequence aContent(&xRefA, 1L);
310 
311                     // create and add animated switch primitive
312                     return Primitive2DReference(new AnimatedBlinkPrimitive2D(aAnimationList, aContent, true));
313                 }
314                 else
315                 {
316                     // add to decomposition
317                     return Primitive2DReference(pNew);
318                 }
319             }
320 
321             if(rText.isScroll())
322             {
323                 // suppress scroll when FontWork
324                 if(rText.getSdrFormTextAttribute().isDefault())
325                 {
326                     // get scroll direction
327                     const SdrTextAniDirection eDirection(rText.getSdrText().GetObject().GetTextAniDirection());
328                     const bool bHorizontal(SDRTEXTANI_LEFT == eDirection || SDRTEXTANI_RIGHT == eDirection);
329 
330                     // decompose to get separated values for the scroll box
331                     basegfx::B2DVector aScale, aTranslate;
332                     double fRotate, fShearX;
333                     aAnchorTransform.decompose(aScale, aTranslate, fRotate, fShearX);
334 
335                     // build transform from scaled only to full AnchorTransform and inverse
336                     const basegfx::B2DHomMatrix aSRT(basegfx::tools::createShearXRotateTranslateB2DHomMatrix(
337                         fShearX, fRotate, aTranslate));
338                     basegfx::B2DHomMatrix aISRT(aSRT);
339                     aISRT.invert();
340 
341                     // bring the primitive back to scaled only and get scaled range, create new clone for this
342                     SdrTextPrimitive2D* pNew2 = pNew->createTransformedClone(aISRT);
343                     OSL_ENSURE(pNew2, "createTextPrimitive: Could not create transformed clone of text primitive (!)");
344                     delete pNew;
345                     pNew = pNew2;
346 
347                     // create neutral geometry::ViewInformation2D for local range and decompose calls. This is okay
348                     // since the decompose is view-independent
349                     const uno::Sequence< beans::PropertyValue > xViewParameters;
350                     geometry::ViewInformation2D aViewInformation2D(xViewParameters);
351 
352                     // get range
353                     const basegfx::B2DRange aScaledRange(pNew->getB2DRange(aViewInformation2D));
354 
355                     // create left outside and right outside transformations. Also take care
356                     // of the clip rectangle
357                     basegfx::B2DHomMatrix aLeft, aRight;
358                     basegfx::B2DPoint aClipTopLeft(0.0, 0.0);
359                     basegfx::B2DPoint aClipBottomRight(aScale.getX(), aScale.getY());
360 
361                     if(bHorizontal)
362                     {
363                         aClipTopLeft.setY(aScaledRange.getMinY());
364                         aClipBottomRight.setY(aScaledRange.getMaxY());
365                         aLeft.translate(-aScaledRange.getMaxX(), 0.0);
366                         aRight.translate(aScale.getX() - aScaledRange.getMinX(), 0.0);
367                     }
368                     else
369                     {
370                         aClipTopLeft.setX(aScaledRange.getMinX());
371                         aClipBottomRight.setX(aScaledRange.getMaxX());
372                         aLeft.translate(0.0, -aScaledRange.getMaxY());
373                         aRight.translate(0.0, aScale.getY() - aScaledRange.getMinY());
374                     }
375 
376                     aLeft *= aSRT;
377                     aRight *= aSRT;
378 
379                     // prepare animation list
380                     drawinglayer::animation::AnimationEntryList aAnimationList;
381 
382                     if(bHorizontal)
383                     {
384                         rText.getScrollTextTiming(aAnimationList, aScale.getX(), aScaledRange.getWidth());
385                     }
386                     else
387                     {
388                         rText.getScrollTextTiming(aAnimationList, aScale.getY(), aScaledRange.getHeight());
389                     }
390 
391                     if(0.0 != aAnimationList.getDuration())
392                     {
393                         // create a new Primitive2DSequence containing the animated text in it's scaled only state.
394                         // use the decomposition to force to simple text primitives, those will no longer
395                         // need the outliner for formatting (alternatively it is also possible to just add
396                         // pNew to aNewPrimitiveSequence)
397                         Primitive2DSequence aAnimSequence(pNew->get2DDecomposition(aViewInformation2D));
398                         delete pNew;
399 
400                         // create a new animatedInterpolatePrimitive and add it
401                         std::vector< basegfx::B2DHomMatrix > aMatrixStack;
402                         aMatrixStack.push_back(aLeft);
403                         aMatrixStack.push_back(aRight);
404                         const Primitive2DReference xRefA(new AnimatedInterpolatePrimitive2D(aMatrixStack, aAnimationList, aAnimSequence, true));
405                         const Primitive2DSequence aContent(&xRefA, 1L);
406 
407                         // scrolling needs an encapsulating clipping primitive
408                         const basegfx::B2DRange aClipRange(aClipTopLeft, aClipBottomRight);
409                         basegfx::B2DPolygon aClipPolygon(basegfx::tools::createPolygonFromRect(aClipRange));
410                         aClipPolygon.transform(aSRT);
411                         return Primitive2DReference(new MaskPrimitive2D(basegfx::B2DPolyPolygon(aClipPolygon), aContent));
412                     }
413                     else
414                     {
415                         // add to decomposition
416                         return Primitive2DReference(pNew);
417                     }
418                 }
419             }
420 
421             if(rText.isInEditMode())
422             {
423                 // #i97628#
424                 // encapsulate with TextHierarchyEditPrimitive2D to allow renderers
425                 // to suppress actively edited content if needed
426                 const Primitive2DReference xRefA(pNew);
427                 const Primitive2DSequence aContent(&xRefA, 1L);
428 
429                 // create and add TextHierarchyEditPrimitive2D primitive
430                 return Primitive2DReference(new TextHierarchyEditPrimitive2D(aContent));
431             }
432             else
433             {
434                 // add to decomposition
435                 return Primitive2DReference(pNew);
436             }
437         }
438 
439         Primitive2DSequence createEmbeddedShadowPrimitive(
440             const Primitive2DSequence& rContent,
441             const attribute::SdrShadowAttribute& rShadow)
442         {
443             if(rContent.hasElements())
444             {
445                 Primitive2DSequence aRetval(2);
446                 basegfx::B2DHomMatrix aShadowOffset;
447 
448                 // prepare shadow offset
449                 aShadowOffset.set(0, 2, rShadow.getOffset().getX());
450                 aShadowOffset.set(1, 2, rShadow.getOffset().getY());
451 
452                 // create shadow primitive and add content
453                 aRetval[0] = Primitive2DReference(
454                     new ShadowPrimitive2D(
455                         aShadowOffset,
456                         rShadow.getColor(),
457                         rContent));
458 
459                 if(0.0 != rShadow.getTransparence())
460                 {
461                     // create SimpleTransparencePrimitive2D
462                     const Primitive2DSequence aTempContent(&aRetval[0], 1);
463 
464                     aRetval[0] = Primitive2DReference(
465                         new UnifiedTransparencePrimitive2D(
466                             aTempContent,
467                             rShadow.getTransparence()));
468                 }
469 
470                 aRetval[1] = Primitive2DReference(new GroupPrimitive2D(rContent));
471                 return aRetval;
472             }
473             else
474             {
475                 return rContent;
476             }
477         }
478     } // end of namespace primitive2d
479 } // end of namespace drawinglayer
480 
481 //////////////////////////////////////////////////////////////////////////////
482 // eof
483