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