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