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 & rUnitPolyPolygon,const basegfx::B2DHomMatrix & rObjectTransform,const attribute::SdrFillAttribute & rFill,const attribute::FillGradientAttribute & rFillGradient)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 
createPolygonLinePrimitive(const basegfx::B2DPolygon & rUnitPolygon,const basegfx::B2DHomMatrix & rObjectTransform,const attribute::SdrLineAttribute & rLine,const attribute::SdrLineStartEndAttribute & rStroke)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 
createTextPrimitive(const basegfx::B2DPolyPolygon & rUnitPolyPolygon,const basegfx::B2DHomMatrix & rObjectTransform,const attribute::SdrTextAttribute & rText,const attribute::SdrLineAttribute & rStroke,bool bCellText,bool bWordWrap,bool bClipOnBounds)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                 // #121494# if we have no scale use at least 1.0 to have a carrier e.g. for
261                 // mirror values, else these will get lost
262                 aAnchorTransform = basegfx::tools::createScaleTranslateB2DHomMatrix(
263                     basegfx::fTools::equalZero(aTextAnchorRange.getWidth()) ? 1.0 : aTextAnchorRange.getWidth(),
264                     basegfx::fTools::equalZero(aTextAnchorRange.getHeight()) ? 1.0 : aTextAnchorRange.getHeight(),
265                     aTextAnchorRange.getMinX(), aTextAnchorRange.getMinY());
266 
267 				// apply mirroring
268 				aAnchorTransform.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0);
269 
270 				// apply object's other transforms
271 				aAnchorTransform = basegfx::tools::createShearXRotateTranslateB2DHomMatrix(fShearX, fRotate, aTranslate)
272 					* aAnchorTransform;
273 
274 				if(rText.isFitToSize())
275 				{
276 					// streched text in range
277 					pNew = new SdrStretchTextPrimitive2D(
278                         &rText.getSdrText(),
279                         rText.getOutlinerParaObject(),
280                         aAnchorTransform,
281                         rText.isFixedCellHeight());
282 				}
283 				else // text in range
284 				{
285 					// build new primitive
286 					pNew = new SdrBlockTextPrimitive2D(
287                         &rText.getSdrText(),
288                         rText.getOutlinerParaObject(),
289                         aAnchorTransform,
290                         rText.getSdrTextHorzAdjust(),
291                         rText.getSdrTextVertAdjust(),
292                         rText.isFixedCellHeight(),
293                         rText.isScroll(),
294                         bCellText,
295                         bWordWrap,
296 						bClipOnBounds);
297 				}
298 			}
299 
300 			OSL_ENSURE(pNew != 0, "createTextPrimitive: no text primitive created (!)");
301 
302 			if(rText.isBlink())
303 			{
304 				// prepare animation and primitive list
305 				drawinglayer::animation::AnimationEntryList aAnimationList;
306 				rText.getBlinkTextTiming(aAnimationList);
307 
308 				if(0.0 != aAnimationList.getDuration())
309 				{
310 					// create content sequence
311 					const Primitive2DReference xRefA(pNew);
312 					const Primitive2DSequence aContent(&xRefA, 1L);
313 
314 					// create and add animated switch primitive
315 					return Primitive2DReference(new AnimatedBlinkPrimitive2D(aAnimationList, aContent, true));
316 				}
317 				else
318 				{
319 					// add to decomposition
320 					return Primitive2DReference(pNew);
321 				}
322 			}
323 
324             if(rText.isScroll())
325 			{
326                 // suppress scroll when FontWork
327 			    if(rText.getSdrFormTextAttribute().isDefault())
328 			    {
329 				    // get scroll direction
330 				    const SdrTextAniDirection eDirection(rText.getSdrText().GetObject().GetTextAniDirection());
331 				    const bool bHorizontal(SDRTEXTANI_LEFT == eDirection || SDRTEXTANI_RIGHT == eDirection);
332 
333 				    // decompose to get separated values for the scroll box
334 				    basegfx::B2DVector aScale, aTranslate;
335 				    double fRotate, fShearX;
336 				    aAnchorTransform.decompose(aScale, aTranslate, fRotate, fShearX);
337 
338 				    // build transform from scaled only to full AnchorTransform and inverse
339 					const basegfx::B2DHomMatrix aSRT(basegfx::tools::createShearXRotateTranslateB2DHomMatrix(
340 						fShearX, fRotate, aTranslate));
341 				    basegfx::B2DHomMatrix aISRT(aSRT);
342 				    aISRT.invert();
343 
344 				    // bring the primitive back to scaled only and get scaled range, create new clone for this
345 				    SdrTextPrimitive2D* pNew2 = pNew->createTransformedClone(aISRT);
346 				    OSL_ENSURE(pNew2, "createTextPrimitive: Could not create transformed clone of text primitive (!)");
347 				    delete pNew;
348 				    pNew = pNew2;
349 
350 				    // create neutral geometry::ViewInformation2D for local range and decompose calls. This is okay
351 				    // since the decompose is view-independent
352 				    const uno::Sequence< beans::PropertyValue > xViewParameters;
353 				    geometry::ViewInformation2D aViewInformation2D(xViewParameters);
354 
355 				    // get range
356 				    const basegfx::B2DRange aScaledRange(pNew->getB2DRange(aViewInformation2D));
357 
358 				    // create left outside and right outside transformations. Also take care
359 				    // of the clip rectangle
360 				    basegfx::B2DHomMatrix aLeft, aRight;
361 				    basegfx::B2DPoint aClipTopLeft(0.0, 0.0);
362 				    basegfx::B2DPoint aClipBottomRight(aScale.getX(), aScale.getY());
363 
364 				    if(bHorizontal)
365 				    {
366 					    aClipTopLeft.setY(aScaledRange.getMinY());
367 					    aClipBottomRight.setY(aScaledRange.getMaxY());
368 					    aLeft.translate(-aScaledRange.getMaxX(), 0.0);
369 					    aRight.translate(aScale.getX() - aScaledRange.getMinX(), 0.0);
370 				    }
371 				    else
372 				    {
373 					    aClipTopLeft.setX(aScaledRange.getMinX());
374 					    aClipBottomRight.setX(aScaledRange.getMaxX());
375 					    aLeft.translate(0.0, -aScaledRange.getMaxY());
376 					    aRight.translate(0.0, aScale.getY() - aScaledRange.getMinY());
377 				    }
378 
379 				    aLeft *= aSRT;
380 				    aRight *= aSRT;
381 
382 				    // prepare animation list
383 				    drawinglayer::animation::AnimationEntryList aAnimationList;
384 
385 				    if(bHorizontal)
386 				    {
387 					    rText.getScrollTextTiming(aAnimationList, aScale.getX(), aScaledRange.getWidth());
388 				    }
389 				    else
390 				    {
391 					    rText.getScrollTextTiming(aAnimationList, aScale.getY(), aScaledRange.getHeight());
392 				    }
393 
394 				    if(0.0 != aAnimationList.getDuration())
395 				    {
396 					    // create a new Primitive2DSequence containing the animated text in it's scaled only state.
397 					    // use the decomposition to force to simple text primitives, those will no longer
398 					    // need the outliner for formatting (alternatively it is also possible to just add
399 					    // pNew to aNewPrimitiveSequence)
400 					    Primitive2DSequence aAnimSequence(pNew->get2DDecomposition(aViewInformation2D));
401 					    delete pNew;
402 
403 					    // create a new animatedInterpolatePrimitive and add it
404 					    std::vector< basegfx::B2DHomMatrix > aMatrixStack;
405 					    aMatrixStack.push_back(aLeft);
406 					    aMatrixStack.push_back(aRight);
407 					    const Primitive2DReference xRefA(new AnimatedInterpolatePrimitive2D(aMatrixStack, aAnimationList, aAnimSequence, true));
408 					    const Primitive2DSequence aContent(&xRefA, 1L);
409 
410 					    // scrolling needs an encapsulating clipping primitive
411 					    const basegfx::B2DRange aClipRange(aClipTopLeft, aClipBottomRight);
412 					    basegfx::B2DPolygon aClipPolygon(basegfx::tools::createPolygonFromRect(aClipRange));
413 					    aClipPolygon.transform(aSRT);
414 					    return Primitive2DReference(new MaskPrimitive2D(basegfx::B2DPolyPolygon(aClipPolygon), aContent));
415 				    }
416 				    else
417 				    {
418 					    // add to decomposition
419 					    return Primitive2DReference(pNew);
420 				    }
421                 }
422 			}
423 
424             if(rText.isInEditMode())
425             {
426                 // #i97628#
427                 // encapsulate with TextHierarchyEditPrimitive2D to allow renderers
428                 // to suppress actively edited content if needed
429 			    const Primitive2DReference xRefA(pNew);
430 			    const Primitive2DSequence aContent(&xRefA, 1L);
431 
432 			    // create and add TextHierarchyEditPrimitive2D primitive
433 			    return Primitive2DReference(new TextHierarchyEditPrimitive2D(aContent));
434             }
435             else
436             {
437 				// add to decomposition
438     			return Primitive2DReference(pNew);
439             }
440 		}
441 
createEmbeddedShadowPrimitive(const Primitive2DSequence & rContent,const attribute::SdrShadowAttribute & rShadow)442         Primitive2DSequence createEmbeddedShadowPrimitive(
443             const Primitive2DSequence& rContent,
444             const attribute::SdrShadowAttribute& rShadow)
445         {
446 			if(rContent.hasElements())
447 			{
448 				Primitive2DSequence aRetval(2);
449 		        basegfx::B2DHomMatrix aShadowOffset;
450 
451 		        // prepare shadow offset
452                 aShadowOffset.set(0, 2, rShadow.getOffset().getX());
453 		        aShadowOffset.set(1, 2, rShadow.getOffset().getY());
454 
455 		        // create shadow primitive and add content
456 		        aRetval[0] = Primitive2DReference(
457                     new ShadowPrimitive2D(
458                         aShadowOffset,
459                         rShadow.getColor(),
460                         rContent));
461 
462 		        if(0.0 != rShadow.getTransparence())
463 		        {
464 			        // create SimpleTransparencePrimitive2D
465 			        const Primitive2DSequence aTempContent(&aRetval[0], 1);
466 
467                     aRetval[0] = Primitive2DReference(
468                         new UnifiedTransparencePrimitive2D(
469                             aTempContent,
470                             rShadow.getTransparence()));
471 		        }
472 
473                 aRetval[1] = Primitive2DReference(new GroupPrimitive2D(rContent));
474                 return aRetval;
475 			}
476             else
477             {
478                 return rContent;
479             }
480         }
481 	} // end of namespace primitive2d
482 } // end of namespace drawinglayer
483 
484 //////////////////////////////////////////////////////////////////////////////
485 // eof
486