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