summaryrefslogtreecommitdiffstats
path: root/svx/source/sdr/primitive2d/sdrdecompositiontools.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--svx/source/sdr/primitive2d/sdrdecompositiontools.cxx884
1 files changed, 884 insertions, 0 deletions
diff --git a/svx/source/sdr/primitive2d/sdrdecompositiontools.cxx b/svx/source/sdr/primitive2d/sdrdecompositiontools.cxx
new file mode 100644
index 000000000..a1838b03f
--- /dev/null
+++ b/svx/source/sdr/primitive2d/sdrdecompositiontools.cxx
@@ -0,0 +1,884 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sdr/primitive2d/sdrdecompositiontools.hxx>
+#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
+#include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonHatchPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
+#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
+#include <drawinglayer/attribute/strokeattribute.hxx>
+#include <drawinglayer/attribute/linestartendattribute.hxx>
+#include <drawinglayer/attribute/sdrfillgraphicattribute.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <drawinglayer/primitive2d/shadowprimitive2d.hxx>
+#include <sdr/attribute/sdrtextattribute.hxx>
+#include <drawinglayer/primitive2d/glowprimitive2d.hxx>
+#include <sdr/primitive2d/sdrtextprimitive2d.hxx>
+#include <svx/svdotext.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <drawinglayer/primitive2d/animatedprimitive2d.hxx>
+#include <drawinglayer/animation/animationtiming.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <drawinglayer/geometry/viewinformation2d.hxx>
+#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
+#include <drawinglayer/attribute/sdrfillattribute.hxx>
+#include <drawinglayer/attribute/sdrlineattribute.hxx>
+#include <drawinglayer/attribute/sdrlinestartendattribute.hxx>
+#include <drawinglayer/attribute/sdrshadowattribute.hxx>
+#include <drawinglayer/attribute/sdrglowattribute.hxx>
+#include <osl/diagnose.h>
+
+// for SlideBackgroundFillPrimitive2D
+#include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
+#include <svx/unoapi.hxx>
+#include <svx/svdpage.hxx>
+#include <sdr/primitive2d/sdrattributecreator.hxx>
+#include <sdr/contact/viewcontactofmasterpagedescriptor.hxx>
+
+using namespace com::sun::star;
+
+
+namespace drawinglayer::primitive2d
+{
+namespace
+{
+// See also: SdrTextObj::AdjustRectToTextDistance
+basegfx::B2DRange getTextAnchorRange(const attribute::SdrTextAttribute& rText,
+ const basegfx::B2DRange& rSnapRange)
+{
+ // Take vertical text orientation into account when deciding
+ // which dimension is its width, and which is its height
+ const OutlinerParaObject& rOutlinerParaObj = rText.getOutlinerParaObject();
+ const bool bVerticalWriting(rOutlinerParaObj.IsEffectivelyVertical());
+ const double fWidthForText = bVerticalWriting ? rSnapRange.getHeight() : rSnapRange.getWidth();
+ // create a range describing the wanted text position and size (aTextAnchorRange). This
+ // means to use the text distance values here
+ // If the margin is larger than the entire width of the text area, then limit the
+ // margin.
+ const double fTextLeftDistance
+ = std::min(static_cast<double>(rText.getTextLeftDistance()), fWidthForText);
+ const double nTextRightDistance
+ = std::min(static_cast<double>(rText.getTextRightDistance()), fWidthForText);
+ double fDistanceForTextL, fDistanceForTextT, fDistanceForTextR, fDistanceForTextB;
+ if (!bVerticalWriting)
+ {
+ fDistanceForTextL = fTextLeftDistance;
+ fDistanceForTextT = rText.getTextUpperDistance();
+ fDistanceForTextR = nTextRightDistance;
+ fDistanceForTextB = rText.getTextLowerDistance();
+ }
+ else if (rOutlinerParaObj.IsTopToBottom())
+ {
+ fDistanceForTextL = rText.getTextLowerDistance();
+ fDistanceForTextT = fTextLeftDistance;
+ fDistanceForTextR = rText.getTextUpperDistance();
+ fDistanceForTextB = nTextRightDistance;
+ }
+ else
+ {
+ fDistanceForTextL = rText.getTextUpperDistance();
+ fDistanceForTextT = nTextRightDistance;
+ fDistanceForTextR = rText.getTextLowerDistance();
+ fDistanceForTextB = fTextLeftDistance;
+ }
+ const basegfx::B2DPoint aTopLeft(rSnapRange.getMinX() + fDistanceForTextL,
+ rSnapRange.getMinY() + fDistanceForTextT);
+ const basegfx::B2DPoint aBottomRight(rSnapRange.getMaxX() - fDistanceForTextR,
+ rSnapRange.getMaxY() - fDistanceForTextB);
+ basegfx::B2DRange aAnchorRange;
+ aAnchorRange.expand(aTopLeft);
+ aAnchorRange.expand(aBottomRight);
+
+ // If the shape has no width, then don't attempt to break the text into multiple
+ // lines, not a single character would satisfy a zero width requirement.
+ // SdrTextObj::impDecomposeBlockTextPrimitive() uses the same constant to
+ // effectively set no limits.
+ if (!bVerticalWriting && aAnchorRange.getWidth() == 0)
+ {
+ aAnchorRange.expand(basegfx::B2DPoint(aTopLeft.getX() - 1000000, aTopLeft.getY()));
+ aAnchorRange.expand(basegfx::B2DPoint(aBottomRight.getX() + 1000000, aBottomRight.getY()));
+ }
+ else if (bVerticalWriting && aAnchorRange.getHeight() == 0)
+ {
+ aAnchorRange.expand(basegfx::B2DPoint(aTopLeft.getX(), aTopLeft.getY() - 1000000));
+ aAnchorRange.expand(basegfx::B2DPoint(aBottomRight.getX(), aBottomRight.getY() + 1000000));
+ }
+ return aAnchorRange;
+}
+
+drawinglayer::attribute::SdrFillAttribute getMasterPageFillAttribute(
+ const geometry::ViewInformation2D& rViewInformation,
+ basegfx::B2DVector& rPageSize)
+{
+ drawinglayer::attribute::SdrFillAttribute aRetval;
+
+ // get SdrPage
+ const SdrPage* pVisualizedPage(GetSdrPageFromXDrawPage(rViewInformation.getVisualizedPage()));
+
+ if(nullptr != pVisualizedPage)
+ {
+ // copy needed values for further processing
+ rPageSize.setX(pVisualizedPage->GetWidth());
+ rPageSize.setY(pVisualizedPage->GetHeight());
+
+ if(pVisualizedPage->IsMasterPage())
+ {
+ // the page is a MasterPage, so we are in MasterPage view
+ // still need #i110846#, see ViewContactOfMasterPage
+ if(pVisualizedPage->getSdrPageProperties().GetStyleSheet())
+ {
+ // create page fill attributes with correct properties
+ aRetval = drawinglayer::primitive2d::createNewSdrFillAttribute(
+ pVisualizedPage->getSdrPageProperties().GetItemSet());
+ }
+ }
+ else
+ {
+ // the page is *no* MasterPage, we are in normal Page view, get the MasterPage
+ if(pVisualizedPage->TRG_HasMasterPage())
+ {
+ sdr::contact::ViewContact& rVC(pVisualizedPage->TRG_GetMasterPageDescriptorViewContact());
+ sdr::contact::ViewContactOfMasterPageDescriptor* pVCOMPD(
+ dynamic_cast<sdr::contact::ViewContactOfMasterPageDescriptor*>(&rVC));
+
+ if(nullptr != pVCOMPD)
+ {
+ // in this case the still needed #i110846# is part of
+ // getCorrectSdrPageProperties, that's the main reason to re-use
+ // that call/functionality here
+ const SdrPageProperties* pCorrectProperties(
+ pVCOMPD->GetMasterPageDescriptor().getCorrectSdrPageProperties());
+
+ if(pCorrectProperties)
+ {
+ // create page fill attributes when correct properties were identified
+ aRetval = drawinglayer::primitive2d::createNewSdrFillAttribute(
+ pCorrectProperties->GetItemSet());
+ }
+ }
+ }
+ }
+ }
+
+ return aRetval;
+}
+
+// provide a Primitive2D for the SlideBackgroundFill-mode. It is capable
+// of expressing that state of fill and it's decomposition implements all
+// needed preparation of the geometry in an isolated and controllable
+// space and way.
+// It is currently simple buffered (due to being derived from
+// BufferedDecompositionPrimitive2D) and detects if FillStyle changes
+class SlideBackgroundFillPrimitive2D final : public BufferedDecompositionPrimitive2D
+{
+private:
+ /// the basegfx::B2DPolyPolygon geometry
+ basegfx::B2DPolyPolygon maPolyPolygon;
+
+ /// the last SdrFillAttribute the geometry was created for
+ drawinglayer::attribute::SdrFillAttribute maLastFill;
+
+protected:
+ // create decomposition data
+ virtual void create2DDecomposition(
+ Primitive2DContainer& rContainer,
+ const geometry::ViewInformation2D& rViewInformation) const override;
+
+public:
+ /// constructor
+ SlideBackgroundFillPrimitive2D(
+ const basegfx::B2DPolyPolygon& rPolyPolygon);
+
+ /// check existing decomposition data, call parent
+ virtual void get2DDecomposition(
+ Primitive2DDecompositionVisitor& rVisitor,
+ const geometry::ViewInformation2D& rViewInformation) const override;
+
+ /// data read access
+ const basegfx::B2DPolyPolygon& getB2DPolyPolygon() const { return maPolyPolygon; }
+
+ /// compare operator
+ virtual bool operator==(const BasePrimitive2D& rPrimitive) const override;
+
+ /// get range
+ virtual basegfx::B2DRange getB2DRange(const geometry::ViewInformation2D& rViewInformation) const override;
+
+ /// provide unique ID
+ virtual sal_uInt32 getPrimitive2DID() const override;
+};
+
+SlideBackgroundFillPrimitive2D::SlideBackgroundFillPrimitive2D(
+ const basegfx::B2DPolyPolygon& rPolyPolygon)
+: BufferedDecompositionPrimitive2D()
+, maPolyPolygon(rPolyPolygon)
+, maLastFill()
+{
+}
+
+void SlideBackgroundFillPrimitive2D::create2DDecomposition(
+ Primitive2DContainer& rContainer,
+ const geometry::ViewInformation2D& rViewInformation) const
+{
+ basegfx::B2DVector aPageSize;
+
+ // get fill from target Page, this will check for all needed things
+ // like MasterPage/relationships, etc. (see getMasterPageFillAttribute impl above)
+ drawinglayer::attribute::SdrFillAttribute aFill(
+ getMasterPageFillAttribute(rViewInformation, aPageSize));
+
+ // if fill is on default (empty), nothing will be shown, we are done
+ if(aFill.isDefault())
+ return;
+
+ // Get PolygonRange of own local geometry
+ const basegfx::B2DRange aPolygonRange(getB2DPolyPolygon().getB2DRange());
+
+ // if local geometry is empty, nothing will be shown, we are done
+ if(aPolygonRange.isEmpty())
+ return;
+
+ // Get PageRange
+ const basegfx::B2DRange aPageRange(0.0, 0.0, aPageSize.getX(), aPageSize.getY());
+
+ // if local geometry does not overlap with PageRange, nothing will be shown, we are done
+ if(!aPageRange.overlaps(aPolygonRange))
+ return;
+
+ // create FillPrimitive2D with the geometry (the PolyPolygon) and
+ // the page's definitonRange to:
+ // - on one hand limit to geometry
+ // - on the other hand allow continutation of fill outside of
+ // MasterPage's range
+ const attribute::FillGradientAttribute aEmptyFillTransparenceGradient;
+ const Primitive2DReference aCreatedFill(
+ createPolyPolygonFillPrimitive(
+ getB2DPolyPolygon(), // geometry
+ aPageRange, // definition range
+ aFill,
+ aEmptyFillTransparenceGradient));
+
+ rContainer = Primitive2DContainer { aCreatedFill };
+}
+
+void SlideBackgroundFillPrimitive2D::get2DDecomposition(
+ Primitive2DDecompositionVisitor& rVisitor,
+ const geometry::ViewInformation2D& rViewInformation) const
+{
+ basegfx::B2DVector aPageSize;
+ drawinglayer::attribute::SdrFillAttribute aFill;
+
+ if(!getBuffered2DDecomposition().empty())
+ {
+ aFill = getMasterPageFillAttribute(rViewInformation, aPageSize);
+
+ if(!(aFill == maLastFill))
+ {
+ // conditions of last local decomposition have changed, delete
+ const_cast< SlideBackgroundFillPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer());
+ }
+ }
+
+ if(getBuffered2DDecomposition().empty())
+ {
+ // remember last Fill
+ const_cast< SlideBackgroundFillPrimitive2D* >(this)->maLastFill = aFill;
+ }
+
+ // use parent implementation
+ BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
+}
+
+bool SlideBackgroundFillPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
+{
+ if (BufferedDecompositionPrimitive2D::operator==(rPrimitive))
+ {
+ const SlideBackgroundFillPrimitive2D& rCompare
+ = static_cast<const SlideBackgroundFillPrimitive2D&>(rPrimitive);
+
+ return getB2DPolyPolygon() == rCompare.getB2DPolyPolygon();
+ }
+
+ return false;
+}
+
+basegfx::B2DRange SlideBackgroundFillPrimitive2D::getB2DRange(
+ const geometry::ViewInformation2D& /*rViewInformation*/) const
+{
+ // return range
+ return basegfx::utils::getRange(getB2DPolyPolygon());
+}
+
+// provide unique ID
+sal_uInt32 SlideBackgroundFillPrimitive2D::getPrimitive2DID() const
+{
+ return PRIMITIVE2D_ID_SLIDEBACKGROUNDFILLPRIMITIVE2D;
+}
+
+}; // end of anonymous namespace
+
+ class TransparencePrimitive2D;
+
+ Primitive2DReference createPolyPolygonFillPrimitive(
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ const attribute::SdrFillAttribute& rFill,
+ const attribute::FillGradientAttribute& rFillGradient)
+ {
+ // when we have no given definition range, use the range of the given geometry
+ // also for definition (simplest case)
+ const basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyPolygon));
+
+ return createPolyPolygonFillPrimitive(
+ rPolyPolygon,
+ aRange,
+ rFill,
+ rFillGradient);
+ }
+
+ Primitive2DReference createPolyPolygonFillPrimitive(
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ const basegfx::B2DRange& rDefinitionRange,
+ const attribute::SdrFillAttribute& rFill,
+ const attribute::FillGradientAttribute& rFillGradient)
+ {
+ if(basegfx::fTools::moreOrEqual(rFill.getTransparence(), 1.0))
+ {
+ return Primitive2DReference();
+ }
+
+ // prepare fully scaled polygon
+ rtl::Reference<BasePrimitive2D> pNewFillPrimitive;
+
+ if(!rFill.getGradient().isDefault())
+ {
+ pNewFillPrimitive = new PolyPolygonGradientPrimitive2D(
+ rPolyPolygon,
+ rDefinitionRange,
+ rFill.getGradient());
+ }
+ else if(!rFill.getHatch().isDefault())
+ {
+ pNewFillPrimitive = new PolyPolygonHatchPrimitive2D(
+ rPolyPolygon,
+ rDefinitionRange,
+ rFill.getColor(),
+ rFill.getHatch());
+ }
+ else if(!rFill.getFillGraphic().isDefault())
+ {
+ pNewFillPrimitive = new PolyPolygonGraphicPrimitive2D(
+ rPolyPolygon,
+ rDefinitionRange,
+ rFill.getFillGraphic().createFillGraphicAttribute(rDefinitionRange));
+ }
+ else if(rFill.isSlideBackgroundFill())
+ {
+ // create needed Primitive2D representation for
+ // SlideBackgroundFill-mode
+ pNewFillPrimitive = new SlideBackgroundFillPrimitive2D(
+ rPolyPolygon);
+ }
+ else
+ {
+ pNewFillPrimitive = new PolyPolygonColorPrimitive2D(
+ rPolyPolygon,
+ rFill.getColor());
+ }
+
+ if(0.0 != rFill.getTransparence())
+ {
+ // create simpleTransparencePrimitive, add created fill primitive
+ Primitive2DContainer aContent { pNewFillPrimitive };
+ return Primitive2DReference(new UnifiedTransparencePrimitive2D(std::move(aContent), rFill.getTransparence()));
+ }
+ else if(!rFillGradient.isDefault())
+ {
+ // create sequence with created fill primitive
+ Primitive2DContainer aContent { pNewFillPrimitive };
+
+ // create FillGradientPrimitive2D for transparence and add to new sequence
+ // fillGradientPrimitive is enough here (compared to PolyPolygonGradientPrimitive2D) since float transparence will be masked anyways
+ const basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyPolygon));
+ Primitive2DReference xRefB(
+ new FillGradientPrimitive2D(
+ aRange,
+ rDefinitionRange,
+ rFillGradient));
+ Primitive2DContainer aAlpha { xRefB };
+
+ // create TransparencePrimitive2D using alpha and content
+ return Primitive2DReference(new TransparencePrimitive2D(std::move(aContent), std::move(aAlpha)));
+ }
+ else
+ {
+ // add to decomposition
+ return pNewFillPrimitive;
+ }
+ }
+
+ Primitive2DReference createPolygonLinePrimitive(
+ const basegfx::B2DPolygon& rPolygon,
+ const attribute::SdrLineAttribute& rLine,
+ const attribute::SdrLineStartEndAttribute& rStroke)
+ {
+ // create line and stroke attribute
+ const attribute::LineAttribute aLineAttribute(rLine.getColor(), rLine.getWidth(), rLine.getJoin(), rLine.getCap());
+ const attribute::StrokeAttribute aStrokeAttribute(std::vector(rLine.getDotDashArray()), rLine.getFullDotDashLen());
+ rtl::Reference<BasePrimitive2D> pNewLinePrimitive;
+
+ if(!rPolygon.isClosed() && !rStroke.isDefault())
+ {
+ attribute::LineStartEndAttribute aStart(rStroke.getStartWidth(), rStroke.getStartPolyPolygon(), rStroke.isStartCentered());
+ attribute::LineStartEndAttribute aEnd(rStroke.getEndWidth(), rStroke.getEndPolyPolygon(), rStroke.isEndCentered());
+
+ // create data
+ pNewLinePrimitive = new PolygonStrokeArrowPrimitive2D(rPolygon, aLineAttribute, aStrokeAttribute, aStart, aEnd);
+ }
+ else
+ {
+ // create data
+ pNewLinePrimitive = new PolygonStrokePrimitive2D(rPolygon, aLineAttribute, aStrokeAttribute);
+ }
+
+ if(0.0 != rLine.getTransparence())
+ {
+ // create simpleTransparencePrimitive, add created fill primitive
+ Primitive2DContainer aContent { pNewLinePrimitive };
+ return Primitive2DReference(new UnifiedTransparencePrimitive2D(std::move(aContent), rLine.getTransparence()));
+ }
+ else
+ {
+ // add to decomposition
+ return pNewLinePrimitive;
+ }
+ }
+
+ Primitive2DReference createTextPrimitive(
+ const basegfx::B2DPolyPolygon& rUnitPolyPolygon,
+ const basegfx::B2DHomMatrix& rObjectTransform,
+ const attribute::SdrTextAttribute& rText,
+ const attribute::SdrLineAttribute& rStroke,
+ bool bCellText,
+ bool bWordWrap)
+ {
+ basegfx::B2DHomMatrix aAnchorTransform(rObjectTransform);
+ rtl::Reference<SdrTextPrimitive2D> pNew;
+
+ if(rText.isContour())
+ {
+ // contour text
+ if(!rStroke.isDefault() && 0.0 != rStroke.getWidth())
+ {
+ // take line width into account and shrink contour polygon accordingly
+ // decompose to get scale
+ basegfx::B2DVector aScale, aTranslate;
+ double fRotate, fShearX;
+ rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX);
+
+ // scale outline to object's size to allow growing with value relative to that size
+ // and also to keep aspect ratio
+ basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon);
+ aScaledUnitPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(
+ fabs(aScale.getX()), fabs(aScale.getY())));
+
+ // grow the polygon. To shrink, use negative value (half width)
+ aScaledUnitPolyPolygon = basegfx::utils::growInNormalDirection(aScaledUnitPolyPolygon, -(rStroke.getWidth() * 0.5));
+
+ // scale back to unit polygon
+ aScaledUnitPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(
+ 0.0 != aScale.getX() ? 1.0 / aScale.getX() : 1.0,
+ 0.0 != aScale.getY() ? 1.0 / aScale.getY() : 1.0));
+
+ // create with unit polygon
+ pNew = new SdrContourTextPrimitive2D(
+ &rText.getSdrText(),
+ rText.getOutlinerParaObject(),
+ aScaledUnitPolyPolygon,
+ rObjectTransform);
+ }
+ else
+ {
+ // create with unit polygon
+ pNew = new SdrContourTextPrimitive2D(
+ &rText.getSdrText(),
+ rText.getOutlinerParaObject(),
+ rUnitPolyPolygon,
+ rObjectTransform);
+ }
+ }
+ else if(!rText.getSdrFormTextAttribute().isDefault())
+ {
+ // text on path, use scaled polygon
+ basegfx::B2DPolyPolygon aScaledPolyPolygon(rUnitPolyPolygon);
+ aScaledPolyPolygon.transform(rObjectTransform);
+ pNew = new SdrPathTextPrimitive2D(
+ &rText.getSdrText(),
+ rText.getOutlinerParaObject(),
+ aScaledPolyPolygon,
+ rText.getSdrFormTextAttribute());
+ }
+ else
+ {
+ // rObjectTransform is the whole SdrObject transformation from unit rectangle
+ // to its size and position. Decompose to allow working with single values.
+ basegfx::B2DVector aScale, aTranslate;
+ double fRotate, fShearX;
+ rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX);
+
+ // extract mirroring
+ const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
+ const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
+ aScale = basegfx::absolute(aScale);
+
+ // Get the real size, since polygon outline and scale
+ // from the object transformation may vary (e.g. ellipse segments)
+ basegfx::B2DHomMatrix aJustScaleTransform;
+ aJustScaleTransform.set(0, 0, aScale.getX());
+ aJustScaleTransform.set(1, 1, aScale.getY());
+ basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon);
+ aScaledUnitPolyPolygon.transform(aJustScaleTransform);
+ const basegfx::B2DRange aTextAnchorRange
+ = getTextAnchorRange(rText, basegfx::utils::getRange(aScaledUnitPolyPolygon));
+
+ // now create a transformation from this basic range (aTextAnchorRange)
+ // #i121494# if we have no scale use at least 1.0 to have a carrier e.g. for
+ // mirror values, else these will get lost
+ aAnchorTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(
+ basegfx::fTools::equalZero(aTextAnchorRange.getWidth()) ? 1.0 : aTextAnchorRange.getWidth(),
+ basegfx::fTools::equalZero(aTextAnchorRange.getHeight()) ? 1.0 : aTextAnchorRange.getHeight(),
+ aTextAnchorRange.getMinX(), aTextAnchorRange.getMinY());
+
+ // apply mirroring
+ aAnchorTransform.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0);
+
+ // apply object's other transforms
+ aAnchorTransform = basegfx::utils::createShearXRotateTranslateB2DHomMatrix(fShearX, fRotate, aTranslate)
+ * aAnchorTransform;
+
+ if(rText.isFitToSize())
+ {
+ // stretched text in range
+ pNew = new SdrStretchTextPrimitive2D(
+ &rText.getSdrText(),
+ rText.getOutlinerParaObject(),
+ aAnchorTransform,
+ rText.isFixedCellHeight());
+ }
+ else if(rText.isAutoFit())
+ {
+ // isotropically scaled text in range
+ pNew = new SdrAutoFitTextPrimitive2D(
+ &rText.getSdrText(),
+ rText.getOutlinerParaObject(),
+ aAnchorTransform,
+ bWordWrap);
+ }
+ else if( rText.isChainable() && !rText.isInEditMode() )
+ {
+ pNew = new SdrChainedTextPrimitive2D(
+ &rText.getSdrText(),
+ rText.getOutlinerParaObject(),
+ aAnchorTransform );
+ }
+ else // text in range
+ {
+ // build new primitive
+ pNew = new SdrBlockTextPrimitive2D(
+ &rText.getSdrText(),
+ rText.getOutlinerParaObject(),
+ aAnchorTransform,
+ rText.getSdrTextHorzAdjust(),
+ rText.getSdrTextVertAdjust(),
+ rText.isFixedCellHeight(),
+ rText.isScroll(),
+ bCellText,
+ bWordWrap);
+ }
+ }
+
+ OSL_ENSURE(pNew != nullptr, "createTextPrimitive: no text primitive created (!)");
+
+ if(rText.isBlink())
+ {
+ // prepare animation and primitive list
+ drawinglayer::animation::AnimationEntryList aAnimationList;
+ rText.getBlinkTextTiming(aAnimationList);
+
+ if(0.0 != aAnimationList.getDuration())
+ {
+ // create content sequence
+ Primitive2DReference xRefA(pNew);
+ Primitive2DContainer aContent { xRefA };
+
+ // create and add animated switch primitive
+ return Primitive2DReference(new AnimatedBlinkPrimitive2D(aAnimationList, std::move(aContent)));
+ }
+ else
+ {
+ // add to decomposition
+ return Primitive2DReference(pNew);
+ }
+ }
+
+ if(rText.isScroll())
+ {
+ // suppress scroll when FontWork
+ if(rText.getSdrFormTextAttribute().isDefault())
+ {
+ // get scroll direction
+ const SdrTextAniDirection eDirection(rText.getSdrText().GetObject().GetTextAniDirection());
+ const bool bHorizontal(SdrTextAniDirection::Left == eDirection || SdrTextAniDirection::Right == eDirection);
+
+ // decompose to get separated values for the scroll box
+ basegfx::B2DVector aScale, aTranslate;
+ double fRotate, fShearX;
+ aAnchorTransform.decompose(aScale, aTranslate, fRotate, fShearX);
+
+ // build transform from scaled only to full AnchorTransform and inverse
+ const basegfx::B2DHomMatrix aSRT(basegfx::utils::createShearXRotateTranslateB2DHomMatrix(
+ fShearX, fRotate, aTranslate));
+ basegfx::B2DHomMatrix aISRT(aSRT);
+ aISRT.invert();
+
+ // bring the primitive back to scaled only and get scaled range, create new clone for this
+ rtl::Reference<SdrTextPrimitive2D> pNew2 = pNew->createTransformedClone(aISRT);
+ OSL_ENSURE(pNew2, "createTextPrimitive: Could not create transformed clone of text primitive (!)");
+ pNew = pNew2.get();
+
+ // create neutral geometry::ViewInformation2D for local range and decompose calls. This is okay
+ // since the decompose is view-independent
+ geometry::ViewInformation2D aViewInformation2D;
+
+ // get range
+ const basegfx::B2DRange aScaledRange(pNew->getB2DRange(aViewInformation2D));
+
+ // create left outside and right outside transformations. Also take care
+ // of the clip rectangle
+ basegfx::B2DHomMatrix aLeft, aRight;
+ basegfx::B2DPoint aClipTopLeft(0.0, 0.0);
+ basegfx::B2DPoint aClipBottomRight(aScale.getX(), aScale.getY());
+
+ if(bHorizontal)
+ {
+ aClipTopLeft.setY(aScaledRange.getMinY());
+ aClipBottomRight.setY(aScaledRange.getMaxY());
+ aLeft.translate(-aScaledRange.getMaxX(), 0.0);
+ aRight.translate(aScale.getX() - aScaledRange.getMinX(), 0.0);
+ }
+ else
+ {
+ aClipTopLeft.setX(aScaledRange.getMinX());
+ aClipBottomRight.setX(aScaledRange.getMaxX());
+ aLeft.translate(0.0, -aScaledRange.getMaxY());
+ aRight.translate(0.0, aScale.getY() - aScaledRange.getMinY());
+ }
+
+ aLeft *= aSRT;
+ aRight *= aSRT;
+
+ // prepare animation list
+ drawinglayer::animation::AnimationEntryList aAnimationList;
+
+ if(bHorizontal)
+ {
+ rText.getScrollTextTiming(aAnimationList, aScale.getX(), aScaledRange.getWidth());
+ }
+ else
+ {
+ rText.getScrollTextTiming(aAnimationList, aScale.getY(), aScaledRange.getHeight());
+ }
+
+ if(0.0 != aAnimationList.getDuration())
+ {
+ // create a new Primitive2DContainer containing the animated text in its scaled only state.
+ // use the decomposition to force to simple text primitives, those will no longer
+ // need the outliner for formatting (alternatively it is also possible to just add
+ // pNew to aNewPrimitiveSequence)
+ Primitive2DContainer aAnimSequence;
+ pNew->get2DDecomposition(aAnimSequence, aViewInformation2D);
+ pNew.clear();
+
+ // create a new animatedInterpolatePrimitive and add it
+ Primitive2DReference xRefA(new AnimatedInterpolatePrimitive2D({ aLeft, aRight }, aAnimationList, std::move(aAnimSequence)));
+ Primitive2DContainer aContent { xRefA };
+
+ // scrolling needs an encapsulating clipping primitive
+ const basegfx::B2DRange aClipRange(aClipTopLeft, aClipBottomRight);
+ basegfx::B2DPolygon aClipPolygon(basegfx::utils::createPolygonFromRect(aClipRange));
+ aClipPolygon.transform(aSRT);
+ return Primitive2DReference(new MaskPrimitive2D(basegfx::B2DPolyPolygon(aClipPolygon), std::move(aContent)));
+ }
+ else
+ {
+ // add to decomposition
+ return Primitive2DReference(pNew);
+ }
+ }
+ }
+
+ if(rText.isInEditMode())
+ {
+ // #i97628#
+ // encapsulate with TextHierarchyEditPrimitive2D to allow renderers
+ // to suppress actively edited content if needed
+ Primitive2DReference xRefA(pNew);
+ Primitive2DContainer aContent { xRefA };
+
+ // create and add TextHierarchyEditPrimitive2D primitive
+ return Primitive2DReference(new TextHierarchyEditPrimitive2D(std::move(aContent)));
+ }
+ else
+ {
+ // add to decomposition
+ return pNew;
+ }
+ }
+
+ Primitive2DContainer createEmbeddedShadowPrimitive(
+ Primitive2DContainer&& rContent,
+ const attribute::SdrShadowAttribute& rShadow,
+ const basegfx::B2DHomMatrix& rObjectMatrix,
+ const Primitive2DContainer* pContentForShadow)
+ {
+ if(rContent.empty())
+ return std::move(rContent);
+
+ basegfx::B2DHomMatrix aShadowOffset;
+
+ if(rShadow.getSize().getX() != 100000)
+ {
+ basegfx::B2DTuple aScale;
+ basegfx::B2DTuple aTranslate;
+ double fRotate = 0;
+ double fShearX = 0;
+ rObjectMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
+ // Scale the shadow
+ double nTranslateX = aTranslate.getX();
+ double nTranslateY = aTranslate.getY();
+
+ // The origin for scaling is the top left corner by default. A negative
+ // shadow offset changes the origin.
+ if (rShadow.getOffset().getX() < 0)
+ nTranslateX += aScale.getX();
+ if (rShadow.getOffset().getY() < 0)
+ nTranslateY += aScale.getY();
+
+ aShadowOffset.translate(-nTranslateX, -nTranslateY);
+ aShadowOffset.scale(rShadow.getSize().getX() * 0.00001, rShadow.getSize().getY() * 0.00001);
+ aShadowOffset.translate(nTranslateX, nTranslateY);
+ }
+
+ aShadowOffset.translate(rShadow.getOffset().getX(), rShadow.getOffset().getY());
+
+ // create shadow primitive and add content
+ const Primitive2DContainer& rContentForShadow
+ = pContentForShadow ? *pContentForShadow : rContent;
+ int nContentWithTransparence = std::count_if(
+ rContentForShadow.begin(), rContentForShadow.end(),
+ [](const Primitive2DReference& xChild) {
+ auto pChild = dynamic_cast<BufferedDecompositionPrimitive2D*>(xChild.get());
+ return pChild && pChild->getTransparenceForShadow() != 0;
+ });
+ if (nContentWithTransparence == 0)
+ {
+ Primitive2DContainer aRetval(2);
+ aRetval[0] = Primitive2DReference(
+ new ShadowPrimitive2D(
+ aShadowOffset,
+ rShadow.getColor(),
+ rShadow.getBlur(),
+ Primitive2DContainer(pContentForShadow ? *pContentForShadow : rContent)));
+
+ if (0.0 != rShadow.getTransparence())
+ {
+ // create SimpleTransparencePrimitive2D
+ Primitive2DContainer aTempContent{ aRetval[0] };
+
+ aRetval[0] = Primitive2DReference(
+ new UnifiedTransparencePrimitive2D(
+ std::move(aTempContent),
+ rShadow.getTransparence()));
+ }
+
+ aRetval[1] = Primitive2DReference(new GroupPrimitive2D(std::move(rContent)));
+ return aRetval;
+ }
+
+ Primitive2DContainer aRetval;
+ for (const auto& xChild : rContentForShadow)
+ {
+ double fChildTransparence = 0.0;
+ auto pChild = dynamic_cast<BufferedDecompositionPrimitive2D*>(xChild.get());
+ if (pChild)
+ {
+ fChildTransparence = pChild->getTransparenceForShadow();
+ fChildTransparence /= 100;
+ }
+ aRetval.push_back(Primitive2DReference(
+ new ShadowPrimitive2D(aShadowOffset, rShadow.getColor(), rShadow.getBlur(),
+ Primitive2DContainer({ xChild }))));
+ if (rShadow.getTransparence() != 0.0 || fChildTransparence != 0.0)
+ {
+ Primitive2DContainer aTempContent{ aRetval.back() };
+
+ double fChildAlpha = 1.0 - fChildTransparence;
+ double fShadowAlpha = 1.0 - rShadow.getTransparence();
+ double fTransparence = 1.0 - fChildAlpha * fShadowAlpha;
+ aRetval.back() = Primitive2DReference(new UnifiedTransparencePrimitive2D(
+ std::move(aTempContent), fTransparence));
+ }
+ }
+
+ aRetval.push_back(
+ Primitive2DReference(new GroupPrimitive2D(std::move(rContent))));
+ return aRetval;
+ }
+
+ Primitive2DContainer createEmbeddedGlowPrimitive(
+ Primitive2DContainer&& rContent,
+ const attribute::SdrGlowAttribute& rGlow)
+ {
+ if(rContent.empty())
+ return std::move(rContent);
+ Primitive2DContainer aRetval(2);
+ aRetval[0] = Primitive2DReference(
+ new GlowPrimitive2D(rGlow.getColor(), rGlow.getRadius(), Primitive2DContainer(rContent)));
+ aRetval[1] = Primitive2DReference(new GroupPrimitive2D(Primitive2DContainer(rContent)));
+ return aRetval;
+ }
+
+ Primitive2DContainer createEmbeddedSoftEdgePrimitive(Primitive2DContainer&& aContent,
+ sal_Int32 nRadius)
+ {
+ if (aContent.empty() || !nRadius)
+ return std::move(aContent);
+ Primitive2DContainer aRetval(1);
+ aRetval[0] = Primitive2DReference(new SoftEdgePrimitive2D(nRadius, std::move(aContent)));
+ return aRetval;
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */