1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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
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
)
79 case model::RectangleAlignment::TopLeft
:
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() };
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
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
;
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));
166 drawinglayer::attribute::SdrFillAttribute
getMasterPageFillAttribute(
167 const geometry::ViewInformation2D
& rViewInformation
,
168 basegfx::B2DVector
& rPageSize
)
170 drawinglayer::attribute::SdrFillAttribute aRetval
;
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());
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());
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
227 // It is currently simple buffered (due to being derived from
228 // BufferedDecompositionPrimitive2D) and detects if FillStyle changes
229 class SlideBackgroundFillPrimitive2D final
: public BufferedDecompositionPrimitive2D
232 /// the basegfx::B2DPolyPolygon geometry
233 basegfx::B2DPolyPolygon maPolyPolygon
;
235 /// the last SdrFillAttribute the geometry was created for
236 drawinglayer::attribute::SdrFillAttribute maLastFill
;
239 // create decomposition data
240 virtual Primitive2DReference
create2DDecomposition(
241 const geometry::ViewInformation2D
& rViewInformation
) const override
;
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
;
254 const basegfx::B2DPolyPolygon
& getB2DPolyPolygon() const { return maPolyPolygon
; }
257 virtual bool operator==(const BasePrimitive2D
& rPrimitive
) const override
;
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
)
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())
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())
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
))
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
313 aEmptyFillTransparenceGradient
));
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();
359 basegfx::B2DRange
SlideBackgroundFillPrimitive2D::getB2DRange(
360 const geometry::ViewInformation2D
& /*rViewInformation*/) const
363 return basegfx::utils::getRange(getB2DPolyPolygon());
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(
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(
429 bHasCompatibleAlphaGradient
? &rAlphaGradient
: nullptr,
430 bHasTransparency
? rFill
.getTransparence() : 0.0);
433 pNewFillPrimitive
= new PolyPolygonGradientPrimitive2D(
438 else if(!rFill
.getHatch().isDefault())
440 pNewFillPrimitive
= new PolyPolygonHatchPrimitive2D(
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(
454 rFill
.getFillGraphic().createFillGraphicAttribute(rDefinitionRange
),
455 rFill
.getTransparence());
458 pNewFillPrimitive
= new PolyPolygonGraphicPrimitive2D(
461 rFill
.getFillGraphic().createFillGraphicAttribute(rDefinitionRange
));
463 else if(rFill
.isSlideBackgroundFill())
465 // create needed Primitive2D representation for
466 // SlideBackgroundFill-mode
467 pNewFillPrimitive
= new SlideBackgroundFillPrimitive2D(
472 // SDPR: check early if we have alpha and add directly
473 if(0.0 != rFill
.getTransparence())
475 return new PolyPolygonRGBAPrimitive2D(
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(
493 return new PolyPolygonColorPrimitive2D(
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
),
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());
543 pNewLinePrimitive
= new PolygonStrokeArrowPrimitive2D(rPolygon
, aLineAttribute
, aStrokeAttribute
, aStart
, aEnd
);
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());
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
,
572 basegfx::B2DHomMatrix
aAnchorTransform(rObjectTransform
);
573 rtl::Reference
<SdrTextPrimitive2D
> pNew
;
575 if(rText
.isContour())
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(
603 rText
.getOutlinerParaObject(),
604 std::move(aScaledUnitPolyPolygon
),
609 // create with unit polygon
610 pNew
= new SdrContourTextPrimitive2D(
612 rText
.getOutlinerParaObject(),
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(
624 rText
.getOutlinerParaObject(),
625 std::move(aScaledPolyPolygon
),
626 rText
.getSdrFormTextAttribute());
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
);
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());
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
)
666 if(rText
.isFitToSize())
668 // stretched text in range
669 pNew
= new SdrStretchTextPrimitive2D(
671 rText
.getOutlinerParaObject(),
673 rText
.isFixedCellHeight());
675 else if(rText
.isAutoFit())
677 // isotropically scaled text in range
678 pNew
= new SdrAutoFitTextPrimitive2D(
680 rText
.getOutlinerParaObject(),
683 rText
.isFixedCellHeight());
685 else if( rText
.isChainable() && !rText
.isInEditMode() )
687 pNew
= new SdrChainedTextPrimitive2D(
689 rText
.getOutlinerParaObject(),
692 else // text in range
694 // build new primitive
695 pNew
= new SdrBlockTextPrimitive2D(
697 rText
.getOutlinerParaObject(),
699 rText
.getSdrTextHorzAdjust(),
700 rText
.getSdrTextVertAdjust(),
701 rText
.isFixedCellHeight(),
708 OSL_ENSURE(pNew
!= nullptr, "createTextPrimitive: no text primitive created (!)");
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
));
726 // add to decomposition
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
);
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 (!)");
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
;
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());
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);
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());
787 // prepare animation list
788 drawinglayer::animation::AnimationEntryList aAnimationList
;
792 rText
.getScrollTextTiming(aAnimationList
, aScale
.getX(), aScaledRange
.getWidth());
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
);
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
));
822 // add to decomposition
828 if(rText
.isInEditMode())
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
));
840 // add to decomposition
845 Primitive2DContainer
createEmbeddedShadowPrimitive(
846 Primitive2DContainer
&& rContent
,
847 const attribute::SdrShadowAttribute
& rShadow
,
848 const basegfx::B2DHomMatrix
& rObjectMatrix
,
849 const Primitive2DContainer
* pContentForShadow
)
852 return std::move(rContent
);
854 basegfx::B2DHomMatrix aShadowOffset
;
856 if(rShadow
.getSize().getX() != 100000)
858 basegfx::B2DTuple aScale
;
859 basegfx::B2DTuple aTranslate
;
862 rObjectMatrix
.decompose(aScale
, aTranslate
, fRotate
, fShearX
);
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);
885 new ShadowPrimitive2D(
889 Primitive2DContainer(pContentForShadow
? *pContentForShadow
: rContent
));
891 if (0.0 != rShadow
.getTransparence())
893 // create SimpleTransparencePrimitive2D
894 Primitive2DContainer aTempContent
{ aRetval
[0] };
897 new UnifiedTransparencePrimitive2D(
898 std::move(aTempContent
),
899 rShadow
.getTransparence());
902 aRetval
[1] = new GroupPrimitive2D(std::move(rContent
));
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
)));
924 Primitive2DContainer
createEmbeddedGlowPrimitive(
925 Primitive2DContainer
&& rContent
,
926 const attribute::SdrGlowAttribute
& rGlow
)
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
)));
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
)));
950 Primitive2DContainer
createEmbeddedSoftEdgePrimitive(Primitive2DContainer
&& aContent
,
953 if (aContent
.empty() || !nRadius
)
954 return std::move(aContent
);
955 Primitive2DContainer
aRetval(1);
956 aRetval
[0] = new SoftEdgePrimitive2D(nRadius
, std::move(aContent
));
960 } // end of namespace
962 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */