cid#1640468 Dereference after null check
[LibreOffice.git] / svx / source / sdr / primitive2d / sdrdecompositiontools.cxx
blob617dcba30631c9f0b608fba14edf9aa10c511475
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sdr/primitive2d/sdrdecompositiontools.hxx>
21 #include <sdr/primitive2d/sdrcellprimitive.hxx>
22 #include <drawinglayer/primitive2d/baseprimitive2d.hxx>
23 #include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx>
24 #include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx>
25 #include <drawinglayer/primitive2d/PolyPolygonHatchPrimitive2D.hxx>
26 #include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
27 #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
28 #include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
29 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
30 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
31 #include <drawinglayer/primitive2d/PolyPolygonRGBAPrimitive2D.hxx>
32 #include <drawinglayer/primitive2d/PolyPolygonAlphaGradientPrimitive2D.hxx>
33 #include <basegfx/polygon/b2dpolypolygontools.hxx>
34 #include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
35 #include <drawinglayer/attribute/strokeattribute.hxx>
36 #include <drawinglayer/attribute/linestartendattribute.hxx>
37 #include <drawinglayer/attribute/sdrfillgraphicattribute.hxx>
38 #include <basegfx/matrix/b2dhommatrix.hxx>
39 #include <drawinglayer/primitive2d/shadowprimitive2d.hxx>
40 #include <sdr/attribute/sdrtextattribute.hxx>
41 #include <drawinglayer/primitive2d/glowprimitive2d.hxx>
42 #include <sdr/primitive2d/sdrtextprimitive2d.hxx>
43 #include <svx/svdotext.hxx>
44 #include <basegfx/polygon/b2dpolygontools.hxx>
45 #include <drawinglayer/primitive2d/animatedprimitive2d.hxx>
46 #include <drawinglayer/animation/animationtiming.hxx>
47 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
48 #include <drawinglayer/geometry/viewinformation2d.hxx>
49 #include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
50 #include <drawinglayer/attribute/sdrfillattribute.hxx>
51 #include <drawinglayer/attribute/sdrlineattribute.hxx>
52 #include <drawinglayer/attribute/sdrlinestartendattribute.hxx>
53 #include <drawinglayer/attribute/sdrshadowattribute.hxx>
54 #include <drawinglayer/attribute/sdrglowattribute.hxx>
55 #include <drawinglayer/attribute/sdrglowtextattribute.hxx>
56 #include <docmodel/theme/FormatScheme.hxx>
57 #include <osl/diagnose.h>
59 // for SlideBackgroundFillPrimitive2D
60 #include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
61 #include <svx/unoapi.hxx>
62 #include <svx/svdpage.hxx>
63 #include <sdr/primitive2d/sdrattributecreator.hxx>
64 #include <sdr/contact/viewcontactofmasterpagedescriptor.hxx>
66 using namespace com::sun::star;
69 namespace drawinglayer::primitive2d
71 namespace
73 /// @returns the offset to apply/unapply to scale according to correct origin for a given alignment.
74 basegfx::B2DTuple getShadowScaleOriginOffset(const basegfx::B2DTuple& aScale,
75 model::RectangleAlignment eAlignment)
77 switch (eAlignment)
79 case model::RectangleAlignment::TopLeft:
80 return { 0, 0 };
81 case model::RectangleAlignment::Top:
82 return { aScale.getX() / 2, 0 };
83 case model::RectangleAlignment::TopRight:
84 return { aScale.getX(), 0 };
85 case model::RectangleAlignment::Left:
86 return { 0, aScale.getY() / 2 };
87 case model::RectangleAlignment::Center:
88 return { aScale.getX() / 2, aScale.getY() / 2 };
89 case model::RectangleAlignment::Right:
90 return { aScale.getX(), aScale.getY() / 2 };
91 case model::RectangleAlignment::BottomLeft:
92 return { 0, aScale.getY() };
93 case model::RectangleAlignment::Bottom:
94 return { aScale.getX() / 2, aScale.getY() };
95 case model::RectangleAlignment::BottomRight:
96 return { aScale.getX(), aScale.getY() };
97 default:
98 return { 0, 0 };
102 // See also: SdrTextObj::AdjustRectToTextDistance
103 basegfx::B2DRange getTextAnchorRange(const attribute::SdrTextAttribute& rText,
104 const basegfx::B2DRange& rSnapRange)
106 // Take vertical text orientation into account when deciding
107 // which dimension is its width, and which is its height
108 const OutlinerParaObject& rOutlinerParaObj = rText.getOutlinerParaObject();
109 const bool bVerticalWriting(rOutlinerParaObj.IsEffectivelyVertical());
110 const double fWidthForText = bVerticalWriting ? rSnapRange.getHeight() : rSnapRange.getWidth();
111 // create a range describing the wanted text position and size (aTextAnchorRange). This
112 // means to use the text distance values here
113 // If the margin is larger than the entire width of the text area, then limit the
114 // margin.
115 const double fTextLeftDistance
116 = std::min(static_cast<double>(rText.getTextLeftDistance()), fWidthForText);
117 const double nTextRightDistance
118 = std::min(static_cast<double>(rText.getTextRightDistance()), fWidthForText);
119 double fDistanceForTextL, fDistanceForTextT, fDistanceForTextR, fDistanceForTextB;
120 if (!bVerticalWriting)
122 fDistanceForTextL = fTextLeftDistance;
123 fDistanceForTextT = rText.getTextUpperDistance();
124 fDistanceForTextR = nTextRightDistance;
125 fDistanceForTextB = rText.getTextLowerDistance();
127 else if (rOutlinerParaObj.IsTopToBottom())
129 fDistanceForTextL = rText.getTextLowerDistance();
130 fDistanceForTextT = fTextLeftDistance;
131 fDistanceForTextR = rText.getTextUpperDistance();
132 fDistanceForTextB = nTextRightDistance;
134 else
136 fDistanceForTextL = rText.getTextUpperDistance();
137 fDistanceForTextT = nTextRightDistance;
138 fDistanceForTextR = rText.getTextLowerDistance();
139 fDistanceForTextB = fTextLeftDistance;
141 const basegfx::B2DPoint aTopLeft(rSnapRange.getMinX() + fDistanceForTextL,
142 rSnapRange.getMinY() + fDistanceForTextT);
143 const basegfx::B2DPoint aBottomRight(rSnapRange.getMaxX() - fDistanceForTextR,
144 rSnapRange.getMaxY() - fDistanceForTextB);
145 basegfx::B2DRange aAnchorRange;
146 aAnchorRange.expand(aTopLeft);
147 aAnchorRange.expand(aBottomRight);
149 // If the shape has no width, then don't attempt to break the text into multiple
150 // lines, not a single character would satisfy a zero width requirement.
151 // SdrTextObj::impDecomposeBlockTextPrimitive() uses the same constant to
152 // effectively set no limits.
153 if (!bVerticalWriting && aAnchorRange.getWidth() == 0)
155 aAnchorRange.expand(basegfx::B2DPoint(aTopLeft.getX() - 1000000, aTopLeft.getY()));
156 aAnchorRange.expand(basegfx::B2DPoint(aBottomRight.getX() + 1000000, aBottomRight.getY()));
158 else if (bVerticalWriting && aAnchorRange.getHeight() == 0)
160 aAnchorRange.expand(basegfx::B2DPoint(aTopLeft.getX(), aTopLeft.getY() - 1000000));
161 aAnchorRange.expand(basegfx::B2DPoint(aBottomRight.getX(), aBottomRight.getY() + 1000000));
163 return aAnchorRange;
166 drawinglayer::attribute::SdrFillAttribute getMasterPageFillAttribute(
167 const geometry::ViewInformation2D& rViewInformation,
168 basegfx::B2DVector& rPageSize)
170 drawinglayer::attribute::SdrFillAttribute aRetval;
172 // get SdrPage
173 const SdrPage* pVisualizedPage(GetSdrPageFromXDrawPage(rViewInformation.getVisualizedPage()));
175 if(nullptr != pVisualizedPage)
177 // copy needed values for further processing
178 rPageSize.setX(pVisualizedPage->GetWidth());
179 rPageSize.setY(pVisualizedPage->GetHeight());
181 if(pVisualizedPage->IsMasterPage())
183 // the page is a MasterPage, so we are in MasterPage view
184 // still need #i110846#, see ViewContactOfMasterPage
185 if(pVisualizedPage->getSdrPageProperties().GetStyleSheet())
187 // create page fill attributes with correct properties
188 aRetval = drawinglayer::primitive2d::createNewSdrFillAttribute(
189 pVisualizedPage->getSdrPageProperties().GetItemSet());
192 else
194 // the page is *no* MasterPage, we are in normal Page view, get the MasterPage
195 if(pVisualizedPage->TRG_HasMasterPage())
197 sdr::contact::ViewContact& rVC(pVisualizedPage->TRG_GetMasterPageDescriptorViewContact());
198 sdr::contact::ViewContactOfMasterPageDescriptor* pVCOMPD(
199 dynamic_cast<sdr::contact::ViewContactOfMasterPageDescriptor*>(&rVC));
201 if(nullptr != pVCOMPD)
203 // in this case the still needed #i110846# is part of
204 // getCorrectSdrPageProperties, that's the main reason to re-use
205 // that call/functionality here
206 const SdrPageProperties* pCorrectProperties(
207 pVCOMPD->GetMasterPageDescriptor().getCorrectSdrPageProperties());
209 if(pCorrectProperties)
211 // create page fill attributes when correct properties were identified
212 aRetval = drawinglayer::primitive2d::createNewSdrFillAttribute(
213 pCorrectProperties->GetItemSet());
220 return aRetval;
223 // provide a Primitive2D for the SlideBackgroundFill-mode. It is capable
224 // of expressing that state of fill and it's decomposition implements all
225 // needed preparation of the geometry in an isolated and controllable
226 // space and way.
227 // It is currently simple buffered (due to being derived from
228 // BufferedDecompositionPrimitive2D) and detects if FillStyle changes
229 class SlideBackgroundFillPrimitive2D final : public BufferedDecompositionPrimitive2D
231 private:
232 /// the basegfx::B2DPolyPolygon geometry
233 basegfx::B2DPolyPolygon maPolyPolygon;
235 /// the last SdrFillAttribute the geometry was created for
236 drawinglayer::attribute::SdrFillAttribute maLastFill;
238 protected:
239 // create decomposition data
240 virtual Primitive2DReference create2DDecomposition(
241 const geometry::ViewInformation2D& rViewInformation) const override;
243 public:
244 /// constructor
245 SlideBackgroundFillPrimitive2D(
246 const basegfx::B2DPolyPolygon& rPolyPolygon);
248 /// check existing decomposition data, call parent
249 virtual void get2DDecomposition(
250 Primitive2DDecompositionVisitor& rVisitor,
251 const geometry::ViewInformation2D& rViewInformation) const override;
253 /// data read access
254 const basegfx::B2DPolyPolygon& getB2DPolyPolygon() const { return maPolyPolygon; }
256 /// compare operator
257 virtual bool operator==(const BasePrimitive2D& rPrimitive) const override;
259 /// get range
260 virtual basegfx::B2DRange getB2DRange(const geometry::ViewInformation2D& rViewInformation) const override;
262 /// provide unique ID
263 virtual sal_uInt32 getPrimitive2DID() const override;
266 SlideBackgroundFillPrimitive2D::SlideBackgroundFillPrimitive2D(
267 const basegfx::B2DPolyPolygon& rPolyPolygon)
268 : BufferedDecompositionPrimitive2D()
269 , maPolyPolygon(rPolyPolygon)
270 , maLastFill()
274 Primitive2DReference SlideBackgroundFillPrimitive2D::create2DDecomposition(
275 const geometry::ViewInformation2D& rViewInformation) const
277 basegfx::B2DVector aPageSize;
279 // get fill from target Page, this will check for all needed things
280 // like MasterPage/relationships, etc. (see getMasterPageFillAttribute impl above)
281 drawinglayer::attribute::SdrFillAttribute aFill(
282 getMasterPageFillAttribute(rViewInformation, aPageSize));
284 // if fill is on default (empty), nothing will be shown, we are done
285 if(aFill.isDefault())
286 return nullptr;
288 // Get PolygonRange of own local geometry
289 const basegfx::B2DRange aPolygonRange(getB2DPolyPolygon().getB2DRange());
291 // if local geometry is empty, nothing will be shown, we are done
292 if(aPolygonRange.isEmpty())
293 return nullptr;
295 // Get PageRange
296 const basegfx::B2DRange aPageRange(0.0, 0.0, aPageSize.getX(), aPageSize.getY());
298 // if local geometry does not overlap with PageRange, nothing will be shown, we are done
299 if(!aPageRange.overlaps(aPolygonRange))
300 return nullptr;
302 // create FillPrimitive2D with the geometry (the PolyPolygon) and
303 // the page's definitonRange to:
304 // - on one hand limit to geometry
305 // - on the other hand allow continuation of fill outside of
306 // MasterPage's range
307 const attribute::FillGradientAttribute aEmptyFillTransparenceGradient;
308 const Primitive2DReference aCreatedFill(
309 createPolyPolygonFillPrimitive(
310 getB2DPolyPolygon(), // geometry
311 aPageRange, // definition range
312 aFill,
313 aEmptyFillTransparenceGradient));
315 return aCreatedFill;
318 void SlideBackgroundFillPrimitive2D::get2DDecomposition(
319 Primitive2DDecompositionVisitor& rVisitor,
320 const geometry::ViewInformation2D& rViewInformation) const
322 basegfx::B2DVector aPageSize;
323 drawinglayer::attribute::SdrFillAttribute aFill;
325 if(getBuffered2DDecomposition())
327 aFill = getMasterPageFillAttribute(rViewInformation, aPageSize);
329 if(!(aFill == maLastFill))
331 // conditions of last local decomposition have changed, delete
332 const_cast< SlideBackgroundFillPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr);
336 if(!getBuffered2DDecomposition())
338 // remember last Fill
339 const_cast< SlideBackgroundFillPrimitive2D* >(this)->maLastFill = std::move(aFill);
342 // use parent implementation
343 BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
346 bool SlideBackgroundFillPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
348 if (BufferedDecompositionPrimitive2D::operator==(rPrimitive))
350 const SlideBackgroundFillPrimitive2D& rCompare
351 = static_cast<const SlideBackgroundFillPrimitive2D&>(rPrimitive);
353 return getB2DPolyPolygon() == rCompare.getB2DPolyPolygon();
356 return false;
359 basegfx::B2DRange SlideBackgroundFillPrimitive2D::getB2DRange(
360 const geometry::ViewInformation2D& /*rViewInformation*/) const
362 // return range
363 return basegfx::utils::getRange(getB2DPolyPolygon());
366 // provide unique ID
367 sal_uInt32 SlideBackgroundFillPrimitive2D::getPrimitive2DID() const
369 return PRIMITIVE2D_ID_SLIDEBACKGROUNDFILLPRIMITIVE2D;
372 }; // end of anonymous namespace
374 Primitive2DReference createPolyPolygonFillPrimitive(
375 const basegfx::B2DPolyPolygon& rPolyPolygon,
376 const attribute::SdrFillAttribute& rFill,
377 const attribute::FillGradientAttribute& rAlphaGradient)
379 // when we have no given definition range, use the range of the given geometry
380 // also for definition (simplest case)
381 const basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyPolygon));
383 return createPolyPolygonFillPrimitive(
384 rPolyPolygon,
385 aRange,
386 rFill,
387 rAlphaGradient);
390 Primitive2DReference createPolyPolygonFillPrimitive(
391 const basegfx::B2DPolyPolygon& rPolyPolygon,
392 const basegfx::B2DRange& rDefinitionRange,
393 const attribute::SdrFillAttribute& rFill,
394 const attribute::FillGradientAttribute& rAlphaGradient)
396 if(basegfx::fTools::moreOrEqual(rFill.getTransparence(), 1.0))
398 return Primitive2DReference();
401 // prepare access to FillGradientAttribute
402 const attribute::FillGradientAttribute& rFillGradient(rFill.getGradient());
404 // prepare fully scaled polygon
405 rtl::Reference<BasePrimitive2D> pNewFillPrimitive;
407 if(!rFillGradient.isDefault())
409 const bool bHasTransparency(!basegfx::fTools::equalZero(rFill.getTransparence()));
410 // note: need to use !bHasTransparency to do the same as below
411 // where embedding to transparency is done. There, simple transparency
412 // gets priority over gradient transparency (and none). Thus here only one
413 // option is used. Note that the implementation of FillGradientPrimitive2D
414 // and PolyPolygonGradientPrimitive2D do support both alphas being used
415 const bool bHasCompatibleAlphaGradient(!bHasTransparency
416 && !rAlphaGradient.isDefault()
417 && rFillGradient.sameDefinitionThanAlpha(rAlphaGradient));
419 if(bHasTransparency || bHasCompatibleAlphaGradient)
421 // SDPR: check early if we have a gradient and an alpha
422 // gradient that 'fits' in its geometric definition
423 // so that it can be rendered as RGBA directly. If yes,
424 // create it and return early
425 return new PolyPolygonGradientPrimitive2D(
426 rPolyPolygon,
427 rDefinitionRange,
428 rFillGradient,
429 bHasCompatibleAlphaGradient ? &rAlphaGradient : nullptr,
430 bHasTransparency ? rFill.getTransparence() : 0.0);
433 pNewFillPrimitive = new PolyPolygonGradientPrimitive2D(
434 rPolyPolygon,
435 rDefinitionRange,
436 rFillGradient);
438 else if(!rFill.getHatch().isDefault())
440 pNewFillPrimitive = new PolyPolygonHatchPrimitive2D(
441 rPolyPolygon,
442 rDefinitionRange,
443 rFill.getColor(),
444 rFill.getHatch());
446 else if(!rFill.getFillGraphic().isDefault())
448 // SDPR: check early if we have alpha and add directly
449 if(0.0 != rFill.getTransparence())
451 return new PolyPolygonGraphicPrimitive2D(
452 rPolyPolygon,
453 rDefinitionRange,
454 rFill.getFillGraphic().createFillGraphicAttribute(rDefinitionRange),
455 rFill.getTransparence());
458 pNewFillPrimitive = new PolyPolygonGraphicPrimitive2D(
459 rPolyPolygon,
460 rDefinitionRange,
461 rFill.getFillGraphic().createFillGraphicAttribute(rDefinitionRange));
463 else if(rFill.isSlideBackgroundFill())
465 // create needed Primitive2D representation for
466 // SlideBackgroundFill-mode
467 pNewFillPrimitive = new SlideBackgroundFillPrimitive2D(
468 rPolyPolygon);
470 else
472 // SDPR: check early if we have alpha and add directly
473 if(0.0 != rFill.getTransparence())
475 return new PolyPolygonRGBAPrimitive2D(
476 rPolyPolygon,
477 rFill.getColor(),
478 rFill.getTransparence());
481 // SDPR: check early if we have alpha gradient and add directly
482 // This may be useful for some SDPRs like Cairo: It can render RGBA
483 // gradients quick and direct, so it can use polygon color as RGB
484 // (no real gradient steps) combined with the existing alpha steps
485 if (!rAlphaGradient.isDefault())
487 return new PolyPolygonAlphaGradientPrimitive2D(
488 rPolyPolygon,
489 rFill.getColor(),
490 rAlphaGradient);
493 return new PolyPolygonColorPrimitive2D(
494 rPolyPolygon,
495 rFill.getColor());
498 if(0.0 != rFill.getTransparence())
500 // create simpleTransparencePrimitive, add created fill primitive
501 Primitive2DContainer aContent { pNewFillPrimitive };
502 return new UnifiedTransparencePrimitive2D(std::move(aContent), rFill.getTransparence());
505 if(!rAlphaGradient.isDefault())
507 // create sequence with created fill primitive
508 Primitive2DContainer aContent { pNewFillPrimitive };
510 // create FillGradientPrimitive2D for transparence and add to new sequence
511 // fillGradientPrimitive is enough here (compared to PolyPolygonGradientPrimitive2D) since float transparence will be masked anyways
512 Primitive2DContainer aAlpha {
513 new FillGradientPrimitive2D(
514 basegfx::utils::getRange(rPolyPolygon),
515 rDefinitionRange,
516 rAlphaGradient)
519 // create TransparencePrimitive2D using alpha and content
520 return new TransparencePrimitive2D(std::move(aContent), std::move(aAlpha));
523 // add to decomposition
524 return pNewFillPrimitive;
527 Primitive2DReference createPolygonLinePrimitive(
528 const basegfx::B2DPolygon& rPolygon,
529 const attribute::SdrLineAttribute& rLine,
530 const attribute::SdrLineStartEndAttribute& rStroke)
532 // create line and stroke attribute
533 const attribute::LineAttribute aLineAttribute(rLine.getColor(), rLine.getWidth(), rLine.getJoin(), rLine.getCap());
534 attribute::StrokeAttribute aStrokeAttribute(std::vector(rLine.getDotDashArray()), rLine.getFullDotDashLen());
535 rtl::Reference<BasePrimitive2D> pNewLinePrimitive;
537 if(!rPolygon.isClosed() && !rStroke.isDefault())
539 attribute::LineStartEndAttribute aStart(rStroke.getStartWidth(), rStroke.getStartPolyPolygon(), rStroke.isStartCentered());
540 attribute::LineStartEndAttribute aEnd(rStroke.getEndWidth(), rStroke.getEndPolyPolygon(), rStroke.isEndCentered());
542 // create data
543 pNewLinePrimitive = new PolygonStrokeArrowPrimitive2D(rPolygon, aLineAttribute, aStrokeAttribute, aStart, aEnd);
545 else
547 // create data
548 pNewLinePrimitive = new PolygonStrokePrimitive2D(rPolygon, aLineAttribute, std::move(aStrokeAttribute));
551 if(0.0 != rLine.getTransparence())
553 // create simpleTransparencePrimitive, add created fill primitive
554 Primitive2DContainer aContent { pNewLinePrimitive };
555 return new UnifiedTransparencePrimitive2D(std::move(aContent), rLine.getTransparence());
557 else
559 // add to decomposition
560 return pNewLinePrimitive;
564 Primitive2DReference createTextPrimitive(
565 const basegfx::B2DPolyPolygon& rUnitPolyPolygon,
566 const basegfx::B2DHomMatrix& rObjectTransform,
567 const attribute::SdrTextAttribute& rText,
568 const attribute::SdrLineAttribute& rStroke,
569 bool bCellText,
570 bool bWordWrap)
572 basegfx::B2DHomMatrix aAnchorTransform(rObjectTransform);
573 rtl::Reference<SdrTextPrimitive2D> pNew;
575 if(rText.isContour())
577 // contour text
578 if(!rStroke.isDefault() && 0.0 != rStroke.getWidth())
580 // take line width into account and shrink contour polygon accordingly
581 // decompose to get scale
582 basegfx::B2DVector aScale, aTranslate;
583 double fRotate, fShearX;
584 rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX);
586 // scale outline to object's size to allow growing with value relative to that size
587 // and also to keep aspect ratio
588 basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon);
589 aScaledUnitPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(
590 fabs(aScale.getX()), fabs(aScale.getY())));
592 // grow the polygon. To shrink, use negative value (half width)
593 aScaledUnitPolyPolygon = basegfx::utils::growInNormalDirection(aScaledUnitPolyPolygon, -(rStroke.getWidth() * 0.5));
595 // scale back to unit polygon
596 aScaledUnitPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(
597 0.0 != aScale.getX() ? 1.0 / aScale.getX() : 1.0,
598 0.0 != aScale.getY() ? 1.0 / aScale.getY() : 1.0));
600 // create with unit polygon
601 pNew = new SdrContourTextPrimitive2D(
602 &rText.getSdrText(),
603 rText.getOutlinerParaObject(),
604 std::move(aScaledUnitPolyPolygon),
605 rObjectTransform);
607 else
609 // create with unit polygon
610 pNew = new SdrContourTextPrimitive2D(
611 &rText.getSdrText(),
612 rText.getOutlinerParaObject(),
613 rUnitPolyPolygon,
614 rObjectTransform);
617 else if(!rText.getSdrFormTextAttribute().isDefault())
619 // text on path, use scaled polygon
620 basegfx::B2DPolyPolygon aScaledPolyPolygon(rUnitPolyPolygon);
621 aScaledPolyPolygon.transform(rObjectTransform);
622 pNew = new SdrPathTextPrimitive2D(
623 &rText.getSdrText(),
624 rText.getOutlinerParaObject(),
625 std::move(aScaledPolyPolygon),
626 rText.getSdrFormTextAttribute());
628 else
630 // rObjectTransform is the whole SdrObject transformation from unit rectangle
631 // to its size and position. Decompose to allow working with single values.
632 basegfx::B2DVector aScale, aTranslate;
633 double fRotate, fShearX;
634 rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX);
636 // extract mirroring
637 const bool bMirrorX(aScale.getX() < 0.0);
638 const bool bMirrorY(aScale.getY() < 0.0);
639 aScale = basegfx::absolute(aScale);
641 // Get the real size, since polygon outline and scale
642 // from the object transformation may vary (e.g. ellipse segments)
643 basegfx::B2DHomMatrix aJustScaleTransform;
644 aJustScaleTransform.set(0, 0, aScale.getX());
645 aJustScaleTransform.set(1, 1, aScale.getY());
646 basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon);
647 aScaledUnitPolyPolygon.transform(aJustScaleTransform);
648 const basegfx::B2DRange aTextAnchorRange
649 = getTextAnchorRange(rText, basegfx::utils::getRange(aScaledUnitPolyPolygon));
651 // now create a transformation from this basic range (aTextAnchorRange)
652 // #i121494# if we have no scale use at least 1.0 to have a carrier e.g. for
653 // mirror values, else these will get lost
654 aAnchorTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(
655 basegfx::fTools::equalZero(aTextAnchorRange.getWidth()) ? 1.0 : aTextAnchorRange.getWidth(),
656 basegfx::fTools::equalZero(aTextAnchorRange.getHeight()) ? 1.0 : aTextAnchorRange.getHeight(),
657 aTextAnchorRange.getMinX(), aTextAnchorRange.getMinY());
659 // apply mirroring
660 aAnchorTransform.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0);
662 // apply object's other transforms
663 aAnchorTransform = basegfx::utils::createShearXRotateTranslateB2DHomMatrix(fShearX, fRotate, aTranslate)
664 * aAnchorTransform;
666 if(rText.isFitToSize())
668 // stretched text in range
669 pNew = new SdrStretchTextPrimitive2D(
670 &rText.getSdrText(),
671 rText.getOutlinerParaObject(),
672 aAnchorTransform,
673 rText.isFixedCellHeight());
675 else if(rText.isAutoFit())
677 // isotropically scaled text in range
678 pNew = new SdrAutoFitTextPrimitive2D(
679 &rText.getSdrText(),
680 rText.getOutlinerParaObject(),
681 aAnchorTransform,
682 bWordWrap,
683 rText.isFixedCellHeight());
685 else if( rText.isChainable() && !rText.isInEditMode() )
687 pNew = new SdrChainedTextPrimitive2D(
688 &rText.getSdrText(),
689 rText.getOutlinerParaObject(),
690 aAnchorTransform );
692 else // text in range
694 // build new primitive
695 pNew = new SdrBlockTextPrimitive2D(
696 &rText.getSdrText(),
697 rText.getOutlinerParaObject(),
698 aAnchorTransform,
699 rText.getSdrTextHorzAdjust(),
700 rText.getSdrTextVertAdjust(),
701 rText.isFixedCellHeight(),
702 rText.isScroll(),
703 bCellText,
704 bWordWrap);
708 OSL_ENSURE(pNew != nullptr, "createTextPrimitive: no text primitive created (!)");
710 if(rText.isBlink())
712 // prepare animation and primitive list
713 drawinglayer::animation::AnimationEntryList aAnimationList;
714 rText.getBlinkTextTiming(aAnimationList);
716 if(0.0 != aAnimationList.getDuration())
718 // create content sequence
719 Primitive2DContainer aContent { pNew };
721 // create and add animated switch primitive
722 return new AnimatedBlinkPrimitive2D(aAnimationList, std::move(aContent));
724 else
726 // add to decomposition
727 return pNew;
731 if(rText.isScroll())
733 // suppress scroll when FontWork
734 if(rText.getSdrFormTextAttribute().isDefault())
736 // get scroll direction
737 const SdrTextAniDirection eDirection(rText.getSdrText().GetObject().GetTextAniDirection());
738 const bool bHorizontal(SdrTextAniDirection::Left == eDirection || SdrTextAniDirection::Right == eDirection);
740 // decompose to get separated values for the scroll box
741 basegfx::B2DVector aScale, aTranslate;
742 double fRotate, fShearX;
743 aAnchorTransform.decompose(aScale, aTranslate, fRotate, fShearX);
745 // build transform from scaled only to full AnchorTransform and inverse
746 const basegfx::B2DHomMatrix aSRT(basegfx::utils::createShearXRotateTranslateB2DHomMatrix(
747 fShearX, fRotate, aTranslate));
748 basegfx::B2DHomMatrix aISRT(aSRT);
749 aISRT.invert();
751 // bring the primitive back to scaled only and get scaled range, create new clone for this
752 rtl::Reference<SdrTextPrimitive2D> pNew2 = pNew->createTransformedClone(aISRT);
753 OSL_ENSURE(pNew2, "createTextPrimitive: Could not create transformed clone of text primitive (!)");
754 pNew = pNew2.get();
756 // create neutral geometry::ViewInformation2D for local range and decompose calls. This is okay
757 // since the decompose is view-independent
758 geometry::ViewInformation2D aViewInformation2D;
760 // get range
761 const basegfx::B2DRange aScaledRange(pNew->getB2DRange(aViewInformation2D));
763 // create left outside and right outside transformations. Also take care
764 // of the clip rectangle
765 basegfx::B2DHomMatrix aLeft, aRight;
766 basegfx::B2DPoint aClipTopLeft(0.0, 0.0);
767 basegfx::B2DPoint aClipBottomRight(aScale.getX(), aScale.getY());
769 if(bHorizontal)
771 aClipTopLeft.setY(aScaledRange.getMinY());
772 aClipBottomRight.setY(aScaledRange.getMaxY());
773 aLeft.translate(-aScaledRange.getMaxX(), 0.0);
774 aRight.translate(aScale.getX() - aScaledRange.getMinX(), 0.0);
776 else
778 aClipTopLeft.setX(aScaledRange.getMinX());
779 aClipBottomRight.setX(aScaledRange.getMaxX());
780 aLeft.translate(0.0, -aScaledRange.getMaxY());
781 aRight.translate(0.0, aScale.getY() - aScaledRange.getMinY());
784 aLeft *= aSRT;
785 aRight *= aSRT;
787 // prepare animation list
788 drawinglayer::animation::AnimationEntryList aAnimationList;
790 if(bHorizontal)
792 rText.getScrollTextTiming(aAnimationList, aScale.getX(), aScaledRange.getWidth());
794 else
796 rText.getScrollTextTiming(aAnimationList, aScale.getY(), aScaledRange.getHeight());
799 if(0.0 != aAnimationList.getDuration())
801 // create a new Primitive2DContainer containing the animated text in its scaled only state.
802 // use the decomposition to force to simple text primitives, those will no longer
803 // need the outliner for formatting (alternatively it is also possible to just add
804 // pNew to aNewPrimitiveSequence)
805 Primitive2DContainer aAnimSequence;
806 pNew->get2DDecomposition(aAnimSequence, aViewInformation2D);
807 pNew.clear();
809 // create a new animatedInterpolatePrimitive and add it
810 Primitive2DContainer aContent {
811 new AnimatedInterpolatePrimitive2D({ aLeft, aRight }, aAnimationList, std::move(aAnimSequence))
814 // scrolling needs an encapsulating clipping primitive
815 const basegfx::B2DRange aClipRange(aClipTopLeft, aClipBottomRight);
816 basegfx::B2DPolygon aClipPolygon(basegfx::utils::createPolygonFromRect(aClipRange));
817 aClipPolygon.transform(aSRT);
818 return new MaskPrimitive2D(basegfx::B2DPolyPolygon(aClipPolygon), std::move(aContent));
820 else
822 // add to decomposition
823 return pNew;
828 if(rText.isInEditMode())
830 // #i97628#
831 // encapsulate with TextHierarchyEditPrimitive2D to allow renderers
832 // to suppress actively edited content if needed
833 Primitive2DContainer aContent { pNew };
835 // create and add TextHierarchyEditPrimitive2D primitive
836 return new TextHierarchyEditPrimitive2D(std::move(aContent));
838 else
840 // add to decomposition
841 return pNew;
845 Primitive2DContainer createEmbeddedShadowPrimitive(
846 Primitive2DContainer&& rContent,
847 const attribute::SdrShadowAttribute& rShadow,
848 const basegfx::B2DHomMatrix& rObjectMatrix,
849 const Primitive2DContainer* pContentForShadow)
851 if(rContent.empty())
852 return std::move(rContent);
854 basegfx::B2DHomMatrix aShadowOffset;
856 if(rShadow.getSize().getX() != 100000)
858 basegfx::B2DTuple aScale;
859 basegfx::B2DTuple aTranslate;
860 double fRotate = 0;
861 double fShearX = 0;
862 rObjectMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
863 // Scale the shadow
864 aTranslate += getShadowScaleOriginOffset(aScale, rShadow.getAlignment());
865 aShadowOffset.translate(-aTranslate);
866 aShadowOffset.scale(rShadow.getSize().getX() * 0.00001, rShadow.getSize().getY() * 0.00001);
867 aShadowOffset.translate(aTranslate);
870 aShadowOffset.translate(rShadow.getOffset().getX(), rShadow.getOffset().getY());
872 // create shadow primitive and add content
873 const Primitive2DContainer& rContentForShadow
874 = pContentForShadow ? *pContentForShadow : rContent;
875 int nContentWithTransparence = std::count_if(
876 rContentForShadow.begin(), rContentForShadow.end(),
877 [](const Primitive2DReference& xChild) {
878 auto pChild = dynamic_cast<SdrCellPrimitive2D*>(xChild.get());
879 return pChild && pChild->getTransparenceForShadow() != 0;
881 if (nContentWithTransparence == 0)
883 Primitive2DContainer aRetval(2);
884 aRetval[0] =
885 new ShadowPrimitive2D(
886 aShadowOffset,
887 rShadow.getColor(),
888 rShadow.getBlur(),
889 Primitive2DContainer(pContentForShadow ? *pContentForShadow : rContent));
891 if (0.0 != rShadow.getTransparence())
893 // create SimpleTransparencePrimitive2D
894 Primitive2DContainer aTempContent{ aRetval[0] };
896 aRetval[0] =
897 new UnifiedTransparencePrimitive2D(
898 std::move(aTempContent),
899 rShadow.getTransparence());
902 aRetval[1] = new GroupPrimitive2D(std::move(rContent));
903 return aRetval;
906 Primitive2DContainer aRetval;
907 for (const auto& xChild : rContentForShadow)
909 aRetval.emplace_back(
910 new ShadowPrimitive2D(aShadowOffset, rShadow.getColor(), rShadow.getBlur(),
911 Primitive2DContainer({ xChild })));
912 if (rShadow.getTransparence() != 0.0)
914 Primitive2DContainer aTempContent{ aRetval.back() };
915 aRetval.back() = new UnifiedTransparencePrimitive2D(
916 std::move(aTempContent), rShadow.getTransparence());
920 aRetval.push_back(new GroupPrimitive2D(std::move(rContent)));
921 return aRetval;
924 Primitive2DContainer createEmbeddedGlowPrimitive(
925 Primitive2DContainer&& rContent,
926 const attribute::SdrGlowAttribute& rGlow)
928 if(rContent.empty())
929 return std::move(rContent);
930 Primitive2DContainer aRetval(2);
931 aRetval[0] = new GlowPrimitive2D(rGlow.getColor(), rGlow.getRadius(), Primitive2DContainer(rContent));
932 aRetval[1] = new GroupPrimitive2D(Primitive2DContainer(std::move(rContent)));
933 return aRetval;
936 Primitive2DContainer createEmbeddedTextGlowPrimitive(
937 Primitive2DContainer&& rContent,
938 const attribute::SdrGlowTextAttribute& rGlow)
940 if (rContent.empty())
941 return std::move(rContent);
943 Primitive2DContainer aRetval(2);
944 aRetval[0] = new GlowPrimitive2D(rGlow.getTextColor(), rGlow.getTextRadius(), Primitive2DContainer(rContent));
945 aRetval[1] = new GroupPrimitive2D(Primitive2DContainer(std::move(rContent)));
947 return aRetval;
950 Primitive2DContainer createEmbeddedSoftEdgePrimitive(Primitive2DContainer&& aContent,
951 sal_Int32 nRadius)
953 if (aContent.empty() || !nRadius)
954 return std::move(aContent);
955 Primitive2DContainer aRetval(1);
956 aRetval[0] = new SoftEdgePrimitive2D(nRadius, std::move(aContent));
957 return aRetval;
960 } // end of namespace
962 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */