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