/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for SlideBackgroundFillPrimitive2D #include #include #include #include #include using namespace com::sun::star; namespace drawinglayer::primitive2d { namespace { /// @returns the offset to apply/unapply to scale according to correct origin for a given alignment. basegfx::B2DTuple getShadowScaleOriginOffset(const basegfx::B2DTuple& aScale, model::RectangleAlignment eAlignment) { switch (eAlignment) { case model::RectangleAlignment::TopLeft: return { 0, 0 }; case model::RectangleAlignment::Top: return { aScale.getX() / 2, 0 }; case model::RectangleAlignment::TopRight: return { aScale.getX(), 0 }; case model::RectangleAlignment::Left: return { 0, aScale.getY() / 2 }; case model::RectangleAlignment::Center: return { aScale.getX() / 2, aScale.getY() / 2 }; case model::RectangleAlignment::Right: return { aScale.getX(), aScale.getY() / 2 }; case model::RectangleAlignment::BottomLeft: return { 0, aScale.getY() }; case model::RectangleAlignment::Bottom: return { aScale.getX() / 2, aScale.getY() }; case model::RectangleAlignment::BottomRight: return { aScale.getX(), aScale.getY() }; default: return { 0, 0 }; } }; // 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(rText.getTextLeftDistance()), fWidthForText); const double nTextRightDistance = std::min(static_cast(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(&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 continuation 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(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 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()); attribute::StrokeAttribute aStrokeAttribute(std::vector(rLine.getDotDashArray()), rLine.getFullDotDashLen()); rtl::Reference 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, std::move(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 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(), std::move(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(), std::move(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 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 aTranslate += getShadowScaleOriginOffset(aScale, rShadow.getAlignment()); aShadowOffset.translate(-aTranslate); aShadowOffset.scale(rShadow.getSize().getX() * 0.00001, rShadow.getSize().getY() * 0.00001); aShadowOffset.translate(aTranslate); } 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(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(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: */