Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/svx/source/svdraw/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 5.10.2025 mit Größe 124 kB image not shown  

Quelle  svdoashp.cxx   Sprache: C

 
/* -*- 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 <vcl/bitmap/BitmapShadowFilter.hxx>
#include <svx/svdoashp.hxx>
#include <svx/unoapi.hxx>
#include <com/sun/star/loader/CannotActivateFactoryException.hpp>
#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/drawing/XCustomShapeEngine.hpp>
#include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <com/sun/star/awt/Rectangle.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <comphelper/processfactory.hxx>
#include <comphelper/propertyvalue.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <com/sun/star/uno/Sequence.h>
#include <tools/helpers.hxx>
#include <svx/svddrag.hxx>
#include <svx/svddrgmt.hxx>
#include <svx/svdmodel.hxx>
#include <svx/svdpage.hxx>
#include <svx/svditer.hxx>
#include <svx/svdobj.hxx>
#include <svx/svdtrans.hxx>
#include <svx/dialmgr.hxx>
#include <svx/strings.hrc>
#include <editeng/eeitem.hxx>
#include <editeng/editstat.hxx>
#include <editeng/adjustitem.hxx>
#include <svx/svdoutl.hxx>
#include <editeng/outlobj.hxx>
#include <svx/sdtfchim.hxx>
#include <svx/EnhancedCustomShapeGeometry.hxx>
#include <svx/EnhancedCustomShapeTypeNames.hxx>
#include <svx/EnhancedCustomShape2d.hxx>
#include <com/sun/star/beans/PropertyValues.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeTextFrame.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp>
#include <editeng/writingmodeitem.hxx>
#include <svx/xlineit0.hxx>
#include <svx/xlnclit.hxx>
#include <sdr/properties/customshapeproperties.hxx>
#include <sdr/contact/viewcontactofsdrobjcustomshape.hxx>
#include <svx/xlntrit.hxx>
#include <svx/xfillit0.hxx>
#include <svx/xfltrit.hxx>
#include <svx/xflclit.hxx>
#include <svx/xflgrit.hxx>
#include <svx/xflhtit.hxx>
#include <svx/xbtmpit.hxx>
#include <vcl/virdev.hxx>
#include <svx/svdview.hxx>
#include <svx/sdmetitm.hxx>
#include <svx/sdprcitm.hxx>
#include <svx/sdshitm.hxx>
#include <svx/sdsxyitm.hxx>
#include <svx/sdtmfitm.hxx>
#include <svx/sdasitm.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/range/b2drange.hxx>
#include <svdobjplusdata.hxx>
#include <sal/log.hxx>
#include <o3tl/string_view.hxx>
#include "presetooxhandleadjustmentrelations.hxx"
#include <editeng/frmdiritem.hxx>

using namespace ::com::sun::star;

static void lcl_ShapeSegmentFromBinary( drawing::EnhancedCustomShapeSegment& rSegInfo, sal_uInt16 nSDat )
{
    switch( nSDat >> 8 )
    {
        case 0x00 :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::LINETO;
            rSegInfo.Count   = nSDat & 0xff;
            if ( !rSegInfo.Count )
                rSegInfo.Count = 1;
            break;
        case 0x20 :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::CURVETO;
            rSegInfo.Count   = nSDat & 0xff;
            if ( !rSegInfo.Count )
                rSegInfo.Count = 1;
            break;
        case 0x40 :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::MOVETO;
            rSegInfo.Count   = nSDat & 0xff;
            if ( !rSegInfo.Count )
                rSegInfo.Count = 1;
            break;
        case 0x60 :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::CLOSESUBPATH;
            rSegInfo.Count   = 0;
            break;
        case 0x80 :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ENDSUBPATH;
            rSegInfo.Count   = 0;
            break;
        case 0xa1 :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ANGLEELLIPSETO;
            rSegInfo.Count   = ( nSDat & 0xff ) / 3;
            break;
        case 0xa2 :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ANGLEELLIPSE;
            rSegInfo.Count   = ( nSDat & 0xff ) / 3;
            break;
        case 0xa3 :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ARCTO;
            rSegInfo.Count   = ( nSDat & 0xff ) >> 2;
            break;
        case 0xa4 :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ARC;
            rSegInfo.Count   = ( nSDat & 0xff ) >> 2;
            break;
        case 0xa5 :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARCTO;
            rSegInfo.Count   = ( nSDat & 0xff ) >> 2;
            break;
        case 0xa6 :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARC;
            rSegInfo.Count   = ( nSDat & 0xff ) >> 2;
            break;
        case 0xa7 :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ELLIPTICALQUADRANTX;
            rSegInfo.Count   = nSDat & 0xff;
            break;
        case 0xa8 :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ELLIPTICALQUADRANTY;
            rSegInfo.Count   = nSDat & 0xff;
            break;
        case 0xaa :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::NOFILL;
            rSegInfo.Count   = 0;
            break;
        case 0xab :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::NOSTROKE;
            rSegInfo.Count   = 0;
            break;
        default:
        case 0xf8 :
            rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::UNKNOWN;
            rSegInfo.Count   = nSDat;
            break;
    }
}

static MSO_SPT ImpGetCustomShapeType( const SdrObjCustomShape& rCustoShape )
{
    MSO_SPT eRetValue = mso_sptNil;

    OUString aEngine( rCustoShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_ENGINE ).GetValue() );
    if ( aEngine.isEmpty() || aEngine == "com.sun.star.drawing.EnhancedCustomShapeEngine" )
    {
        OUString sShapeType;
        const SdrCustomShapeGeometryItem& rGeometryItem( rCustoShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
        const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( u"Type"_ustr );
        if ( pAny && ( *pAny >>= sShapeType ) )
            eRetValue = EnhancedCustomShapeTypeNames::Get( sShapeType );
    }
    return eRetValue;
};

static bool ImpVerticalSwitch( const SdrObjCustomShape& rCustoShape )
{
    bool bRet = false;
    MSO_SPT eShapeType( ImpGetCustomShapeType( rCustoShape ) );
    switch( eShapeType )
    {
        case mso_sptAccentBorderCallout90 :     // 2 ortho
        case mso_sptBorderCallout1 :            // 2 diag
        case mso_sptBorderCallout2 :            // 3
        {
            bRet = true;
        }
        break;
        defaultbreak;
    }
    return bRet;
}

// #i37011# create a clone with all attributes changed to shadow attributes
// and translation executed, too.
static rtl::Reference<SdrObject> ImpCreateShadowObjectClone(const SdrObject& rOriginal, const SfxItemSet& rOriginalSet)
{
    rtl::Reference<SdrObject> pRetval;
    const bool bShadow(rOriginalSet.Get(SDRATTR_SHADOW).GetValue());

    if(bShadow)
    {
        // create a shadow representing object
        const sal_Int32 nXDist(rOriginalSet.Get(SDRATTR_SHADOWXDIST).GetValue());
        const sal_Int32 nYDist(rOriginalSet.Get(SDRATTR_SHADOWYDIST).GetValue());
        const ::Color aShadowColor(rOriginalSet.Get(SDRATTR_SHADOWCOLOR).GetColorValue());
        const sal_uInt16 nShadowTransparence(rOriginalSet.Get(SDRATTR_SHADOWTRANSPARENCE).GetValue());
        pRetval = rOriginal.CloneSdrObject(rOriginal.getSdrModelFromSdrObject());
        DBG_ASSERT(pRetval, "ImpCreateShadowObjectClone: Could not clone object (!)");

        // look for used stuff
        SdrObjListIter aIterator(rOriginal);
        bool bLineUsed(false);
        bool bAllFillUsed(false);
        bool bSolidFillUsed(false);
        bool bGradientFillUsed(false);
        bool bHatchFillUsed(false);
        bool bBitmapFillUsed(false);

        while(aIterator.IsMore())
        {
            SdrObject* pObj = aIterator.Next();
            drawing::FillStyle eFillStyle = pObj->GetMergedItem(XATTR_FILLSTYLE).GetValue();

            if(!bLineUsed)
            {
                drawing::LineStyle eLineStyle = pObj->GetMergedItem(XATTR_LINESTYLE).GetValue();

                if(drawing::LineStyle_NONE != eLineStyle)
                {
                    bLineUsed = true;
                }
            }

            if(!bAllFillUsed)
            {
                if(!bSolidFillUsed && drawing::FillStyle_SOLID == eFillStyle)
                {
                    bSolidFillUsed = true;
                    bAllFillUsed = (bSolidFillUsed && bGradientFillUsed && bHatchFillUsed && bBitmapFillUsed);
                }
                if(!bGradientFillUsed && drawing::FillStyle_GRADIENT == eFillStyle)
                {
                    bGradientFillUsed = true;
                    bAllFillUsed = (bSolidFillUsed && bGradientFillUsed && bHatchFillUsed && bBitmapFillUsed);
                }
                if(!bHatchFillUsed && drawing::FillStyle_HATCH == eFillStyle)
                {
                    bHatchFillUsed = true;
                    bAllFillUsed = (bSolidFillUsed && bGradientFillUsed && bHatchFillUsed && bBitmapFillUsed);
                }
                if(!bBitmapFillUsed && drawing::FillStyle_BITMAP == eFillStyle)
                {
                    bBitmapFillUsed = true;
                    bAllFillUsed = (bSolidFillUsed && bGradientFillUsed && bHatchFillUsed && bBitmapFillUsed);
                }
            }
        }

        // translate to shadow coordinates
        pRetval->NbcMove(Size(nXDist, nYDist));

        // set items as needed
        SfxItemSet aTempSet(rOriginalSet);

        // if a SvxWritingModeItem (Top->Bottom) is set the text object
        // is creating a paraobject, but paraobjects can not be created without model. So
        // we are preventing the crash by setting the writing mode always left to right,
        // this is not bad since our shadow geometry does not contain text.
        aTempSet.Put(SvxWritingModeItem(text::WritingMode_LR_TB, SDRATTR_TEXTDIRECTION));

        // no shadow
        aTempSet.Put(makeSdrShadowItem(false));
        aTempSet.Put(makeSdrShadowXDistItem(0));
        aTempSet.Put(makeSdrShadowYDistItem(0));

        // line color and transparency like shadow
        if(bLineUsed)
        {
            aTempSet.Put(XLineColorItem(OUString(), aShadowColor));
            aTempSet.Put(XLineTransparenceItem(nShadowTransparence));
        }

        // fill color and transparency like shadow
        if(bSolidFillUsed)
        {
            aTempSet.Put(XFillColorItem(OUString(), aShadowColor));
            aTempSet.Put(XFillTransparenceItem(nShadowTransparence));
        }

        // gradient and transparency like shadow
        if(bGradientFillUsed)
        {
            basegfx::BGradient aGradient(rOriginalSet.Get(XATTR_FILLGRADIENT).GetGradientValue());
            sal_uInt8 nStartLuminance(Color(aGradient.GetColorStops().front().getStopColor()).GetLuminance());
            sal_uInt8 nEndLuminance(Color(aGradient.GetColorStops().back().getStopColor()).GetLuminance());

            if(aGradient.GetStartIntens() != 100)
            {
                nStartLuminance = static_cast<sal_uInt8>(nStartLuminance * (static_cast<double>(aGradient.GetStartIntens()) / 100.0));
            }

            if(aGradient.GetEndIntens() != 100)
            {
                nEndLuminance = static_cast<sal_uInt8>(nEndLuminance * (static_cast<double>(aGradient.GetEndIntens()) / 100.0));
            }

            ::Color aStartColor(
                static_cast<sal_uInt8>((nStartLuminance * aShadowColor.GetRed()) / 256),
                static_cast<sal_uInt8>((nStartLuminance * aShadowColor.GetGreen()) / 256),
                static_cast<sal_uInt8>((nStartLuminance * aShadowColor.GetBlue()) / 256));

            ::Color aEndColor(
                static_cast<sal_uInt8>((nEndLuminance * aShadowColor.GetRed()) / 256),
                static_cast<sal_uInt8>((nEndLuminance * aShadowColor.GetGreen()) / 256),
                static_cast<sal_uInt8>((nEndLuminance * aShadowColor.GetBlue()) / 256));

            aGradient.SetColorStops(
                basegfx::BColorStops(
                    aStartColor.getBColor(),
                    aEndColor.getBColor()));
            aTempSet.Put(XFillGradientItem(aGradient));
            aTempSet.Put(XFillTransparenceItem(nShadowTransparence));
        }

        // hatch and transparency like shadow
        if(bHatchFillUsed)
        {
            XHatch aHatch(rOriginalSet.Get(XATTR_FILLHATCH).GetHatchValue());
            aHatch.SetColor(aShadowColor);
            aTempSet.Put(XFillHatchItem(aHatch));
            aTempSet.Put(XFillTransparenceItem(nShadowTransparence));
        }

        // bitmap and transparency like shadow
        if(bBitmapFillUsed)
        {
            GraphicObject aGraphicObject(rOriginalSet.Get(XATTR_FILLBITMAP).GetGraphicObject());
            BitmapEx aBitmapEx(aGraphicObject.GetGraphic().GetBitmapEx());

            if(!aBitmapEx.IsEmpty())
            {
                ScopedVclPtr<VirtualDevice> pVirDev(VclPtr<VirtualDevice>::Create());
                pVirDev->SetOutputSizePixel(aBitmapEx.GetSizePixel());
                BitmapFilter::Filter(aBitmapEx, BitmapShadowFilter(aShadowColor));
                pVirDev->DrawBitmapEx(Point(), aBitmapEx);
                aGraphicObject.SetGraphic(Graphic(pVirDev->GetBitmapEx(Point(0,0), aBitmapEx.GetSizePixel())));
            }

            aTempSet.Put(XFillBitmapItem(aGraphicObject));
            aTempSet.Put(XFillTransparenceItem(nShadowTransparence));
        }

        // set attributes and paint shadow object
        pRetval->SetMergedItemSet( aTempSet );
    }
    return pRetval;
}


uno::Reference<drawing::XCustomShapeEngine> const & SdrObjCustomShape::GetCustomShapeEngine() const
{
    if (mxCustomShapeEngine.is())
        return mxCustomShapeEngine;

    uno::Reference<drawing::XShape> aXShape = GetXShapeForSdrObject(const_cast<SdrObjCustomShape*>(this));
    if ( !aXShape )
        return mxCustomShapeEngine;

    const uno::Reference<uno::XComponentContext>& xContext( ::comphelper::getProcessComponentContext() );

    OUString aEngine(GetMergedItem( SDRATTR_CUSTOMSHAPE_ENGINE ).GetValue());
    static constexpr OUStringLiteral sEnhancedCustomShapeEngine = u"com.sun.star.drawing.EnhancedCustomShapeEngine";
    if ( aEngine.isEmpty() )
        aEngine = sEnhancedCustomShapeEngine;

    {
        static constexpr OUString sCustomShape = u"CustomShape"_ustr;
        uno::Sequence<beans::PropertyValue> aPropValues{ comphelper::makePropertyValue(sCustomShape,
                                                                             aXShape) };
        uno::Sequence<uno::Any> aArgument{ uno::Any(aPropValues) };
        try
        {
            uno::Reference<uno::XInterface> xInterface(xContext->getServiceManager()->createInstanceWithArgumentsAndContext(aEngine, aArgument, xContext));
            if (xInterface.is())
                mxCustomShapeEngine.set(xInterface, uno::UNO_QUERY);
        }
        catch (const loader::CannotActivateFactoryException&)
        {
        }
    }

    return mxCustomShapeEngine;
}

const SdrObject* SdrObjCustomShape::GetSdrObjectFromCustomShape() const
{
    if ( !mXRenderedCustomShape.is() )
    {
        uno::Reference<drawing::XCustomShapeEngine> xCustomShapeEngine( GetCustomShapeEngine() );
        if ( xCustomShapeEngine.is() )
            const_cast<SdrObjCustomShape*>(this)->mXRenderedCustomShape = xCustomShapeEngine->render();
    }
    SdrObject* pRenderedCustomShape = mXRenderedCustomShape.is()
                ? SdrObject::getSdrObjectFromXShape( mXRenderedCustomShape )
                : nullptr;
    return pRenderedCustomShape;
}

// #i37011# Shadow geometry creation
const SdrObject* SdrObjCustomShape::GetSdrObjectShadowFromCustomShape() const
{
    if(!mpLastShadowGeometry)
    {
        const SdrObject* pSdrObject = GetSdrObjectFromCustomShape();
        if(pSdrObject)
        {
            const SfxItemSet& rOriginalSet = GetObjectItemSet();
            const bool bShadow(rOriginalSet.Get( SDRATTR_SHADOW ).GetValue());

            if(bShadow)
            {
                // create a clone with all attributes changed to shadow attributes
                // and translation executed, too.
                const_cast<SdrObjCustomShape*>(this)->mpLastShadowGeometry =
                    ImpCreateShadowObjectClone(*pSdrObject, rOriginalSet);
            }
        }
    }

    return mpLastShadowGeometry.get();
}

bool SdrObjCustomShape::IsTextPath() const
{
    static constexpr OUString sTextPath( u"TextPath"_ustr );
    bool bTextPathOn = false;
    const SdrCustomShapeGeometryItem& rGeometryItem = GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY );
    const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( sTextPath, sTextPath );
    if ( pAny )
        *pAny >>= bTextPathOn;
    return bTextPathOn;
}

bool SdrObjCustomShape::UseNoFillStyle() const
{
    bool bRet = false;
    OUString sShapeType;
    static constexpr OUString sType( u"Type"_ustr );
    const SdrCustomShapeGeometryItem& rGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
    const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( sType );
    if ( pAny )
        *pAny >>= sShapeType;
    bRet = !IsCustomShapeFilledByDefault( EnhancedCustomShapeTypeNames::Get( sType ) );

    return bRet;
}

bool SdrObjCustomShape::IsMirroredX() const
{
    bool bMirroredX = false;
    const SdrCustomShapeGeometryItem & rGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
    const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( u"MirroredX"_ustr );
    if ( pAny )
        *pAny >>= bMirroredX;
    return bMirroredX;
}
bool SdrObjCustomShape::IsMirroredY() const
{
    bool bMirroredY = false;
    const SdrCustomShapeGeometryItem & rGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
    const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( u"MirroredY"_ustr );
    if ( pAny )
        *pAny >>= bMirroredY;
    return bMirroredY;
}
void SdrObjCustomShape::SetMirroredX( const bool bMirrorX )
{
    SdrCustomShapeGeometryItem aGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
    beans::PropertyValue aPropVal;
    aPropVal.Name = "MirroredX";
    aPropVal.Value <<= bMirrorX;
    aGeometryItem.SetPropertyValue( aPropVal );
    SetMergedItem( aGeometryItem );
}
void SdrObjCustomShape::SetMirroredY( const bool bMirrorY )
{
    SdrCustomShapeGeometryItem aGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
    beans::PropertyValue aPropVal;
    aPropVal.Name = "MirroredY";
    aPropVal.Value <<= bMirrorY;
    aGeometryItem.SetPropertyValue( aPropVal );
    SetMergedItem( aGeometryItem );
}

double SdrObjCustomShape::GetExtraTextRotation( const bool bPreRotation ) const
{
    double fExtraTextRotateAngle = 0.0;
    if (bPreRotation)
    {
        // textPreRotateAngle might be set by macro or diagram (SmartArt) import
        const uno::Any* pAny;
        const SdrCustomShapeGeometryItem& rGeometryItem = GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY );
        pAny = rGeometryItem.GetPropertyValueByName(u"TextPreRotateAngle"_ustr);
        if ( pAny )
            *pAny >>= fExtraTextRotateAngle;

        // As long as the edit engine is not able to render these text directions we
        // emulate them by setting a suitable text pre-rotation.
        const SvxFrameDirectionItem& rDirectionItem = GetMergedItem(SDRATTR_WRITINGMODE2);
        if (rDirectionItem.GetValue() == SvxFrameDirection::Vertical_RL_TB90)
            fExtraTextRotateAngle -= 90;
        else if (rDirectionItem.GetValue() == SvxFrameDirection::Vertical_LR_BT)
            fExtraTextRotateAngle -=270;
    }
    else
    {
        const uno::Any* pAny;
        const SdrCustomShapeGeometryItem& rGeometryItem = GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY );
        pAny = rGeometryItem.GetPropertyValueByName(u"TextRotateAngle"_ustr);
        if ( pAny )
            *pAny >>= fExtraTextRotateAngle;
    }
    return fExtraTextRotateAngle;
}

bool SdrObjCustomShape::GetTextBounds( tools::Rectangle& rTextBound ) const
{
    bool bRet = false;

    uno::Reference<drawing::XCustomShapeEngine> xCustomShapeEngine( GetCustomShapeEngine() );
    if ( xCustomShapeEngine.is() )
    {
        awt::Rectangle aR( xCustomShapeEngine->getTextBounds() );
        if ( aR.Width > 1 && aR.Height > 1 )
        {
            rTextBound = tools::Rectangle( Point( aR.X, aR.Y ), Size( aR.Width, aR.Height ) );
            bRet = true;
        }
    }
    return bRet;
}
basegfx::B2DPolyPolygon SdrObjCustomShape::GetLineGeometry( const bool bBezierAllowed ) const
{
    basegfx::B2DPolyPolygon aRetval;
    uno::Reference<drawing::XCustomShapeEngine> xCustomShapeEngine( GetCustomShapeEngine() );
    if ( xCustomShapeEngine.is() )
    {
        drawing::PolyPolygonBezierCoords aBezierCoords = xCustomShapeEngine->getLineGeometry();
        try
        {
            aRetval = basegfx::utils::UnoPolyPolygonBezierCoordsToB2DPolyPolygon( aBezierCoords );
            if ( !bBezierAllowed && aRetval.areControlPointsUsed())
            {
                aRetval = basegfx::utils::adaptiveSubdivideByAngle(aRetval);
            }
        }
        catch ( const lang::IllegalArgumentException & )
        {
        }
    }
    return aRetval;
}

std::vector< SdrCustomShapeInteraction > SdrObjCustomShape::GetInteractionHandles() const
{
    std::vector< SdrCustomShapeInteraction > aRet;
    try
    {
        uno::Reference<drawing::XCustomShapeEngine> xCustomShapeEngine( GetCustomShapeEngine() );
        if ( xCustomShapeEngine.is() )
        {
            int i;
            uno::Sequence<uno::Reference<drawing::XCustomShapeHandle>> xInteractionHandles( xCustomShapeEngine->getInteraction() );
            for ( i = 0; i < xInteractionHandles.getLength(); i++ )
            {
                if ( xInteractionHandles[ i ].is() )
                {
                    SdrCustomShapeInteraction aSdrCustomShapeInteraction;
                    aSdrCustomShapeInteraction.xInteraction = xInteractionHandles[ i ];
                    aSdrCustomShapeInteraction.aPosition = xInteractionHandles[ i ]->getPosition();

                    CustomShapeHandleModes nMode = CustomShapeHandleModes::NONE;
                    switch( ImpGetCustomShapeType( *this ) )
                    {
                        case mso_sptAccentBorderCallout90 :     // 2 ortho
                        {
                            if (i == 0)
                                nMode |= CustomShapeHandleModes::RESIZE_FIXED | CustomShapeHandleModes::CREATE_FIXED;
                            else if (i == 1)
                                nMode |= CustomShapeHandleModes::RESIZE_ABSOLUTE_X | CustomShapeHandleModes::RESIZE_ABSOLUTE_Y | CustomShapeHandleModes::MOVE_SHAPE | CustomShapeHandleModes::ORTHO4;
                        }
                        break;

                        case mso_sptChevron :
                        case mso_sptHomePlate :
                             nMode |= CustomShapeHandleModes::RESIZE_ABSOLUTE_NEGX;
                        break;

                        case mso_sptWedgeRectCallout :
                        case mso_sptWedgeRRectCallout :
                        case mso_sptCloudCallout :
                        case mso_sptWedgeEllipseCallout :
                        {
                            if (i == 0)
                                nMode |= CustomShapeHandleModes::RESIZE_FIXED;
                        }
                        break;

                        case mso_sptBorderCallout1 :            // 2 diag
                        {
                            if (i == 0)
                                nMode |= CustomShapeHandleModes::RESIZE_FIXED | CustomShapeHandleModes::CREATE_FIXED;
                            else if (i == 1)
                                nMode |= CustomShapeHandleModes::RESIZE_ABSOLUTE_X | CustomShapeHandleModes::RESIZE_ABSOLUTE_Y | CustomShapeHandleModes::MOVE_SHAPE;
                        }
                        break;
                        case mso_sptBorderCallout2 :            // 3
                        {
                            if (i == 0)
                                nMode |= CustomShapeHandleModes::RESIZE_FIXED | CustomShapeHandleModes::CREATE_FIXED;
                            else if (i == 2)
                                nMode |= CustomShapeHandleModes::RESIZE_ABSOLUTE_X | CustomShapeHandleModes::RESIZE_ABSOLUTE_Y | CustomShapeHandleModes::MOVE_SHAPE;
                        }
                        break;
                        case mso_sptCallout90 :
                        case mso_sptAccentCallout90 :
                        case mso_sptBorderCallout90 :
                        case mso_sptCallout1 :
                        case mso_sptCallout2 :
                        case mso_sptCallout3 :
                        case mso_sptAccentCallout1 :
                        case mso_sptAccentCallout2 :
                        case mso_sptAccentCallout3 :
                        case mso_sptBorderCallout3 :
                        case mso_sptAccentBorderCallout1 :
                        case mso_sptAccentBorderCallout2 :
                        case mso_sptAccentBorderCallout3 :
                        {
                            if (i == 0)
                                nMode |= CustomShapeHandleModes::RESIZE_FIXED | CustomShapeHandleModes::CREATE_FIXED;
                        }
                        break;
                        defaultbreak;
                    }
                    aSdrCustomShapeInteraction.nMode = nMode;
                    aRet.push_back( aSdrCustomShapeInteraction );
                }
            }
        }
    }
    catchconst uno::RuntimeException& )
    {
    }
    return aRet;
}


// BaseProperties section
#define DEFAULT_MINIMUM_SIGNED_COMPARE  (sal_Int32(0x80000000))
#define DEFAULT_MAXIMUM_SIGNED_COMPARE  (sal_Int32(0x7fffffff))

static sal_Int32 GetNumberOfProperties ( const SvxMSDffHandle* pData )
{
    sal_Int32           nPropertiesNeeded=1;  // position is always needed
    SvxMSDffHandleFlags nFlags = pData->nFlags;

    if ( nFlags & SvxMSDffHandleFlags::MIRRORED_X )
        nPropertiesNeeded++;
    if ( nFlags & SvxMSDffHandleFlags::MIRRORED_Y )
        nPropertiesNeeded++;
    if ( nFlags & SvxMSDffHandleFlags::SWITCHED )
        nPropertiesNeeded++;
    if ( nFlags & SvxMSDffHandleFlags::POLAR )
    {
        nPropertiesNeeded++;
        if ( nFlags & SvxMSDffHandleFlags::RADIUS_RANGE )
        {
            if ( pData->nRangeXMin != DEFAULT_MINIMUM_SIGNED_COMPARE )
                nPropertiesNeeded++;
            if ( pData->nRangeXMax != DEFAULT_MAXIMUM_SIGNED_COMPARE )
                nPropertiesNeeded++;
        }
    }
    else if ( nFlags & SvxMSDffHandleFlags::RANGE )
    {
        if ( pData->nRangeXMin != DEFAULT_MINIMUM_SIGNED_COMPARE )
            nPropertiesNeeded++;
        if ( pData->nRangeXMax != DEFAULT_MAXIMUM_SIGNED_COMPARE )
            nPropertiesNeeded++;
        if ( pData->nRangeYMin != DEFAULT_MINIMUM_SIGNED_COMPARE )
            nPropertiesNeeded++;
        if ( pData->nRangeYMax != DEFAULT_MAXIMUM_SIGNED_COMPARE )
            nPropertiesNeeded++;
    }

    return nPropertiesNeeded;
}

static void lcl_ShapePropertiesFromDFF( const SvxMSDffHandle* pData, beans::PropertyValues& rPropValues )
{
    SvxMSDffHandleFlags nFlags = pData->nFlags;
    sal_Int32 n=0;
    auto pPropValues = rPropValues.getArray();

    // POSITION
    {
        drawing::EnhancedCustomShapeParameterPair aPosition;
        EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aPosition.First, pData->nPositionX, truetrue );
        EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aPosition.Second, pData->nPositionY, truefalse );
        pPropValues[ n ].Name = "Position";
        pPropValues[ n++ ].Value <<= aPosition;
    }
    if ( nFlags & SvxMSDffHandleFlags::MIRRORED_X )
    {
        pPropValues[ n ].Name = "MirroredX";
        pPropValues[ n++ ].Value <<= true;
    }
    if ( nFlags & SvxMSDffHandleFlags::MIRRORED_Y )
    {
        pPropValues[ n ].Name = "MirroredY";
        pPropValues[ n++ ].Value <<= true;
    }
    if ( nFlags & SvxMSDffHandleFlags::SWITCHED )
    {
        pPropValues[ n ].Name = "Switched";
        pPropValues[ n++ ].Value <<= true;
    }
    if ( nFlags & SvxMSDffHandleFlags::POLAR )
    {
        drawing::EnhancedCustomShapeParameterPair aCenter;
        EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aCenter.First, pData->nCenterX,
                           bool( nFlags & SvxMSDffHandleFlags::CENTER_X_IS_SPECIAL ), true  );
        EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aCenter.Second, pData->nCenterY,
                           bool( nFlags & SvxMSDffHandleFlags::CENTER_Y_IS_SPECIAL ), false );
        pPropValues[ n ].Name = "Polar";
        pPropValues[ n++ ].Value <<= aCenter;
        if ( nFlags & SvxMSDffHandleFlags::RADIUS_RANGE )
        {
            if ( pData->nRangeXMin != DEFAULT_MINIMUM_SIGNED_COMPARE )
            {
                drawing::EnhancedCustomShapeParameter aRadiusRangeMinimum;
                EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRadiusRangeMinimum, pData->nRangeXMin,
                           bool( nFlags & SvxMSDffHandleFlags::RANGE_X_MIN_IS_SPECIAL ), true  );
                pPropValues[ n ].Name = "RadiusRangeMinimum";
                pPropValues[ n++ ].Value <<= aRadiusRangeMinimum;
            }
            if ( pData->nRangeXMax != DEFAULT_MAXIMUM_SIGNED_COMPARE )
            {
                drawing::EnhancedCustomShapeParameter aRadiusRangeMaximum;
                EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRadiusRangeMaximum, pData->nRangeXMax,
                           bool( nFlags & SvxMSDffHandleFlags::RANGE_X_MAX_IS_SPECIAL ), false );
                pPropValues[ n ].Name = "RadiusRangeMaximum";
                pPropValues[ n++ ].Value <<= aRadiusRangeMaximum;
            }
        }
    }
    else if ( nFlags & SvxMSDffHandleFlags::RANGE )
    {
        if ( pData->nRangeXMin != DEFAULT_MINIMUM_SIGNED_COMPARE )
        {
            drawing::EnhancedCustomShapeParameter aRangeXMinimum;
            EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRangeXMinimum, pData->nRangeXMin,
                           bool( nFlags & SvxMSDffHandleFlags::RANGE_X_MIN_IS_SPECIAL ), true  );
            pPropValues[ n ].Name = "RangeXMinimum";
            pPropValues[ n++ ].Value <<= aRangeXMinimum;
        }
        if ( pData->nRangeXMax != DEFAULT_MAXIMUM_SIGNED_COMPARE )
        {
            drawing::EnhancedCustomShapeParameter aRangeXMaximum;
            EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRangeXMaximum, pData->nRangeXMax,
                           bool( nFlags & SvxMSDffHandleFlags::RANGE_X_MAX_IS_SPECIAL ), false );
            pPropValues[ n ].Name = "RangeXMaximum";
            pPropValues[ n++ ].Value <<= aRangeXMaximum;
        }
        if ( pData->nRangeYMin != DEFAULT_MINIMUM_SIGNED_COMPARE )
        {
            drawing::EnhancedCustomShapeParameter aRangeYMinimum;
            EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRangeYMinimum, pData->nRangeYMin,
                             bool( nFlags & SvxMSDffHandleFlags::RANGE_Y_MIN_IS_SPECIAL ), true );
            pPropValues[ n ].Name = "RangeYMinimum";
            pPropValues[ n++ ].Value <<= aRangeYMinimum;
        }
        if ( pData->nRangeYMax != DEFAULT_MAXIMUM_SIGNED_COMPARE )
        {
            drawing::EnhancedCustomShapeParameter aRangeYMaximum;
            EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRangeYMaximum, pData->nRangeYMax,
                             bool( nFlags & SvxMSDffHandleFlags::RANGE_Y_MAX_IS_SPECIAL ), false );
            pPropValues[ n ].Name = "RangeYMaximum";
            pPropValues[ n++ ].Value <<= aRangeYMaximum;
        }
    }
}

std::unique_ptr<sdr::properties::BaseProperties> SdrObjCustomShape::CreateObjectSpecificProperties()
{
    return std::make_unique<sdr::properties::CustomShapeProperties>(*this);
}

SdrObjCustomShape::SdrObjCustomShape(SdrModel& rSdrModel)
:   SdrTextObj(rSdrModel)
    , m_fObjectRotation(0.0)
    , mbAdjustingTextFrameWidthAndHeight(false)
{
    m_bClosedObj = true// custom shapes may be filled
    mbTextFrame = true;
}

SdrObjCustomShape::SdrObjCustomShape(SdrModel& rSdrModel, SdrObjCustomShape const & rSource)
:   SdrTextObj(rSdrModel, rSource)
    , m_fObjectRotation(0.0)
    , mbAdjustingTextFrameWidthAndHeight(false)
{
    m_bClosedObj = true// custom shapes may be filled
    mbTextFrame = true;

    m_fObjectRotation = rSource.m_fObjectRotation;
    mbAdjustingTextFrameWidthAndHeight = rSource.mbAdjustingTextFrameWidthAndHeight;
    assert(!mbAdjustingTextFrameWidthAndHeight);
    InvalidateRenderGeometry();
}

SdrObjCustomShape::~SdrObjCustomShape()
{
    // delete buffered display geometry
    InvalidateRenderGeometry();
}

void SdrObjCustomShape::MergeDefaultAttributes( const OUString* pType )
{
    beans::PropertyValue aPropVal;
    OUString sShapeType;
    static constexpr OUString sType( u"Type"_ustr );
    SdrCustomShapeGeometryItem aGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
    if ( pType && !pType->isEmpty() )
    {
        sal_Int32 nType = pType->toInt32();
        if ( nType )
            sShapeType = EnhancedCustomShapeTypeNames::Get( static_cast< MSO_SPT >( nType ) );
        else
            sShapeType = *pType;

        aPropVal.Name = sType;
        aPropVal.Value <<= sShapeType;
        aGeometryItem.SetPropertyValue( aPropVal );
    }
    else
    {
        uno::Any *pAny = aGeometryItem.GetPropertyValueByName( sType );
        if ( pAny )
            *pAny >>= sShapeType;
    }
    MSO_SPT eSpType = EnhancedCustomShapeTypeNames::Get( sShapeType );

    const sal_Int32* pDefData = nullptr;
    const mso_CustomShape* pDefCustomShape = GetCustomShapeContent( eSpType );
    if ( pDefCustomShape )
        pDefData = pDefCustomShape->pDefData;

    uno::Sequence<drawing::EnhancedCustomShapeAdjustmentValue> seqAdjustmentValues;


    // AdjustmentValues

    static constexpr OUString sAdjustmentValues( u"AdjustmentValues"_ustr );
    const uno::Any* pAny = aGeometryItem.GetPropertyValueByName( sAdjustmentValues );
    if ( pAny )
        *pAny >>= seqAdjustmentValues;
    if ( pDefCustomShape && pDefData )  // now check if we have to default some adjustment values
    {
        // first check if there are adjustment values are to be appended
        sal_Int32 i, nAdjustmentValues = seqAdjustmentValues.getLength();
        sal_Int32 nAdjustmentDefaults = *pDefData++;
        if ( nAdjustmentDefaults > nAdjustmentValues )
            seqAdjustmentValues.realloc( nAdjustmentDefaults );
        auto pseqAdjustmentValues = seqAdjustmentValues.getArray();
        for ( i = nAdjustmentValues; i < nAdjustmentDefaults; i++ )
        {
            pseqAdjustmentValues[ i ].Value <<= pDefData[ i ];
            pseqAdjustmentValues[ i ].State = beans::PropertyState_DIRECT_VALUE;
        }
        // check if there are defaulted adjustment values that should be filled the hard coded defaults (pDefValue)
        sal_Int32 nCount = std::min(nAdjustmentValues, nAdjustmentDefaults);
        for ( i = 0; i < nCount; i++ )
        {
            if ( seqAdjustmentValues[ i ].State != beans::PropertyState_DIRECT_VALUE )
            {
                pseqAdjustmentValues[ i ].Value <<= pDefData[ i ];
                pseqAdjustmentValues[ i ].State = beans::PropertyState_DIRECT_VALUE;
            }
        }
    }
    aPropVal.Name = sAdjustmentValues;
    aPropVal.Value <<= seqAdjustmentValues;
    aGeometryItem.SetPropertyValue( aPropVal );


    // Coordsize

    static constexpr OUString sViewBox( u"ViewBox"_ustr );
    const uno::Any* pViewBox = aGeometryItem.GetPropertyValueByName( sViewBox );
    awt::Rectangle aViewBox;
    if ( !pViewBox || !(*pViewBox >>= aViewBox ) )
    {
        if ( pDefCustomShape )
        {
            aViewBox.X = 0;
            aViewBox.Y = 0;
            aViewBox.Width = pDefCustomShape->nCoordWidth;
            aViewBox.Height= pDefCustomShape->nCoordHeight;
            aPropVal.Name = sViewBox;
            aPropVal.Value <<= aViewBox;
            aGeometryItem.SetPropertyValue( aPropVal );
        }
    }

    static constexpr OUString sPath( u"Path"_ustr );


    // Path/Coordinates

    static constexpr OUString sCoordinates( u"Coordinates"_ustr );
    pAny = aGeometryItem.GetPropertyValueByName( sPath, sCoordinates );
    if (!pAny && pDefCustomShape && !pDefCustomShape->pVertices.empty())
    {
        sal_Int32 i, nCount = pDefCustomShape->pVertices.size();
        uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqCoordinates( nCount );
        auto pseqCoordinates = seqCoordinates.getArray();
        for ( i = 0; i < nCount; i++ )
        {
            EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqCoordinates[ i ].First, pDefCustomShape->pVertices[ i ].nValA );
            EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqCoordinates[ i ].Second, pDefCustomShape->pVertices[ i ].nValB );
        }
        aPropVal.Name = sCoordinates;
        aPropVal.Value <<= seqCoordinates;
        aGeometryItem.SetPropertyValue( sPath, aPropVal );
    }

    // Path/GluePoints
    static constexpr OUString sGluePoints( u"GluePoints"_ustr );
    pAny = aGeometryItem.GetPropertyValueByName( sPath, sGluePoints );
    if (!pAny && pDefCustomShape && !pDefCustomShape->pGluePoints.empty())
    {
        sal_Int32 i, nCount = pDefCustomShape->pGluePoints.size();
        uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqGluePoints( nCount );
        auto pseqGluePoints = seqGluePoints.getArray();
        for ( i = 0; i < nCount; i++ )
        {
            EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqGluePoints[ i ].First, pDefCustomShape->pGluePoints[ i ].nValA );
            EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqGluePoints[ i ].Second, pDefCustomShape->pGluePoints[ i ].nValB );
        }
        aPropVal.Name = sGluePoints;
        aPropVal.Value <<= seqGluePoints;
        aGeometryItem.SetPropertyValue( sPath, aPropVal );
    }

    // Path/Segments
    static constexpr OUString sSegments( u"Segments"_ustr );
    pAny = aGeometryItem.GetPropertyValueByName( sPath, sSegments );
    if ( !pAny && pDefCustomShape && pDefCustomShape->nElements && pDefCustomShape->pElements )
    {
        sal_Int32 i, nCount = pDefCustomShape->nElements;
        uno::Sequence<drawing::EnhancedCustomShapeSegment> seqSegments( nCount );
        auto pseqSegments = seqSegments.getArray();
        for ( i = 0; i < nCount; i++ )
        {
            drawing::EnhancedCustomShapeSegment& rSegInfo = pseqSegments[ i ];
            sal_uInt16 nSDat = pDefCustomShape->pElements[ i ];
            lcl_ShapeSegmentFromBinary( rSegInfo, nSDat );
        }
        aPropVal.Name = sSegments;
        aPropVal.Value <<= seqSegments;
        aGeometryItem.SetPropertyValue( sPath, aPropVal );
    }

    // Path/StretchX
    static constexpr OUString sStretchX( u"StretchX"_ustr );
    pAny = aGeometryItem.GetPropertyValueByName( sPath, sStretchX );
    if ( !pAny && pDefCustomShape )
    {
        sal_Int32 nXRef = pDefCustomShape->nXRef;
        if ( nXRef != DEFAULT_MINIMUM_SIGNED_COMPARE )
        {
            aPropVal.Name = sStretchX;
            aPropVal.Value <<= nXRef;
            aGeometryItem.SetPropertyValue( sPath, aPropVal );
        }
    }

    // Path/StretchY
    static constexpr OUString sStretchY( u"StretchY"_ustr );
    pAny = aGeometryItem.GetPropertyValueByName( sPath, sStretchY );
    if ( !pAny && pDefCustomShape )
    {
        sal_Int32 nYRef = pDefCustomShape->nYRef;
        if ( nYRef != DEFAULT_MINIMUM_SIGNED_COMPARE )
        {
            aPropVal.Name = sStretchY;
            aPropVal.Value <<= nYRef;
            aGeometryItem.SetPropertyValue( sPath, aPropVal );
        }
    }

    // Path/TextFrames
    static constexpr OUString sTextFrames( u"TextFrames"_ustr );
    pAny = aGeometryItem.GetPropertyValueByName( sPath, sTextFrames );
    if (!pAny && pDefCustomShape && !pDefCustomShape->pTextRect.empty())
    {
        sal_Int32 i, nCount = pDefCustomShape->pTextRect.size();
        uno::Sequence<drawing::EnhancedCustomShapeTextFrame> seqTextFrames( nCount );
        auto pseqTextFrames = seqTextFrames.getArray();
        for (i = 0; i < nCount; i++)
        {
            const SvxMSDffTextRectangles* pRectangles = &pDefCustomShape->pTextRect[i];
            EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames[ i ].TopLeft.First,     pRectangles->nPairA.nValA );
            EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames[ i ].TopLeft.Second,    pRectangles->nPairA.nValB );
            EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames[ i ].BottomRight.First,  pRectangles->nPairB.nValA );
            EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames[ i ].BottomRight.Second, pRectangles->nPairB.nValB );
        }
        aPropVal.Name = sTextFrames;
        aPropVal.Value <<= seqTextFrames;
        aGeometryItem.SetPropertyValue( sPath, aPropVal );
    }

    // Equations
    static constexpr OUString sEquations(  u"Equations"_ustr  );
    pAny = aGeometryItem.GetPropertyValueByName( sEquations );
    if (!pAny && pDefCustomShape && !pDefCustomShape->pCalculation.empty() )
    {
        sal_Int32 i, nCount = pDefCustomShape->pCalculation.size();
        uno::Sequence< OUString > seqEquations( nCount );
        auto pseqEquations = seqEquations.getArray();
        for (i = 0; i < nCount; i++)
        {
            const SvxMSDffCalculationData* pData = &pDefCustomShape->pCalculation[i];
            pseqEquations[ i ] = EnhancedCustomShape2d::GetEquation( pData->nFlags, pData->nVal[ 0 ], pData->nVal[ 1 ], pData->nVal[ 2 ] );
        }
        aPropVal.Name = sEquations;
        aPropVal.Value <<= seqEquations;
        aGeometryItem.SetPropertyValue( aPropVal );
    }

    // Handles
    static constexpr OUString sHandles(  u"Handles"_ustr  );
    pAny = aGeometryItem.GetPropertyValueByName( sHandles );
    if  (!pAny && pDefCustomShape && !pDefCustomShape->pHandles.empty())
    {
        sal_Int32 i, nCount = pDefCustomShape->pHandles.size();
        uno::Sequence<beans::PropertyValues> seqHandles( nCount );
        auto pseqHandles = seqHandles.getArray();
        for (i = 0; i < nCount; i++)
        {
            const SvxMSDffHandle* pData = &pDefCustomShape->pHandles[i];
            sal_Int32 nPropertiesNeeded;
            beans::PropertyValues& rPropValues = pseqHandles[ i ];
            nPropertiesNeeded = GetNumberOfProperties( pData );
            rPropValues.realloc( nPropertiesNeeded );
            lcl_ShapePropertiesFromDFF( pData, rPropValues );
        }
        aPropVal.Name = sHandles;
        aPropVal.Value <<= seqHandles;
        aGeometryItem.SetPropertyValue( aPropVal );
    }
    else if (pAny && sShapeType.startsWith("ooxml-") && sShapeType != "ooxml-non-primitive")
    {
        // ODF is not able to store the ooxml way of connecting handle to an adjustment
        // value by name, e.g. attribute RefX="adj". So the information is lost, when exporting
        // a pptx to odp, for example. This part reconstructs this information for the
        // ooxml preset shapes from their definition.
        uno::Sequence<beans::PropertyValues> seqHandles;
        *pAny >>= seqHandles;
        auto seqHandlesRange = asNonConstRange(seqHandles);
        bool bChanged(false);
        for (sal_Int32 i = 0; i < seqHandles.getLength(); i++)
        {
            comphelper::SequenceAsHashMap aHandleProps(seqHandles[i]);
            OUString sFirstRefType;
            sal_Int32 nFirstAdjRef;
            OUString sSecondRefType;
            sal_Int32 nSecondAdjRef;
            PresetOOXHandleAdj::GetOOXHandleAdjRelation(sShapeType, i, sFirstRefType, nFirstAdjRef,
                                                        sSecondRefType, nSecondAdjRef);
            if (sFirstRefType != "na" && 0 <= nFirstAdjRef
                && nFirstAdjRef < seqAdjustmentValues.getLength())
            {
                bChanged |= aHandleProps.createItemIfMissing(sFirstRefType, nFirstAdjRef);
            }
            if (sSecondRefType != "na" && 0 <= nSecondAdjRef
                && nSecondAdjRef < seqAdjustmentValues.getLength())
            {
                bChanged |= aHandleProps.createItemIfMissing(sSecondRefType, nSecondAdjRef);
            }
            aHandleProps >> seqHandlesRange[i];
        }
        if (bChanged)
        {
            aPropVal.Name = sHandles;
            aPropVal.Value <<= seqHandles;
            aGeometryItem.SetPropertyValue(aPropVal);
        }
    }

    SetMergedItem( aGeometryItem );
}

bool SdrObjCustomShape::IsDefaultGeometry( const DefaultType eDefaultType ) const
{
    bool bIsDefaultGeometry = false;

    OUString sShapeType;
    const SdrCustomShapeGeometryItem & rGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );

    const uno::Any *pAny = rGeometryItem.GetPropertyValueByName( u"Type"_ustr );
    if ( pAny )
        *pAny >>= sShapeType;

    MSO_SPT eSpType = EnhancedCustomShapeTypeNames::Get( sShapeType );

    const mso_CustomShape* pDefCustomShape = GetCustomShapeContent( eSpType );
    static constexpr OUString sPath( u"Path"_ustr );
    switch( eDefaultType )
    {
        case DefaultType::Viewbox :
        {
            const uno::Any* pViewBox = rGeometryItem.GetPropertyValueByName( u"ViewBox"_ustr );
            awt::Rectangle aViewBox;
            if (pViewBox && (*pViewBox >>= aViewBox) && pDefCustomShape)
            {
                if ( ( aViewBox.Width == pDefCustomShape->nCoordWidth )
                    && ( aViewBox.Height == pDefCustomShape->nCoordHeight ) )
                    bIsDefaultGeometry = true;
            }
        }
        break;

        case DefaultType::Path :
        {
            pAny = rGeometryItem.GetPropertyValueByName( sPath, u"Coordinates"_ustr );
            if (pAny && pDefCustomShape && !pDefCustomShape->pVertices.empty())
            {
                uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqCoordinates1;
                if ( *pAny >>= seqCoordinates1 )
                {
                    sal_Int32 i, nCount = pDefCustomShape->pVertices.size();
                    uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqCoordinates2( nCount );
                    auto pseqCoordinates2 = seqCoordinates2.getArray();
                    for ( i = 0; i < nCount; i++ )
                    {
                        EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqCoordinates2[ i ].First, pDefCustomShape->pVertices[ i ].nValA );
                        EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqCoordinates2[ i ].Second, pDefCustomShape->pVertices[ i ].nValB );
                    }
                    if ( seqCoordinates1 == seqCoordinates2 )
                        bIsDefaultGeometry = true;
                }
            }
            else if (pDefCustomShape && pDefCustomShape->pVertices.empty())
                bIsDefaultGeometry = true;
        }
        break;

        case DefaultType::Gluepoints :
        {
            pAny = rGeometryItem.GetPropertyValueByName( sPath, u"GluePoints"_ustr );
            if (pAny && pDefCustomShape && !pDefCustomShape->pGluePoints.empty())
            {
                uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqGluePoints1;
                if ( *pAny >>= seqGluePoints1 )
                {
                    sal_Int32 i, nCount = pDefCustomShape->pGluePoints.size();
                    uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqGluePoints2( nCount );
                    auto pseqGluePoints2 = seqGluePoints2.getArray();
                    for ( i = 0; i < nCount; i++ )
                    {
                        EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqGluePoints2[ i ].First, pDefCustomShape->pGluePoints[ i ].nValA );
                        EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqGluePoints2[ i ].Second, pDefCustomShape->pGluePoints[ i ].nValB );
                    }
                    if ( seqGluePoints1 == seqGluePoints2 )
                        bIsDefaultGeometry = true;
                }
            }
            else if (pDefCustomShape && pDefCustomShape->pGluePoints.empty())
                bIsDefaultGeometry = true;
        }
        break;

        case DefaultType::Segments :
        {
            // Path/Segments
            pAny = rGeometryItem.GetPropertyValueByName( sPath, u"Segments"_ustr );
            if ( pAny )
            {
                uno::Sequence<drawing::EnhancedCustomShapeSegment> seqSegments1;
                if ( *pAny >>= seqSegments1 )
                {
                    if ( pDefCustomShape && pDefCustomShape->nElements && pDefCustomShape->pElements )
                    {
                        sal_Int32 i, nCount = pDefCustomShape->nElements;
                        if ( nCount )
                        {
                            uno::Sequence<drawing::EnhancedCustomShapeSegment> seqSegments2( nCount );
                            auto pseqSegments2 = seqSegments2.getArray();
                            for ( i = 0; i < nCount; i++ )
                            {
                                drawing::EnhancedCustomShapeSegment& rSegInfo = pseqSegments2[ i ];
                                sal_uInt16 nSDat = pDefCustomShape->pElements[ i ];
                                lcl_ShapeSegmentFromBinary( rSegInfo, nSDat );
                            }
                            if ( seqSegments1 == seqSegments2 )
                                bIsDefaultGeometry = true;
                        }
                    }
                    else
                    {
                        // check if it's the default segment description ( M L Z N )
                        if ( seqSegments1.getLength() == 4 )
                        {
                            if ( ( seqSegments1[ 0 ].Command == drawing::EnhancedCustomShapeSegmentCommand::MOVETO )
                                && ( seqSegments1[ 1 ].Command == drawing::EnhancedCustomShapeSegmentCommand::LINETO )
                                && ( seqSegments1[ 2 ].Command == drawing::EnhancedCustomShapeSegmentCommand::CLOSESUBPATH )
                                && ( seqSegments1[ 3 ].Command == drawing::EnhancedCustomShapeSegmentCommand::ENDSUBPATH ) )
                                bIsDefaultGeometry = true;
                        }
                    }
                }
            }
            else if ( pDefCustomShape && ( ( pDefCustomShape->nElements == 0 ) || ( pDefCustomShape->pElements == nullptr ) ) )
                bIsDefaultGeometry = true;
        }
        break;

        case DefaultType::StretchX :
        {
            pAny = rGeometryItem.GetPropertyValueByName( sPath, u"StretchX"_ustr );
            if ( pAny && pDefCustomShape )
            {
                sal_Int32 nStretchX = 0;
                if ( *pAny >>= nStretchX )
                {
                    if ( pDefCustomShape->nXRef == nStretchX )
                        bIsDefaultGeometry = true;
                }
            }
            else if ( pDefCustomShape && ( pDefCustomShape->nXRef == DEFAULT_MINIMUM_SIGNED_COMPARE ) )
                bIsDefaultGeometry = true;
        }
        break;

        case DefaultType::StretchY :
        {
            pAny = rGeometryItem.GetPropertyValueByName( sPath, u"StretchY"_ustr );
            if ( pAny && pDefCustomShape )
            {
                sal_Int32 nStretchY = 0;
                if ( *pAny >>= nStretchY )
                {
                    if ( pDefCustomShape->nYRef == nStretchY )
                        bIsDefaultGeometry = true;
                }
            }
            else if ( pDefCustomShape && ( pDefCustomShape->nYRef == DEFAULT_MINIMUM_SIGNED_COMPARE ) )
                bIsDefaultGeometry = true;
        }
        break;

        case DefaultType::Equations :
        {
            pAny = rGeometryItem.GetPropertyValueByName( u"Equations"_ustr );
            if (pAny && pDefCustomShape && !pDefCustomShape->pCalculation.empty())
            {
                uno::Sequence<OUString> seqEquations1;
                if ( *pAny >>= seqEquations1 )
                {
                    sal_Int32 i, nCount = pDefCustomShape->pCalculation.size();
                    uno::Sequence<OUString> seqEquations2( nCount );
                    auto pseqEquations2 = seqEquations2.getArray();

                    for (i = 0; i < nCount; i++)
                    {
                        const SvxMSDffCalculationData* pData = &pDefCustomShape->pCalculation[i];
                        pseqEquations2[ i ] = EnhancedCustomShape2d::GetEquation( pData->nFlags, pData->nVal[ 0 ], pData->nVal[ 1 ], pData->nVal[ 2 ] );
                    }

                    if ( seqEquations1 == seqEquations2 )
                        bIsDefaultGeometry = true;
                }
            }
            else if (pDefCustomShape && pDefCustomShape->pCalculation.empty())
                bIsDefaultGeometry = true;
        }
        break;

        case DefaultType::TextFrames :
        {
            pAny = rGeometryItem.GetPropertyValueByName( sPath, u"TextFrames"_ustr );
            if (pAny && pDefCustomShape && !pDefCustomShape->pTextRect.empty())
            {
                uno::Sequence<drawing::EnhancedCustomShapeTextFrame> seqTextFrames1;
                if ( *pAny >>= seqTextFrames1 )
                {
                    sal_Int32 i, nCount = pDefCustomShape->pTextRect.size();
                    uno::Sequence<drawing::EnhancedCustomShapeTextFrame> seqTextFrames2( nCount );
                    auto pseqTextFrames2 = seqTextFrames2.getArray();
                    for (i = 0; i < nCount; i++)
                    {
                        const SvxMSDffTextRectangles* pRectangles = &pDefCustomShape->pTextRect[i];
                        EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames2[ i ].TopLeft.First,    pRectangles->nPairA.nValA );
                        EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames2[ i ].TopLeft.Second,   pRectangles->nPairA.nValB );
                        EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames2[ i ].BottomRight.First,  pRectangles->nPairB.nValA );
                        EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames2[ i ].BottomRight.Second, pRectangles->nPairB.nValB );
                    }
                    if ( seqTextFrames1 == seqTextFrames2 )
                        bIsDefaultGeometry = true;
                }
            }
            else if (pDefCustomShape && pDefCustomShape->pTextRect.empty())
                bIsDefaultGeometry = true;
        }
        break;
    }
    return bIsDefaultGeometry;
}

void SdrObjCustomShape::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const
{
    rInfo.bResizeFreeAllowed=m_fObjectRotation == 0.0;
    rInfo.bResizePropAllowed=true;
    rInfo.bRotateFreeAllowed=true;
    rInfo.bRotate90Allowed  =true;
    rInfo.bMirrorFreeAllowed=true;
    rInfo.bMirror45Allowed  =true;
    rInfo.bMirror90Allowed  =true;
    rInfo.bTransparenceAllowed = false;
    rInfo.bShearAllowed     =true;
    rInfo.bEdgeRadiusAllowed=false;
    rInfo.bNoContortion     =true;

    // #i37011#
    if ( !mXRenderedCustomShape.is() )
        return;

    const SdrObject* pRenderedCustomShape = SdrObject::getSdrObjectFromXShape( mXRenderedCustomShape );
    if ( !pRenderedCustomShape )
        return;

    // #i37262#
    // Iterate self over the contained objects, since there are combinations of
    // polygon and curve objects. In that case, aInfo.bCanConvToPath and
    // aInfo.bCanConvToPoly would be false. What is needed here is an or, not an and.
    SdrObjListIter aIterator(*pRenderedCustomShape);
    while(aIterator.IsMore())
    {
        SdrObject* pCandidate = aIterator.Next();
        SdrObjTransformInfoRec aInfo;
        pCandidate->TakeObjInfo(aInfo);

        // set path and poly conversion if one is possible since
        // this object will first be broken
        const bool bCanConvToPathOrPoly(aInfo.bCanConvToPath || aInfo.bCanConvToPoly);
        if(rInfo.bCanConvToPath != bCanConvToPathOrPoly)
        {
            rInfo.bCanConvToPath = bCanConvToPathOrPoly;
        }

        if(rInfo.bCanConvToPoly != bCanConvToPathOrPoly)
        {
            rInfo.bCanConvToPoly = bCanConvToPathOrPoly;
        }

        if(rInfo.bCanConvToContour != aInfo.bCanConvToContour)
        {
            rInfo.bCanConvToContour = aInfo.bCanConvToContour;
        }

        if(rInfo.bShearAllowed != aInfo.bShearAllowed)
        {
            rInfo.bShearAllowed = aInfo.bShearAllowed;
        }
    }
}

SdrObjKind SdrObjCustomShape::GetObjIdentifier() const
{
    return SdrObjKind::CustomShape;
}

// #115391# This implementation is based on the TextFrame size of the CustomShape and the
// state of the ResizeShapeToFitText flag to correctly set TextMinFrameWidth/Height
void SdrObjCustomShape::AdaptTextMinSize()
{
    if (getSdrModelFromSdrObject().IsCreatingDataObj() || getSdrModelFromSdrObject().IsPasteResize())
        return;

    // check if we need to change anything before creating an SfxItemSet, because that is expensive
    const bool bResizeShapeToFitText(GetObjectItem(SDRATTR_TEXT_AUTOGROWHEIGHT).GetValue());
    tools::Rectangle aTextBound(getRectangle());
    bool bChanged(false);
    if(bResizeShapeToFitText)
        bChanged = true;
    else if(GetTextBounds(aTextBound))
        bChanged = true;
    if (!bChanged)
       return;

    SfxItemSetFixed<SDRATTR_TEXT_MINFRAMEHEIGHT, SDRATTR_TEXT_AUTOGROWHEIGHT,
        SDRATTR_TEXT_MINFRAMEWIDTH, SDRATTR_TEXT_AUTOGROWWIDTH> // contains SDRATTR_TEXT_MAXFRAMEWIDTH
        aSet(*GetObjectItemSet().GetPool());

    if(bResizeShapeToFitText)
    {
        // always reset MinWidthHeight to zero to only rely on text size and frame size
        // to allow resizing being completely dependent on text size only
        aSet.Put(makeSdrTextMinFrameWidthItem(0));
        aSet.Put(makeSdrTextMinFrameHeightItem(0));
    }
    else
    {
        // recreate from CustomShape-specific TextBounds
        const tools::Long nHDist(GetTextLeftDistance() + GetTextRightDistance());
        const tools::Long nVDist(GetTextUpperDistance() + GetTextLowerDistance());
        const tools::Long nTWdt(std::max(tools::Long(0), static_cast<tools::Long>(aTextBound.GetWidth() - 1 - nHDist)));
        const tools::Long nTHgt(std::max(tools::Long(0), static_cast<tools::Long>(aTextBound.GetHeight() - 1 - nVDist)));

        aSet.Put(makeSdrTextMinFrameWidthItem(nTWdt));
        aSet.Put(makeSdrTextMinFrameHeightItem(nTHgt));
    }

    SetObjectItemSet(aSet);
}

void SdrObjCustomShape::NbcSetSnapRect(const tools::Rectangle& rRectangle)
{
    tools::Rectangle aRectangle(rRectangle);
    ImpJustifyRect(aRectangle);
    setRectangle(aRectangle);
    InvalidateRenderGeometry();

    AdaptTextMinSize();

    ImpCheckShear();
    SetBoundAndSnapRectsDirty();
    SetChanged();
}

void SdrObjCustomShape::SetSnapRect( const tools::Rectangle& rRect )
{
    tools::Rectangle aBoundRect0;
    if ( m_pUserCall )
        aBoundRect0 = GetLastBoundRect();
    NbcSetSnapRect( rRect );
    BroadcastObjectChange();
    SendUserCall(SdrUserCallType::Resize,aBoundRect0);
}

void SdrObjCustomShape::NbcSetLogicRect(const tools::Rectangle& rRectangle, bool bAdaptTextMinSize)
{
    tools::Rectangle aRectangle(rRectangle);
    ImpJustifyRect(aRectangle);
    setRectangle(aRectangle);
    InvalidateRenderGeometry();

    if (bAdaptTextMinSize)
        AdaptTextMinSize();

    SetBoundAndSnapRectsDirty();
    SetChanged();
}

void SdrObjCustomShape::SetLogicRect( const tools::Rectangle& rRect )
{
    tools::Rectangle aBoundRect0;
    if ( m_pUserCall )
        aBoundRect0 = GetLastBoundRect();
    NbcSetLogicRect(rRect);
    BroadcastObjectChange();
    SendUserCall(SdrUserCallType::Resize,aBoundRect0);
}

void SdrObjCustomShape::Move( const Size& rSiz )
{
    if ( rSiz.Width() || rSiz.Height() )
    {
        tools::Rectangle aBoundRect0;
        if ( m_pUserCall )
            aBoundRect0 = GetLastBoundRect();
        NbcMove(rSiz);
        SetChanged();
        BroadcastObjectChange();
        SendUserCall(SdrUserCallType::MoveOnly,aBoundRect0);
    }
}
void SdrObjCustomShape::NbcMove( const Size& rSiz )
{
    SdrTextObj::NbcMove( rSiz );
    if ( mXRenderedCustomShape.is() )
    {
        SdrObject* pRenderedCustomShape = SdrObject::getSdrObjectFromXShape(mXRenderedCustomShape);
        if ( pRenderedCustomShape )
        {
            // #i97149# the visualisation shape needs to be informed
            // about change, too
            pRenderedCustomShape->ActionChanged();
            pRenderedCustomShape->NbcMove( rSiz );
        }
    }

    // #i37011# adapt geometry shadow
    if(mpLastShadowGeometry)
    {
        mpLastShadowGeometry->NbcMove( rSiz );
    }
}

void SdrObjCustomShape::NbcResize( const Point& rRef, const Fraction& rxFact, const Fraction& ryFact )
{
    // taking care of handles that should not been changed
    tools::Rectangle aOld(getRectangle());
    std::vector< SdrCustomShapeInteraction > aInteractionHandles( GetInteractionHandles() );

    SdrTextObj::NbcResize( rRef, rxFact, ryFact );

    if ( ( rxFact.GetNumerator() != rxFact.GetDenominator() )
        || ( ryFact.GetNumerator()!= ryFact.GetDenominator() ) )
    {
        if ( ( ( rxFact.GetNumerator() < 0 ) && ( rxFact.GetDenominator() > 0 ) ) ||
            ( ( rxFact.GetNumerator() > 0 ) && ( rxFact.GetDenominator() < 0 ) ) )
        {
            SetMirroredX( !IsMirroredX() );
        }
        if ( ( ( ryFact.GetNumerator() < 0 ) && ( ryFact.GetDenominator() > 0 ) ) ||
            ( ( ryFact.GetNumerator() > 0 ) && ( ryFact.GetDenominator() < 0 ) ) )
        {
            SetMirroredY( !IsMirroredY() );
        }
    }

    for (const auto& rInteraction : aInteractionHandles)
    {
        try
        {
            if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_FIXED )
                rInteraction.xInteraction->setControllerPosition( rInteraction.aPosition );
            if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_X )
            {
                sal_Int32 nX = ( rInteraction.aPosition.X - aOld.Left() ) + getRectangle().Left();
                rInteraction.xInteraction->setControllerPosition(awt::Point(nX, rInteraction.xInteraction->getPosition().Y));
            }
            else if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_NEGX )
            {
                sal_Int32 nX = getRectangle().Right() - (aOld.Right() - rInteraction.aPosition.X);
                rInteraction.xInteraction->setControllerPosition(awt::Point(nX, rInteraction.xInteraction->getPosition().Y));
            }
            if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_Y )
            {
                sal_Int32 nY = ( rInteraction.aPosition.Y - aOld.Top() ) + getRectangle().Top();
                rInteraction.xInteraction->setControllerPosition(awt::Point(rInteraction.xInteraction->getPosition().X, nY));
            }
        }
        catch ( const uno::RuntimeException& )
        {
        }
    }

    // updating fObjectRotation
    Degree100 nTextObjRotation = maGeo.m_nRotationAngle;
    double fAngle = toDegrees(nTextObjRotation);
    if (IsMirroredX())
    {
        if (IsMirroredY())
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=93 H=95 G=93

¤ Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.0.17Bemerkung:  Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können  ¤

*Eine klare Vorstellung vom Zielzustand






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.