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