LibreOffice Module svx (master)  1
sdrdecompositiontools.cxx
Go to the documentation of this file.
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 
35 #include <svx/svdotext.hxx>
47 
48 
49 using namespace com::sun::star;
50 
51 
52 namespace drawinglayer
53 {
54  namespace primitive2d
55  {
56 
57  class TransparencePrimitive2D;
58 
60  const basegfx::B2DPolyPolygon& rPolyPolygon,
61  const attribute::SdrFillAttribute& rFill,
62  const attribute::FillGradientAttribute& rFillGradient)
63  {
64  // when we have no given definition range, use the range of the given geometry
65  // also for definition (simplest case)
66  const basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyPolygon));
67 
69  rPolyPolygon,
70  aRange,
71  rFill,
72  rFillGradient);
73  }
74 
76  const basegfx::B2DPolyPolygon& rPolyPolygon,
77  const basegfx::B2DRange& rDefinitionRange,
78  const attribute::SdrFillAttribute& rFill,
79  const attribute::FillGradientAttribute& rFillGradient)
80  {
82  {
83  return Primitive2DReference();
84  }
85 
86  // prepare fully scaled polygon
87  BasePrimitive2D* pNewFillPrimitive = nullptr;
88 
89  if(!rFill.getGradient().isDefault())
90  {
91  pNewFillPrimitive = new PolyPolygonGradientPrimitive2D(
92  rPolyPolygon,
93  rDefinitionRange,
94  rFill.getGradient());
95  }
96  else if(!rFill.getHatch().isDefault())
97  {
98  pNewFillPrimitive = new PolyPolygonHatchPrimitive2D(
99  rPolyPolygon,
100  rDefinitionRange,
101  rFill.getColor(),
102  rFill.getHatch());
103  }
104  else if(!rFill.getFillGraphic().isDefault())
105  {
106  pNewFillPrimitive = new PolyPolygonGraphicPrimitive2D(
107  rPolyPolygon,
108  rDefinitionRange,
109  rFill.getFillGraphic().createFillGraphicAttribute(rDefinitionRange));
110  }
111  else
112  {
113  pNewFillPrimitive = new PolyPolygonColorPrimitive2D(
114  rPolyPolygon,
115  rFill.getColor());
116  }
117 
118  if(0.0 != rFill.getTransparence())
119  {
120  // create simpleTransparencePrimitive, add created fill primitive
121  const Primitive2DReference xRefA(pNewFillPrimitive);
122  const Primitive2DContainer aContent { xRefA };
124  }
125  else if(!rFillGradient.isDefault())
126  {
127  // create sequence with created fill primitive
128  const Primitive2DReference xRefA(pNewFillPrimitive);
129  const Primitive2DContainer aContent { xRefA };
130 
131  // create FillGradientPrimitive2D for transparence and add to new sequence
132  // fillGradientPrimitive is enough here (compared to PolyPolygonGradientPrimitive2D) since float transparence will be masked anyways
133  const basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyPolygon));
134  const Primitive2DReference xRefB(
136  aRange,
137  rDefinitionRange,
138  rFillGradient));
139  const Primitive2DContainer aAlpha { xRefB };
140 
141  // create TransparencePrimitive2D using alpha and content
142  return Primitive2DReference(new TransparencePrimitive2D(aContent, aAlpha));
143  }
144  else
145  {
146  // add to decomposition
147  return Primitive2DReference(pNewFillPrimitive);
148  }
149  }
150 
152  const basegfx::B2DPolygon& rPolygon,
153  const attribute::SdrLineAttribute& rLine,
155  {
156  // create line and stroke attribute
157  const attribute::LineAttribute aLineAttribute(rLine.getColor(), rLine.getWidth(), rLine.getJoin(), rLine.getCap());
158  const attribute::StrokeAttribute aStrokeAttribute(rLine.getDotDashArray(), rLine.getFullDotDashLen());
159  BasePrimitive2D* pNewLinePrimitive = nullptr;
160 
161  if(!rPolygon.isClosed() && !rStroke.isDefault())
162  {
164  attribute::LineStartEndAttribute aEnd(rStroke.getEndWidth(), rStroke.getEndPolyPolygon(), rStroke.isEndCentered());
165 
166  // create data
167  pNewLinePrimitive = new PolygonStrokeArrowPrimitive2D(rPolygon, aLineAttribute, aStrokeAttribute, aStart, aEnd);
168  }
169  else
170  {
171  // create data
172  pNewLinePrimitive = new PolygonStrokePrimitive2D(rPolygon, aLineAttribute, aStrokeAttribute);
173  }
174 
175  if(0.0 != rLine.getTransparence())
176  {
177  // create simpleTransparencePrimitive, add created fill primitive
178  const Primitive2DReference xRefA(pNewLinePrimitive);
179  const Primitive2DContainer aContent { xRefA };
181  }
182  else
183  {
184  // add to decomposition
185  return Primitive2DReference(pNewLinePrimitive);
186  }
187  }
188 
190  const basegfx::B2DPolyPolygon& rUnitPolyPolygon,
191  const basegfx::B2DHomMatrix& rObjectTransform,
192  const attribute::SdrTextAttribute& rText,
193  const attribute::SdrLineAttribute& rStroke,
194  bool bCellText,
195  bool bWordWrap)
196  {
197  basegfx::B2DHomMatrix aAnchorTransform(rObjectTransform);
198  std::unique_ptr<SdrTextPrimitive2D> pNew;
199 
200  if(rText.isContour())
201  {
202  // contour text
203  if(!rStroke.isDefault() && 0.0 != rStroke.getWidth())
204  {
205  // take line width into account and shrink contour polygon accordingly
206  // decompose to get scale
207  basegfx::B2DVector aScale, aTranslate;
208  double fRotate, fShearX;
209  rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX);
210 
211  // scale outline to object's size to allow growing with value relative to that size
212  // and also to keep aspect ratio
213  basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon);
214  aScaledUnitPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(
215  fabs(aScale.getX()), fabs(aScale.getY())));
216 
217  // grow the polygon. To shrink, use negative value (half width)
218  aScaledUnitPolyPolygon = basegfx::utils::growInNormalDirection(aScaledUnitPolyPolygon, -(rStroke.getWidth() * 0.5));
219 
220  // scale back to unit polygon
221  aScaledUnitPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(
222  0.0 != aScale.getX() ? 1.0 / aScale.getX() : 1.0,
223  0.0 != aScale.getY() ? 1.0 / aScale.getY() : 1.0));
224 
225  // create with unit polygon
226  pNew.reset(new SdrContourTextPrimitive2D(
227  &rText.getSdrText(),
228  rText.getOutlinerParaObject(),
229  aScaledUnitPolyPolygon,
230  rObjectTransform));
231  }
232  else
233  {
234  // create with unit polygon
235  pNew.reset(new SdrContourTextPrimitive2D(
236  &rText.getSdrText(),
237  rText.getOutlinerParaObject(),
238  rUnitPolyPolygon,
239  rObjectTransform));
240  }
241  }
242  else if(!rText.getSdrFormTextAttribute().isDefault())
243  {
244  // text on path, use scaled polygon
245  basegfx::B2DPolyPolygon aScaledPolyPolygon(rUnitPolyPolygon);
246  aScaledPolyPolygon.transform(rObjectTransform);
247  pNew.reset(new SdrPathTextPrimitive2D(
248  &rText.getSdrText(),
249  rText.getOutlinerParaObject(),
250  aScaledPolyPolygon,
251  rText.getSdrFormTextAttribute()));
252  }
253  else
254  {
255  // rObjectTransform is the whole SdrObject transformation from unit rectangle
256  // to its size and position. Decompose to allow working with single values.
257  basegfx::B2DVector aScale, aTranslate;
258  double fRotate, fShearX;
259  rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX);
260 
261  // extract mirroring
262  const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
263  const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
264  aScale = basegfx::absolute(aScale);
265 
266  // Get the real size, since polygon outline and scale
267  // from the object transformation may vary (e.g. ellipse segments)
268  basegfx::B2DHomMatrix aJustScaleTransform;
269  aJustScaleTransform.set(0, 0, aScale.getX());
270  aJustScaleTransform.set(1, 1, aScale.getY());
271  basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon);
272  aScaledUnitPolyPolygon.transform(aJustScaleTransform);
273  const basegfx::B2DRange aSnapRange(basegfx::utils::getRange(aScaledUnitPolyPolygon));
274 
275  // create a range describing the wanted text position and size (aTextAnchorRange). This
276  // means to use the text distance values here
277  const basegfx::B2DPoint aTopLeft(aSnapRange.getMinX() + rText.getTextLeftDistance(), aSnapRange.getMinY() + rText.getTextUpperDistance());
278  const basegfx::B2DPoint aBottomRight(aSnapRange.getMaxX() - rText.getTextRightDistance(), aSnapRange.getMaxY() - rText.getTextLowerDistance());
279  basegfx::B2DRange aTextAnchorRange;
280  aTextAnchorRange.expand(aTopLeft);
281  aTextAnchorRange.expand(aBottomRight);
282 
283  // now create a transformation from this basic range (aTextAnchorRange)
284  // #i121494# if we have no scale use at least 1.0 to have a carrier e.g. for
285  // mirror values, else these will get lost
287  basegfx::fTools::equalZero(aTextAnchorRange.getWidth()) ? 1.0 : aTextAnchorRange.getWidth(),
288  basegfx::fTools::equalZero(aTextAnchorRange.getHeight()) ? 1.0 : aTextAnchorRange.getHeight(),
289  aTextAnchorRange.getMinX(), aTextAnchorRange.getMinY());
290 
291  // apply mirroring
292  aAnchorTransform.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0);
293 
294  // apply object's other transforms
295  aAnchorTransform = basegfx::utils::createShearXRotateTranslateB2DHomMatrix(fShearX, fRotate, aTranslate)
296  * aAnchorTransform;
297 
298  if(rText.isFitToSize())
299  {
300  // stretched text in range
301  pNew.reset(new SdrStretchTextPrimitive2D(
302  &rText.getSdrText(),
303  rText.getOutlinerParaObject(),
304  aAnchorTransform,
305  rText.isFixedCellHeight()));
306  }
307  else if(rText.isAutoFit())
308  {
309  // isotropically scaled text in range
310  pNew.reset(new SdrAutoFitTextPrimitive2D(
311  &rText.getSdrText(),
312  rText.getOutlinerParaObject(),
313  aAnchorTransform,
314  bWordWrap));
315  }
316  else if( rText.isChainable() && !rText.isInEditMode() )
317  {
318  pNew.reset(new SdrChainedTextPrimitive2D(
319  &rText.getSdrText(),
320  rText.getOutlinerParaObject(),
321  aAnchorTransform ));
322  }
323  else // text in range
324  {
325  // build new primitive
326  pNew.reset(new SdrBlockTextPrimitive2D(
327  &rText.getSdrText(),
328  rText.getOutlinerParaObject(),
329  aAnchorTransform,
330  rText.getSdrTextHorzAdjust(),
331  rText.getSdrTextVertAdjust(),
332  rText.isFixedCellHeight(),
333  rText.isScroll(),
334  bCellText,
335  bWordWrap,
336  false/*bClipOnBounds*/));
337  }
338  }
339 
340  OSL_ENSURE(pNew != nullptr, "createTextPrimitive: no text primitive created (!)");
341 
342  if(rText.isBlink())
343  {
344  // prepare animation and primitive list
346  rText.getBlinkTextTiming(aAnimationList);
347 
348  if(0.0 != aAnimationList.getDuration())
349  {
350  // create content sequence
351  const Primitive2DReference xRefA(pNew.release());
352  const Primitive2DContainer aContent { xRefA };
353 
354  // create and add animated switch primitive
355  return Primitive2DReference(new AnimatedBlinkPrimitive2D(aAnimationList, aContent));
356  }
357  else
358  {
359  // add to decomposition
360  return Primitive2DReference(pNew.release());
361  }
362  }
363 
364  if(rText.isScroll())
365  {
366  // suppress scroll when FontWork
367  if(rText.getSdrFormTextAttribute().isDefault())
368  {
369  // get scroll direction
370  const SdrTextAniDirection eDirection(rText.getSdrText().GetObject().GetTextAniDirection());
371  const bool bHorizontal(SdrTextAniDirection::Left == eDirection || SdrTextAniDirection::Right == eDirection);
372 
373  // decompose to get separated values for the scroll box
374  basegfx::B2DVector aScale, aTranslate;
375  double fRotate, fShearX;
376  aAnchorTransform.decompose(aScale, aTranslate, fRotate, fShearX);
377 
378  // build transform from scaled only to full AnchorTransform and inverse
380  fShearX, fRotate, aTranslate));
381  basegfx::B2DHomMatrix aISRT(aSRT);
382  aISRT.invert();
383 
384  // bring the primitive back to scaled only and get scaled range, create new clone for this
385  std::unique_ptr<SdrTextPrimitive2D> pNew2 = pNew->createTransformedClone(aISRT);
386  OSL_ENSURE(pNew2, "createTextPrimitive: Could not create transformed clone of text primitive (!)");
387  pNew = std::move(pNew2);
388 
389  // create neutral geometry::ViewInformation2D for local range and decompose calls. This is okay
390  // since the decompose is view-independent
391  const uno::Sequence< beans::PropertyValue > xViewParameters;
392  geometry::ViewInformation2D aViewInformation2D(xViewParameters);
393 
394  // get range
395  const basegfx::B2DRange aScaledRange(pNew->getB2DRange(aViewInformation2D));
396 
397  // create left outside and right outside transformations. Also take care
398  // of the clip rectangle
399  basegfx::B2DHomMatrix aLeft, aRight;
400  basegfx::B2DPoint aClipTopLeft(0.0, 0.0);
401  basegfx::B2DPoint aClipBottomRight(aScale.getX(), aScale.getY());
402 
403  if(bHorizontal)
404  {
405  aClipTopLeft.setY(aScaledRange.getMinY());
406  aClipBottomRight.setY(aScaledRange.getMaxY());
407  aLeft.translate(-aScaledRange.getMaxX(), 0.0);
408  aRight.translate(aScale.getX() - aScaledRange.getMinX(), 0.0);
409  }
410  else
411  {
412  aClipTopLeft.setX(aScaledRange.getMinX());
413  aClipBottomRight.setX(aScaledRange.getMaxX());
414  aLeft.translate(0.0, -aScaledRange.getMaxY());
415  aRight.translate(0.0, aScale.getY() - aScaledRange.getMinY());
416  }
417 
418  aLeft *= aSRT;
419  aRight *= aSRT;
420 
421  // prepare animation list
423 
424  if(bHorizontal)
425  {
426  rText.getScrollTextTiming(aAnimationList, aScale.getX(), aScaledRange.getWidth());
427  }
428  else
429  {
430  rText.getScrollTextTiming(aAnimationList, aScale.getY(), aScaledRange.getHeight());
431  }
432 
433  if(0.0 != aAnimationList.getDuration())
434  {
435  // create a new Primitive2DContainer containing the animated text in its scaled only state.
436  // use the decomposition to force to simple text primitives, those will no longer
437  // need the outliner for formatting (alternatively it is also possible to just add
438  // pNew to aNewPrimitiveSequence)
439  Primitive2DContainer aAnimSequence;
440  pNew->get2DDecomposition(aAnimSequence, aViewInformation2D);
441  pNew.reset();
442 
443  // create a new animatedInterpolatePrimitive and add it
444  std::vector< basegfx::B2DHomMatrix > aMatrixStack;
445  aMatrixStack.push_back(aLeft);
446  aMatrixStack.push_back(aRight);
447  const Primitive2DReference xRefA(new AnimatedInterpolatePrimitive2D(aMatrixStack, aAnimationList, aAnimSequence));
448  const Primitive2DContainer aContent { xRefA };
449 
450  // scrolling needs an encapsulating clipping primitive
451  const basegfx::B2DRange aClipRange(aClipTopLeft, aClipBottomRight);
453  aClipPolygon.transform(aSRT);
454  return Primitive2DReference(new MaskPrimitive2D(basegfx::B2DPolyPolygon(aClipPolygon), aContent));
455  }
456  else
457  {
458  // add to decomposition
459  return Primitive2DReference(pNew.release());
460  }
461  }
462  }
463 
464  if(rText.isInEditMode())
465  {
466  // #i97628#
467  // encapsulate with TextHierarchyEditPrimitive2D to allow renderers
468  // to suppress actively edited content if needed
469  const Primitive2DReference xRefA(pNew.release());
470  const Primitive2DContainer aContent { xRefA };
471 
472  // create and add TextHierarchyEditPrimitive2D primitive
474  }
475  else
476  {
477  // add to decomposition
478  return Primitive2DReference(pNew.release());
479  }
480  }
481 
483  const Primitive2DContainer& rContent,
484  const attribute::SdrShadowAttribute& rShadow)
485  {
486  if(!rContent.empty())
487  {
488  Primitive2DContainer aRetval(2);
489  basegfx::B2DHomMatrix aShadowOffset;
490 
491  // prepare shadow offset
492  aShadowOffset.set(0, 2, rShadow.getOffset().getX());
493  aShadowOffset.set(1, 2, rShadow.getOffset().getY());
494 
495  // create shadow primitive and add content
496  aRetval[0] = Primitive2DReference(
497  new ShadowPrimitive2D(
498  aShadowOffset,
499  rShadow.getColor(),
500  rContent));
501 
502  if(0.0 != rShadow.getTransparence())
503  {
504  // create SimpleTransparencePrimitive2D
505  const Primitive2DContainer aTempContent { aRetval[0] };
506 
507  aRetval[0] = Primitive2DReference(
509  aTempContent,
510  rShadow.getTransparence()));
511  }
512 
513  aRetval[1] = Primitive2DReference(new GroupPrimitive2D(rContent));
514  return aRetval;
515  }
516  else
517  {
518  return rContent;
519  }
520  }
521  } // end of namespace primitive2d
522 } // end of namespace drawinglayer
523 
524 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
static bool moreOrEqual(const double &rfValA, const double &rfValB)
B2DPolygon growInNormalDirection(const B2DPolygon &rCandidate, double fValue)
const FillGradientAttribute & getGradient() const
void setX(double fX)
void expand(const B2DTuple &rTuple)
void set(sal_uInt16 nRow, sal_uInt16 nColumn, double fValue)
css::drawing::LineCap getCap() const
B2DTuple absolute(const B2DTuple &rTup)
const basegfx::BColor & getColor() const
double getX() const
double getY() const
const basegfx::BColor & getColor() const
double getMaxX() const
const basegfx::B2DPolyPolygon & getStartPolyPolygon() const
Primitive2DReference createPolygonLinePrimitive(const basegfx::B2DPolygon &rPolygon, const attribute::SdrLineAttribute &rLine, const attribute::SdrLineStartEndAttribute &rStroke)
B2DHomMatrix createScaleB2DHomMatrix(double fScaleX, double fScaleY)
basegfx::B2DLineJoin getJoin() const
B2DHomMatrix createScaleTranslateB2DHomMatrix(double fScaleX, double fScaleY, double fTranslateX, double fTranslateY)
const FillHatchAttribute & getHatch() const
SdrTextHorzAdjust getSdrTextHorzAdjust() const
double getMaxY() const
bool isClosed() const
const OutlinerParaObject & getOutlinerParaObject() const
static bool less(const double &rfValA, const double &rfValB)
FillGraphicAttribute createFillGraphicAttribute(const basegfx::B2DRange &rRange) const
const basegfx::B2DPolyPolygon & getEndPolyPolygon() const
void getScrollTextTiming(drawinglayer::animation::AnimationEntryList &rAnimList, double fFrameLength, double fTextLength) const
virtual double getDuration() const override
bool decompose(B2DTuple &rScale, B2DTuple &rTranslate, double &rRotate, double &rShearX) const
SdrTextObj & GetObject() const
Definition: svdtext.hxx:64
B2DPolygon createPolygonFromRect(const B2DRectangle &rRect, double fRadiusX, double fRadiusY)
const basegfx::B2DVector & getOffset() const
Primitive2DReference createTextPrimitive(const basegfx::B2DPolyPolygon &rUnitPolyPolygon, const basegfx::B2DHomMatrix &rObjectTransform, const attribute::SdrTextAttribute &rText, const attribute::SdrLineAttribute &rStroke, bool bCellText, bool bWordWrap)
const ::std::vector< double > & getDotDashArray() const
static bool equalZero(const double &rfVal)
const basegfx::BColor & getColor() const
void scale(double fX, double fY)
void transform(const basegfx::B2DHomMatrix &rMatrix)
B2DRange getRange(const B2DPolygon &rCandidate)
void transform(const basegfx::B2DHomMatrix &rMatrix)
double getMinY() const
void setY(double fY)
void getBlinkTextTiming(drawinglayer::animation::AnimationEntryList &rAnimList) const
Primitive2DReference createPolyPolygonFillPrimitive(const basegfx::B2DPolyPolygon &rPolyPolygon, const basegfx::B2DRange &rDefinitionRange, const attribute::SdrFillAttribute &rFill, const attribute::FillGradientAttribute &rFillGradient)
css::uno::Reference< css::graphic::XPrimitive2D > Primitive2DReference
SdrTextAniDirection GetTextAniDirection() const
Definition: svdotext.cxx:1775
SdrTextAniDirection
Definition: sdtaditm.hxx:29
Primitive2DContainer createEmbeddedShadowPrimitive(const Primitive2DContainer &rContent, const attribute::SdrShadowAttribute &rShadow)
const SdrFormTextAttribute & getSdrFormTextAttribute() const
double getMinX() const
const SdrFillGraphicAttribute & getFillGraphic() const
SdrTextVertAdjust getSdrTextVertAdjust() const
B2DHomMatrix createShearXRotateTranslateB2DHomMatrix(double fShearX, double fRadiant, double fTranslateX, double fTranslateY)