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