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