/* -*- 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 <memory>

#include <com/sun/star/presentation/XPresentation2.hpp>

#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/drawing/LineStyle.hpp>
#include <com/sun/star/lang/DisposedException.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <com/sun/star/lang/ServiceNotRegisteredException.hpp>
#include <com/sun/star/lang/Locale.hpp>
#include <com/sun/star/awt/XDevice.hpp>
#include <com/sun/star/document/IndexedPropertyValues.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/util/XTheme.hpp>
#include <com/sun/star/animations/AnimationFill.hpp>
#include <com/sun/star/animations/AnimationRestart.hpp>
#include <com/sun/star/animations/AnimationEndSync.hpp>
#include <com/sun/star/animations/AnimationCalcMode.hpp>
#include <com/sun/star/animations/AnimationAdditiveMode.hpp>
#include <com/sun/star/animations/AnimationNodeType.hpp>
#include <com/sun/star/animations/AnimationTransformType.hpp>
#include <com/sun/star/animations/AnimationColorSpace.hpp>
#include <com/sun/star/animations/Event.hpp>
#include <com/sun/star/animations/EventTrigger.hpp>
#include <com/sun/star/animations/Timing.hpp>
#include <com/sun/star/animations/TransitionType.hpp>
#include <com/sun/star/animations/TransitionSubType.hpp>
#include <com/sun/star/animations/ValuePair.hpp>
#include <com/sun/star/animations/XAnimate.hpp>
#include <com/sun/star/animations/XAnimateMotion.hpp>
#include <com/sun/star/animations/XAnimateColor.hpp>
#include <com/sun/star/animations/XAnimateTransform.hpp>
#include <com/sun/star/animations/XIterateContainer.hpp>
#include <com/sun/star/animations/XTimeContainer.hpp>
#include <com/sun/star/animations/XTransitionFilter.hpp>
#include <com/sun/star/presentation/EffectNodeType.hpp>
#include <com/sun/star/presentation/EffectPresetClass.hpp>
#include <com/sun/star/presentation/ParagraphTarget.hpp>
#include <com/sun/star/presentation/ShapeAnimationSubType.hpp>
#include <com/sun/star/presentation/TextAnimationType.hpp>


#include <com/sun/star/embed/Aspects.hpp>

#include <animations/animationnodehelper.hxx>

#include <officecfg/Office/Common.hxx>
#include <officecfg/Office/Impress.hxx>
#include <comphelper/dispatchcommand.hxx>
#include <comphelper/indexedpropertyvalues.hxx>
#include <comphelper/lok.hxx>
#include <comphelper/propertysequence.hxx>
#include <comphelper/propertyvalue.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/servicehelper.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/profilezone.hxx>

#include <sal/log.hxx>
#include <editeng/unofield.hxx>
#include <notifydocumentevent.hxx>
#include <tpaction.hxx>
#include <unomodel.hxx>
#include <sdhtmlfilter.hxx>
#include "unopool.hxx"
#include <sfx2/lokhelper.hxx>
#include <sfx2/dispatch.hxx>
#include <vcl/svapp.hxx>
#include <Outliner.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>

#include <editeng/UnoForbiddenCharsTable.hxx>
#include <svx/svdoutl.hxx>
#include <o3tl/any.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>
#include <o3tl/test_info.hxx>
#include <o3tl/unit_conversion.hxx>
#include <svx/UnoNamespaceMap.hxx>
#include <svx/svdlayer.hxx>
#include <svx/svdsob.hxx>
#include <svx/svdundo.hxx>
#include <svx/svdomedia.hxx>
#include <svx/unoapi.hxx>
#include <svx/unofill.hxx>
#include <svx/sdrpagewindow.hxx>
#include <svx/sdrpaintwindow.hxx>
#include <editeng/fontitem.hxx>
#include <toolkit/awt/vclxdevice.hxx>
#include <svx/svdpool.hxx>
#include <svx/svdpagv.hxx>
#include <svtools/unoimap.hxx>
#include <svx/unoshape.hxx>
#include <editeng/unonrule.hxx>
#include <editeng/eeitem.hxx>
#include <unotools/datetime.hxx>
#include <sax/tools/converter.hxx>
#include <xmloff/autolayout.hxx>
#include <xmloff/xmltoken.hxx>
#include <rtl/math.hxx>
#include <tools/helpers.hxx>
#include <tools/json_writer.hxx>
#include <TransitionPreset.hxx>

// Support creation of GraphicStorageHandler and EmbeddedObjectResolver
#include <svx/xmleohlp.hxx>
#include <xmloff/xmlgrhlp.hxx>
#include <DrawDocShell.hxx>
#include <ViewShellBase.hxx>
#include "UnoDocumentSettings.hxx"

#include <Annotation.hxx>
#include <drawdoc.hxx>
#include <sdmod.hxx>
#include <sdresid.hxx>
#include <sdpage.hxx>

#include <strings.hrc>
#include <strings.hxx>
#include <unolayer.hxx>
#include <unopage.hxx>
#include "unocpres.hxx"
#include "unoobj.hxx"
#include <stlpool.hxx>
#include "unopback.hxx"
#include <unokywds.hxx>

#include <FrameView.hxx>
#include <ClientView.hxx>
#include <DrawViewShell.hxx>
#include <ViewShell.hxx>
#include <Window.hxx>
#include <optsitem.hxx>
#include <SlideshowLayerRenderer.hxx>

#include <vcl/pdfextoutdevdata.hxx>
#include <vcl/pdf/PDFNote.hxx>

#include <com/sun/star/presentation/AnimationSpeed.hpp>
#include <com/sun/star/presentation/ClickAction.hpp>
#include <svx/sdr/contact/viewobjectcontact.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <svx/sdr/contact/displayinfo.hxx>

#include <com/sun/star/office/XAnnotation.hpp>
#include <com/sun/star/office/XAnnotationAccess.hpp>
#include <com/sun/star/office/XAnnotationEnumeration.hpp>
#include <com/sun/star/geometry/RealPoint2D.hpp>
#include <com/sun/star/util/DateTime.hpp>

#include <drawinglayer/primitive2d/structuretagprimitive2d.hxx>

#include <sfx2/lokcomponenthelpers.hxx>
#include <sfx2/LokControlHandler.hxx>
#include <tools/gen.hxx>
#include <tools/debug.hxx>
#include <tools/urlobj.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <tools/UnitConversion.hxx>
#include <svx/ColorSets.hxx>
#include <docmodel/theme/Theme.hxx>

#include <frozen/bits/defines.h>
#include <frozen/bits/elsa_std.h>
#include <frozen/unordered_map.h>
#include <SlideSorter.hxx>
#include <SlideSorterViewShell.hxx>
#include <controller/SlideSorterController.hxx>
#include <controller/SlsPageSelector.hxx>

#include <app.hrc>

using namespace ::cppu;
using namespace ::com::sun::star;
using namespace ::sd;

const TranslateId aTypeResIds[SdLinkTargetType::Count] =
{
    STR_SD_PAGE,            // SdLinkTargetType::Page
    STR_NOTES_MODE,         // SdLinkTargetType::Notes
    STR_HANDOUT,            // SdLinkTargetType::Handout
    STR_MASTERPAGE_NAME,    // SdLinkTargetType::MasterPage
};

TranslateId SdTPAction::GetClickActionSdResId( presentation::ClickAction eCA )
{
    switch( eCA )
    {
        case presentation::ClickAction_NONE:             return STR_CLICK_ACTION_NONE;
        case presentation::ClickAction_PREVPAGE:         return STR_CLICK_ACTION_PREVPAGE;
        case presentation::ClickAction_NEXTPAGE:         return STR_CLICK_ACTION_NEXTPAGE;
        case presentation::ClickAction_FIRSTPAGE:        return STR_CLICK_ACTION_FIRSTPAGE;
        case presentation::ClickAction_LASTPAGE:         return STR_CLICK_ACTION_LASTPAGE;
        case presentation::ClickAction_BOOKMARK:         return STR_CLICK_ACTION_BOOKMARK;
        case presentation::ClickAction_DOCUMENT:         return STR_CLICK_ACTION_DOCUMENT;
        case presentation::ClickAction_PROGRAM:          return STR_CLICK_ACTION_PROGRAM;
        case presentation::ClickAction_MACRO:            return STR_CLICK_ACTION_MACRO;
        case presentation::ClickAction_SOUND:            return STR_CLICK_ACTION_SOUND;
        case presentation::ClickAction_VERB:             return STR_CLICK_ACTION_VERB;
        case presentation::ClickAction_STOPPRESENTATION: return STR_CLICK_ACTION_STOPPRESENTATION;
        default: OSL_FAIL( "No StringResource for ClickAction available!" );
    }
    return {};
}

class SdUnoForbiddenCharsTable : public SvxUnoForbiddenCharsTable,
                                 public SfxListener
{
public:
    explicit SdUnoForbiddenCharsTable(SdrModel* pModel);
    virtual ~SdUnoForbiddenCharsTable() override;

    // SfxListener
    virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) noexcept override;
protected:
    virtual void onChange() override;

private:
    SdrModel*   mpModel;
};

namespace {

class SlideBackgroundInfo
{
public:
    SlideBackgroundInfo(const uno::Reference<drawing::XDrawPage>& xDrawPage,
                        const uno::Reference<drawing::XDrawPage>& xMasterPage);
    bool slideHasOwnBackground() const { return mbIsCustom; }
    bool hasBackground() const { return bHasBackground; }
    bool isSolidColor() const { return mbIsSolidColor; }
    ::Color getFillColor() const;
    sal_Int32 getFillTransparency() const;
    OString getFillColorAsRGBA() const;
private:
    bool getFillStyleImpl(const uno::Reference<drawing::XDrawPage>& xDrawPage);
private:
    uno::Reference<beans::XPropertySet> mxBackground;
    bool mbIsCustom;
    bool bHasBackground;
    bool mbIsSolidColor;
    drawing::FillStyle maFillStyle;
};

SlideBackgroundInfo::SlideBackgroundInfo(
        const uno::Reference<drawing::XDrawPage>& xDrawPage,
        const uno::Reference<drawing::XDrawPage>& xMasterPage)
    : mbIsCustom(false)
    , bHasBackground(false)
    , mbIsSolidColor(false)
    , maFillStyle(drawing::FillStyle_NONE)
{
    mbIsCustom = getFillStyleImpl(xDrawPage);
    bHasBackground = mbIsCustom;
    if (!bHasBackground)
    {
        bHasBackground = getFillStyleImpl(xMasterPage);
    }
    if (bHasBackground)
    {
        if (maFillStyle == drawing::FillStyle_SOLID)
        {
            OUString sGradientName;
            mxBackground->getPropertyValue("FillTransparenceGradientName") >>= sGradientName;
            if (sGradientName.isEmpty())
            {
                mbIsSolidColor = true;
            }
        }
    }
}

sal_Int32 SlideBackgroundInfo::getFillTransparency() const
{
    if (!mxBackground.is())
        return 0;
    sal_Int32 nFillTransparency = 0;
    mxBackground->getPropertyValue("FillTransparence") >>= nFillTransparency;
    return nFillTransparency;
}

::Color SlideBackgroundInfo::getFillColor() const
{
    if (!mxBackground.is())
        return {};
    if (sal_Int32 nFillColor; mxBackground->getPropertyValue("FillColor") >>= nFillColor)
    {
        return ::Color(ColorTransparency, nFillColor & 0xffffff);
    }
    return {};
}

OString SlideBackgroundInfo::getFillColorAsRGBA() const
{
    ::Color aColor = getFillColor();
    OString sColor = aColor.AsRGBHEXString().toUtf8();
    sal_uInt32 nAlpha = std::round((100 - getFillTransparency()) * 255 / 100.0);
    std::stringstream ss;
    ss << std::hex << std::uppercase << std::setfill ('0') << std::setw(2) << nAlpha;
    sColor += ss.str().c_str();
    return sColor;
}

bool SlideBackgroundInfo::getFillStyleImpl(const uno::Reference<drawing::XDrawPage>& xDrawPage)
{
    if( xDrawPage.is() )
    {
        uno::Reference< beans::XPropertySet > xPropSet( xDrawPage, uno::UNO_QUERY );
        if( xPropSet.is() )
        {
            uno::Reference< beans::XPropertySet > xBackground;
            if (xPropSet->getPropertySetInfo()->hasPropertyByName("Background"))
                xPropSet->getPropertyValue( "Background" ) >>= xBackground;
            if( xBackground.is() )
            {
                drawing::FillStyle aFillStyle;
                if( xBackground->getPropertyValue( "FillStyle" ) >>= aFillStyle )
                {
                    maFillStyle = aFillStyle;
                    if (aFillStyle != drawing::FillStyle_NONE)
                    {
                        mxBackground = std::move(xBackground);
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

using namespace ::css::animations;
using namespace ::css::beans;
using namespace ::css::container;
using namespace ::css::uno;
using namespace ::xmloff::token;
using namespace ::css::presentation;

template <typename T, std::size_t N>
constexpr auto mapEnumToString(std::pair<T, std::string_view> const (&items)[N])
{
    return frozen::make_unordered_map<T, std::string_view, N>(items);
}

constexpr auto constTransitionTypeToString = mapEnumToString<sal_Int16>({
    { animations::TransitionType::BARWIPE, "BarWipe" }, // Wipe
    { animations::TransitionType::PINWHEELWIPE, "PineWheelWipe" }, // Wheel
    { animations::TransitionType::SLIDEWIPE, "SlideWipe" }, // Cover, Uncover
    { animations::TransitionType::RANDOMBARWIPE, "RandomBarWipe" }, // Bars
    { animations::TransitionType::CHECKERBOARDWIPE, "CheckerBoardWipe" }, // Checkers
    { animations::TransitionType::FOURBOXWIPE, "FourBoxWipe" }, // Shape
    { animations::TransitionType::IRISWIPE, "IrisWipe" }, // Box
    { animations::TransitionType::FANWIPE, "FanWipe" }, // Wedge
    { animations::TransitionType::BLINDSWIPE, "BlindWipe"}, // Venetian
    { animations::TransitionType::FADE, "Fade"},
    { animations::TransitionType::DISSOLVE, "Dissolve"},
    { animations::TransitionType::PUSHWIPE, "PushWipe"}, // Comb
    { animations::TransitionType::ELLIPSEWIPE, "EllipseWipe"}, // Shape
    { animations::TransitionType::BARNDOORWIPE, "BarnDoorWipe"}, // Split
    { animations::TransitionType::WATERFALLWIPE, "WaterfallWipe"}, // Diagonal
    { animations::TransitionType::MISCSHAPEWIPE, "MiscShapeWipe"},
    { animations::TransitionType::ZOOM, "Zoom"}
});

constexpr auto constTransitionSubTypeToString = mapEnumToString<sal_Int16>({
    { animations::TransitionSubType::LEFTTORIGHT, "LeftToRight" },
    { animations::TransitionSubType::TOPTOBOTTOM, "TopToBottom" },
    { animations::TransitionSubType::EIGHTBLADE, "8Blade" },
    { animations::TransitionSubType::FOURBLADE, "4Blade" },
    { animations::TransitionSubType::THREEBLADE, "3Blade" },
    { animations::TransitionSubType::TWOBLADEVERTICAL, "2BladeVertical" },
    { animations::TransitionSubType::ONEBLADE, "1Blade" },
    { animations::TransitionSubType::FROMTOPLEFT, "FromTopLeft" },
    { animations::TransitionSubType::FROMTOPRIGHT, "FromTopRight"},
    { animations::TransitionSubType::FROMBOTTOMLEFT, "FromBottomLeft"},
    { animations::TransitionSubType::FROMBOTTOMRIGHT, "FromBottomRight"},
    { animations::TransitionSubType::VERTICAL, "Vertical"},
    { animations::TransitionSubType::HORIZONTAL, "Horizontal"},
    { animations::TransitionSubType::DOWN, "Down"},
    { animations::TransitionSubType::ACROSS, "Across"},
    { animations::TransitionSubType::CORNERSOUT, "CornersOut"},
    { animations::TransitionSubType::DIAMOND, "Diamond"},
    { animations::TransitionSubType::CIRCLE, "Circle"},
    { animations::TransitionSubType::RECTANGLE, "Rectangle"},
    { animations::TransitionSubType::CENTERTOP, "CenterTop"},
    { animations::TransitionSubType::CROSSFADE, "CrossFade"},
    { animations::TransitionSubType::FADEOVERCOLOR, "FadeOverColor"},
    { animations::TransitionSubType::FROMLEFT, "FromLeft"},
    { animations::TransitionSubType::FROMRIGHT, "FromRight"},
    { animations::TransitionSubType::FROMTOP, "FromTop"},
    { animations::TransitionSubType::HORIZONTALLEFT, "HorizontalLeft"},
    { animations::TransitionSubType::HORIZONTALRIGHT, "HorizontalRight"},
    { animations::TransitionSubType::COMBVERTICAL, "CombVertical"},
    { animations::TransitionSubType::COMBHORIZONTAL, "CombHorizontal"},
    { animations::TransitionSubType::TOPLEFT, "TopLeft"},
    { animations::TransitionSubType::TOPRIGHT, "TopRight"},
    { animations::TransitionSubType::BOTTOMRIGHT, "BottomRight"},
    { animations::TransitionSubType::BOTTOMLEFT, "BottomLeft"},
    { animations::TransitionSubType::TOPCENTER, "TopCenter"},
    { animations::TransitionSubType::RIGHTCENTER, "RightCenter"},
    { animations::TransitionSubType::BOTTOMCENTER, "BottomCenter"},
    { animations::TransitionSubType::FANOUTHORIZONTAL, "FanOutHorizontal"},
    { animations::TransitionSubType::CORNERSIN, "CornersIn"},
    { animations::TransitionSubType::HEART, "Heart"},
    { animations::TransitionSubType::ROTATEIN, "RotateIn"}
});

constexpr auto constAnimationNodeTypeToString = mapEnumToString<sal_Int16>({
    { AnimationNodeType::ANIMATE, "Animate" },
    { AnimationNodeType::ANIMATECOLOR, "AnimateColor" },
    { AnimationNodeType::ANIMATEMOTION, "AnimateMotion" },
    { AnimationNodeType::ANIMATEPHYSICS, "Animate" },
    { AnimationNodeType::ANIMATETRANSFORM, "AnimateTransform" },
    { AnimationNodeType::AUDIO, "Audio" },
    { AnimationNodeType::COMMAND, "Command" },
    { AnimationNodeType::CUSTOM, "Custom" },
    { AnimationNodeType::ITERATE, "Iterate" },
    { AnimationNodeType::PAR, "Par" },
    { AnimationNodeType::SEQ, "Seq" },
    { AnimationNodeType::SET, "Set" },
    { AnimationNodeType::TRANSITIONFILTER, "TransitionFilter" },
});

constexpr auto constFillToString = mapEnumToString<sal_Int16>({
    { AnimationFill::DEFAULT, "Default" },
    { AnimationFill::REMOVE, "Remove" },
    { AnimationFill::FREEZE, "Freeze" },
    { AnimationFill::HOLD, "Hold" },
    { AnimationFill::TRANSITION, "Transition" },
    { AnimationFill::AUTO, "Auto" },
});

constexpr auto constRestartToString = mapEnumToString<sal_Int16>({
    { AnimationRestart::DEFAULT, "Default" },
    { AnimationRestart::ALWAYS, "Always" },
    { AnimationRestart::WHEN_NOT_ACTIVE, "WhenNotActive" },
    { AnimationRestart::NEVER, "Never" },
});

constexpr auto constEndSyncToString = mapEnumToString<sal_Int16>({
    { AnimationEndSync::FIRST, "First" },
    { AnimationEndSync::LAST, "Last" },
    { AnimationEndSync::ALL, "All" },
    { AnimationEndSync::MEDIA, "Media" },
});

constexpr auto constCalcModeToString = mapEnumToString<sal_Int16>({
    { AnimationCalcMode::DISCRETE, "Discrete" },
    { AnimationCalcMode::LINEAR, "Linear" },
    { AnimationCalcMode::PACED, "Paced" },
    { AnimationCalcMode::SPLINE, "Spline" },
});

constexpr auto constAdditiveModeToString = mapEnumToString<sal_Int16>({
    { AnimationAdditiveMode::BASE, "Base" },
    { AnimationAdditiveMode::SUM, "Sum" },
    { AnimationAdditiveMode::REPLACE, "Replace" },
    { AnimationAdditiveMode::MULTIPLY, "Multiply" },
    { AnimationAdditiveMode::NONE, "None" },
});

constexpr auto constEffectPresetClassToString = mapEnumToString<sal_Int16>({
    { EffectPresetClass::CUSTOM, "Custom" },
    { EffectPresetClass::ENTRANCE, "Entrance" },
    { EffectPresetClass::EXIT, "Exit" },
    { EffectPresetClass::EMPHASIS, "Emphasis" },
    { EffectPresetClass::MOTIONPATH, "MotionPath" },
    { EffectPresetClass::OLEACTION, "OleAction" },
    { EffectPresetClass::MEDIACALL, "MediaCall" },
});

constexpr auto constEffectNodeTypeToString = mapEnumToString<sal_Int16>({
    { EffectNodeType::DEFAULT, "Default" },
    { EffectNodeType::ON_CLICK, "OnClick" },
    { EffectNodeType::WITH_PREVIOUS, "WithPrevious" },
    { EffectNodeType::AFTER_PREVIOUS, "AfterPrevious" },
    { EffectNodeType::MAIN_SEQUENCE, "MainSequence" },
    { EffectNodeType::TIMING_ROOT, "TimingRoot" },
    { EffectNodeType::INTERACTIVE_SEQUENCE, "InteractiveSequence" },
});

constexpr auto constEventTriggerToString = mapEnumToString<sal_Int16>({
    { EventTrigger::BEGIN_EVENT, "BeginEvent" },
    { EventTrigger::END_EVENT, "EndEvent" },
    { EventTrigger::NONE, "None" },
    { EventTrigger::ON_BEGIN, "OnBegin" },
    { EventTrigger::ON_CLICK, "OnClick" },
    { EventTrigger::ON_DBL_CLICK, "OnDblClick" },
    { EventTrigger::ON_END, "OnEnd" },
    { EventTrigger::ON_MOUSE_ENTER, "OnMouseEnter" },
    { EventTrigger::ON_MOUSE_LEAVE, "OnMouseLeave" },
    { EventTrigger::ON_NEXT, "OnNext" },
    { EventTrigger::ON_PREV, "OnPrev" },
    { EventTrigger::ON_STOP_AUDIO, "OnStopAudio" },
    { EventTrigger::REPEAT, "Repeat" },
});

constexpr auto constTimingToString = mapEnumToString<Timing>({
    { Timing_INDEFINITE, "indefinite" },
    { Timing_MEDIA, "media" },
});

constexpr auto constTransformTypeToString = mapEnumToString<sal_Int16>({
    { AnimationTransformType::TRANSLATE, "Translate" },
    { AnimationTransformType::SCALE, "Scale" },
    { AnimationTransformType::ROTATE, "Rotate" },
    { AnimationTransformType::SKEWX, "SkewX" },
    { AnimationTransformType::SKEWY, "SkewY" },
});

constexpr auto constSubItemToString = mapEnumToString<sal_Int16>({
    { ShapeAnimationSubType::AS_WHOLE, "AsWhole" },
    { ShapeAnimationSubType::ONLY_BACKGROUND, "OnlyBackground" },
    { ShapeAnimationSubType::ONLY_TEXT, "OnlyText" },
});

constexpr auto constIterateTypeToString = mapEnumToString<sal_Int16>({
    { TextAnimationType::BY_PARAGRAPH, "ByParagraph" },
    { TextAnimationType::BY_WORD, "ByWord" },
    { TextAnimationType::BY_LETTER, "ByLetter" },
});

constexpr auto constFillStyleToString = mapEnumToString<drawing::FillStyle>({
    { drawing::FillStyle_NONE, "None" },
    { drawing::FillStyle_SOLID, "Solid" },
    { drawing::FillStyle_BITMAP, "Bitmap" },
    { drawing::FillStyle_GRADIENT, "Gradient" },
    { drawing::FillStyle_HATCH, "Hatch" },
});

constexpr auto constLineStyleToString = mapEnumToString<drawing::LineStyle>({
    { drawing::LineStyle_NONE, "None" },
    { drawing::LineStyle_SOLID, "Solid" },
    { drawing::LineStyle_DASH, "Dash" },
});


constexpr auto constAttributeNameToXMLEnum
    = frozen::make_unordered_map<std::string_view, XMLTokenEnum>({
        { "X", XML_X },
        { "Y", XML_Y },
        { "Width", XML_WIDTH },
        { "Height", XML_HEIGHT },
        { "Rotate", XML_ROTATE },
        { "SkewX", XML_SKEWX },
        { "FillColor", XML_FILL_COLOR },
        { "FillStyle", XML_FILL },
        { "LineColor", XML_STROKE_COLOR },
        { "LineStyle",XML_STROKE  },
        { "CharColor", XML_COLOR },
        { "CharRotation", XML_TEXT_ROTATION_ANGLE },
        { "CharWeight", XML_FONT_WEIGHT },
        { "CharUnderline", XML_TEXT_UNDERLINE },
        { "CharFontName", XML_FONT_FAMILY },
        { "CharHeight", XML_FONT_SIZE },
        { "CharPosture", XML_FONT_STYLE },
        { "Visibility", XML_VISIBILITY },
        { "Opacity", XML_OPACITY },
        { "DimColor", XML_DIM },
});

class AnimationsExporter
{
public:
    AnimationsExporter(::tools::JsonWriter& rWriter,
                       const Reference<drawing::XDrawPage>& xDrawPage);
    void exportAnimations();
    void exportTriggers() const;
    [[nodiscard]] bool hasEffects() const { return mbHasEffects; }

private:
    void exportNode(const Reference<XAnimationNode>& xNode);
    void exportNodeImpl(const Reference<XAnimationNode>& xNode);
    void exportContainer(const Reference<XTimeContainer>& xContainer);

    void exportAnimate(const Reference<XAnimate>& xAnimate);

    void convertValue(XMLTokenEnum eAttributeName, OStringBuffer& sTmp, const Any& rValue) const;
    void convertTiming(OStringBuffer& sTmp, const Any& rValue);

    void appendTrigger(const css::uno::Any& rTarget, const OString& rTriggerHash);
    void exportTriggersImpl(const uno::Reference<drawing::XShapes>& xShapes) const;

private:
    ::tools::JsonWriter& mrWriter;
    Reference<drawing::XDrawPage> mxDrawPage;
    Reference<XPropertySet> mxPageProps;
    Reference<XAnimationNode> mxRootNode;
    bool mbHasEffects;
    std::unordered_map<SdrObject*, OString> maEventTriggerSet;
};

AnimationsExporter::AnimationsExporter(::tools::JsonWriter& rWriter,
                                       const Reference<drawing::XDrawPage>& xDrawPage)
    : mrWriter(rWriter)
    , mxDrawPage(xDrawPage)
    , mbHasEffects(false)
{
    if (!mxDrawPage.is())
        return;

    try
    {
        mxPageProps = Reference<XPropertySet>(xDrawPage, UNO_QUERY);
        if (!mxPageProps.is())
            return;

        Reference<XAnimationNodeSupplier> xAnimNodeSupplier(mxDrawPage, UNO_QUERY);
        if (!xAnimNodeSupplier.is())
            return;

        Reference<XAnimationNode> xRootNode = xAnimNodeSupplier->getAnimationNode();
        if (xRootNode.is())
        {
            // first check if there are no animations
            Reference<XEnumerationAccess> xEnumerationAccess(xRootNode, UNO_QUERY_THROW);
            Reference<XEnumeration> xEnumeration(xEnumerationAccess->createEnumeration(),
                                                 css::uno::UNO_SET_THROW);
            if (xEnumeration->hasMoreElements())
            {
                // first child node may be an empty main sequence, check this
                Reference<XAnimationNode> xMainNode(xEnumeration->nextElement(), UNO_QUERY_THROW);
                Reference<XEnumerationAccess> xMainEnumerationAccess(xMainNode, UNO_QUERY_THROW);
                Reference<XEnumeration> xMainEnumeration(
                    xMainEnumerationAccess->createEnumeration(), css::uno::UNO_SET_THROW);

                // only export if the main sequence is not empty or if there are additional
                // trigger sequences
                mbHasEffects
                    = xMainEnumeration->hasMoreElements() || xEnumeration->hasMoreElements();
            }
        }
        if (mbHasEffects)
            mxRootNode = std::move(xRootNode);
    }
    catch (const RuntimeException&)
    {
        TOOLS_WARN_EXCEPTION("sd", "unomodel: AnimationsExporter");
    }
}

template <typename EnumT, size_t N>
constexpr bool convertEnum(OStringBuffer& rBuffer, EnumT nValue,
                           const frozen::unordered_map<EnumT, std::string_view, N>& rMap)
{
    auto iterator = rMap.find(nValue);
    if (iterator == rMap.end())
        return false;
    rBuffer.append(iterator->second);
    return true;
}

void convertDouble(OStringBuffer& rBuffer, double fValue)
{
        ::rtl::math::doubleToStringBuffer(rBuffer, fValue, rtl_math_StringFormat_Automatic,
                                          rtl_math_DecimalPlaces_Max, '.', true);
}

void convertBool(OStringBuffer& rBuffer, bool bValue)
{
    rBuffer.append( bValue );
}

void convertPath(OStringBuffer& sTmp, const Any& rPath)
{
    OUString aStr;
    rPath >>= aStr;
    sTmp = aStr.toUtf8();
}

void convertColor(OStringBuffer& rBuffer, sal_Int32 nColor)
{
    OUStringBuffer aUBuffer;
    ::sax::Converter::convertColor(aUBuffer, nColor);
    rBuffer.append(aUBuffer.makeStringAndClear().toUtf8());
}

void convertColor(OStringBuffer& rBuffer, const Any& rValue)
{
    sal_Int32 nColor = 0;
    if (rValue >>= nColor)
    {
        convertColor(rBuffer, nColor);
    }
    else
    {
        Sequence<double> aHSL;
        if ((rValue >>= aHSL) && (aHSL.getLength() == 3))
        {
            rBuffer.append("hsl(" + OString::number(aHSL[0]) + ","
                           + OString::number(aHSL[1] * 100.0) + "%,"
                           + OString::number(aHSL[2] * 100.0) + "%)");
        }
    }
}

bool isValidNode(const Reference<XAnimationNode>& xNode)
{
    if (xNode.is())
    {
        sal_Int16 nNodeType = xNode->getType();
        auto iterator = constAnimationNodeTypeToString.find(nNodeType);
        return iterator != constAnimationNodeTypeToString.end();
    }
    return false;
}

SdrObject* getObjectForShape(uno::Reference<drawing::XShape> const& xShape)
{
    if (!xShape.is())
        return nullptr;
    SvxShape* pShape = comphelper::getFromUnoTunnel<SvxShape>(xShape);
    if (pShape)
        return pShape->GetSdrObject();
    return nullptr;
}

SdrObject* getTargetObject(const uno::Any& aTargetAny)
{
    SdrObject* pObject = nullptr;
    uno::Reference<drawing::XShape> xShape;

    if ((aTargetAny >>= xShape) && xShape.is())
    {
        pObject = getObjectForShape(xShape);
    }
    else // if target is not a shape - could be paragraph target containing a shape
    {
        presentation::ParagraphTarget aParagraphTarget;
        if ((aTargetAny >>= aParagraphTarget) && aParagraphTarget.Shape.is())
        {
            pObject = getObjectForShape(aParagraphTarget.Shape);
        }
    }

    return pObject;
}

bool isNodeTargetInShapeGroup(const Reference<XAnimationNode>& xNode)
{
    Reference<XAnimate> xAnimate(xNode, UNO_QUERY);
    if (xAnimate.is())
    {
        SdrObject* pObject = getTargetObject(xAnimate->getTarget());
        if (pObject)
            return pObject->getParentSdrObjectFromSdrObject() != nullptr;
    }
    return false;
}

bool isNodeTargetAGroup(const Reference<XAnimationNode>& xNode)
{
    Reference<XAnimate> xAnimate(xNode, UNO_QUERY);
    if (xAnimate.is())
    {
        SdrObject* pObject = getTargetObject(xAnimate->getTarget());
        if (pObject)
            return pObject->getChildrenOfSdrObject() != nullptr;
    }
    return false;
}

bool isEffectValidForTarget(const Reference<XAnimationNode>& xNode)
{
    const Sequence<NamedValue> aUserData(xNode->getUserData());
    for (const auto& rValue : aUserData)
    {
        if (!IsXMLToken(rValue.Name, XML_PRESET_ID))
            continue;

        OUString aPresetId;
        if (rValue.Value >>= aPresetId)
        {
            if (constNonValidEffectsForGroupSet.find(aPresetId.toUtf8())
                != constNonValidEffectsForGroupSet.end())
            {
                // it's in the list, so we need to check if the effect target is a group or not
                Reference<XTimeContainer> xContainer(xNode, UNO_QUERY);
                if (xContainer.is())
                {
                    Reference<XEnumerationAccess> xEnumerationAccess(xContainer, UNO_QUERY);
                    Reference<XEnumeration> xEnumeration = xEnumerationAccess->createEnumeration();

                    // target is the same for all children, check the first one
                    if (xEnumeration.is() && xEnumeration->hasMoreElements())
                    {
                        Reference<XAnimationNode> xChildNode(xEnumeration->nextElement(),
                                                             UNO_QUERY);
                        if (isNodeTargetAGroup(xChildNode))
                            return false;
                    }
                }
            }
        }
        // preset id found and checked, we can exit
        break;
    }
    return true;
}

void AnimationsExporter::exportAnimations()
{
    if (!mxDrawPage.is() || !mxPageProps.is() || !mxRootNode.is() || !hasEffects())
        return;

    if (isValidNode(mxRootNode))
    {
        auto aNode = mrWriter.startNode("root");
        exportNodeImpl(mxRootNode);
    }
}

void AnimationsExporter::exportNode(const Reference<XAnimationNode>& xNode)
{
    // afaics, when a shape is part of a group any applied effect is ignored
    // moreover, some kind of effect, like the ones based on color animations,
    // is ignored when applied to a group
    if (!isValidNode(xNode) || isNodeTargetInShapeGroup(xNode) || !isEffectValidForTarget(xNode))
        return;
    auto aStruct = mrWriter.startStruct();
    exportNodeImpl(xNode);
}

void AnimationsExporter::exportNodeImpl(const Reference<XAnimationNode>& xNode)
{
    try
    {
        std::string sId = GetInterfaceHash(xNode);
        mrWriter.put("id", sId);
        sal_Int16 nNodeType = xNode->getType();
        auto iterator = constAnimationNodeTypeToString.find(nNodeType);
        assert(iterator != constAnimationNodeTypeToString.end() && "must be previously checked with isValidNode");
        mrWriter.put("nodeName", iterator->second);

        // common properties
        OStringBuffer sTmp;
        Any aTemp;
        double fTemp = 0;
        sal_Int16 nTemp;

        aTemp = xNode->getBegin();
        if (aTemp.hasValue())
        {
            convertTiming(sTmp, aTemp);
            mrWriter.put("begin", sTmp.makeStringAndClear());
        }
        aTemp = xNode->getDuration();
        if (aTemp.hasValue())
        {
            if (aTemp >>= fTemp)
            {
                convertDouble(sTmp, fTemp);
                sTmp.append('s');
                mrWriter.put("dur", sTmp.makeStringAndClear());
            }
            else
            {
                Timing eTiming;
                if (aTemp >>= eTiming)
                {
                    mrWriter.put("dur", eTiming == Timing_INDEFINITE ? "indefinite" : "media");
                }
            }
        }
        aTemp = xNode->getEnd();
        if (aTemp.hasValue())
        {
            convertTiming(sTmp, aTemp);
            mrWriter.put("end", sTmp.makeStringAndClear());
        }
        nTemp = xNode->getFill();
        if (nTemp != AnimationFill::DEFAULT)
        {
            convertEnum(sTmp, nTemp, constFillToString);
            mrWriter.put("fill", sTmp.makeStringAndClear());
        }
        nTemp = xNode->getFillDefault();
        if (nTemp != AnimationFill::INHERIT)
        {
            convertEnum(sTmp, nTemp, constFillToString);
            mrWriter.put("fillDefault", sTmp.makeStringAndClear());
        }
        nTemp = xNode->getRestart();
        if (nTemp != AnimationRestart::DEFAULT)
        {
            convertEnum(sTmp, nTemp, constRestartToString);
            mrWriter.put("restart", sTmp.makeStringAndClear());
        }
        nTemp = xNode->getRestartDefault();
        if (nTemp != AnimationRestart::INHERIT)
        {
            convertEnum(sTmp, nTemp, constRestartToString);
            mrWriter.put("restartDefault", sTmp.makeStringAndClear());
        }
        fTemp = xNode->getAcceleration();
        if (fTemp != 0.0)
        {
            convertDouble(sTmp, fTemp);
            mrWriter.put("accelerate", sTmp.makeStringAndClear());
        }
        fTemp = xNode->getDecelerate();
        if (fTemp != 0.0)
        {
            convertDouble(sTmp, fTemp);
            mrWriter.put("decelerate", sTmp.makeStringAndClear());
        }
        bool bTemp = xNode->getAutoReverse();
        if (bTemp)
        {
            convertBool(sTmp, bTemp);
            mrWriter.put("autoreverse", sTmp.makeStringAndClear());
        }
        aTemp = xNode->getRepeatCount();
        if (aTemp.hasValue())
        {
            Timing eTiming;
            if ((aTemp >>= eTiming) && (eTiming == Timing_INDEFINITE))
            {
                mrWriter.put("repeatCount", "indefinite");
            }
            else if (aTemp >>= fTemp)
            {
                convertDouble(sTmp, fTemp);
                mrWriter.put("repeatCount", sTmp.makeStringAndClear());
            }
        }
        aTemp = xNode->getRepeatDuration();
        if (aTemp.hasValue())
        {
            Timing eTiming;
            if ((aTemp >>= eTiming) && (eTiming == Timing_INDEFINITE))
            {
                mrWriter.put("repeatDur", "indefinite");
            }
            else if (aTemp >>= fTemp)
            {
                convertDouble(sTmp, fTemp);
                mrWriter.put("repeatDur", sTmp.makeStringAndClear());
            }
        }
        aTemp = xNode->getEndSync();
        if (aTemp.hasValue() && (aTemp >>= nTemp))
        {
            convertEnum(sTmp, nTemp, constEndSyncToString);
            mrWriter.put("endSync", sTmp.makeStringAndClear());
        }

        sal_Int16 nContainerNodeType = EffectNodeType::DEFAULT;
        const Sequence<NamedValue> aUserData(xNode->getUserData());
        for (const auto& rValue : aUserData)
        {
            if (IsXMLToken(rValue.Name, XML_NODE_TYPE))
            {
                if ((rValue.Value >>= nContainerNodeType)
                    && (nContainerNodeType != EffectNodeType::DEFAULT))
                {
                    convertEnum(sTmp, nContainerNodeType, constEffectNodeTypeToString);
                    mrWriter.put("nodeType", sTmp.makeStringAndClear());
                }
            }
            else if (IsXMLToken(rValue.Name, XML_PRESET_ID))
            {
                OUString aPresetId;
                if (rValue.Value >>= aPresetId)
                {
                    mrWriter.put("presetId", aPresetId);
                }
            }
            else if (IsXMLToken(rValue.Name, XML_PRESET_SUB_TYPE))
            {
                OUString aPresetSubType;
                if (rValue.Value >>= aPresetSubType)
                {
                    mrWriter.put("presetSubType", aPresetSubType);
                }
            }
            else if (IsXMLToken(rValue.Name, XML_PRESET_CLASS))
            {
                sal_Int16 nEffectPresetClass = sal_uInt16(0);
                if (rValue.Value >>= nEffectPresetClass)
                {
                    convertEnum(sTmp, nEffectPresetClass, constEffectPresetClassToString);
                    mrWriter.put("presetClass", sTmp.makeStringAndClear());
                }
            }
            else if (IsXMLToken(rValue.Name, XML_MASTER_ELEMENT))
            {
                Reference<XInterface> xMaster;
                rValue.Value >>= xMaster;
                if (xMaster.is())
                {
                    const std::string aIdentifier(GetInterfaceHash(xMaster));
                    if (!aIdentifier.empty())
                        mrWriter.put("masterElement", aIdentifier);
                }
            }
            else if (IsXMLToken(rValue.Name, XML_GROUP_ID))
            {
                sal_Int32 nGroupId = 0;
                if (rValue.Value >>= nGroupId)
                    mrWriter.put("groupId", nGroupId);
            }
            else
            {
                OUString aTmp;
                if (rValue.Value >>= aTmp)
                    mrWriter.put(rValue.Name, aTmp);
            }
        }

        switch (nNodeType)
        {
            case AnimationNodeType::PAR:
            case AnimationNodeType::SEQ:
            case AnimationNodeType::ITERATE:
            {
                Reference<XTimeContainer> xContainer(xNode, UNO_QUERY_THROW);
                exportContainer(xContainer);
            }
            break;

            case AnimationNodeType::ANIMATE:
            case AnimationNodeType::SET:
            case AnimationNodeType::ANIMATEMOTION:
            case AnimationNodeType::ANIMATEPHYSICS:
            case AnimationNodeType::ANIMATECOLOR:
            case AnimationNodeType::ANIMATETRANSFORM:
            case AnimationNodeType::TRANSITIONFILTER:
            {
                Reference<XAnimate> xAnimate(xNode, UNO_QUERY_THROW);
                exportAnimate(xAnimate);
            }
            break;
            case AnimationNodeType::AUDIO:
            {
                SAL_WARN("sd", "AnimationsExporter::exportNode(): Audio Node not supported.");
            }
            break;
            case AnimationNodeType::COMMAND:
            {
                SAL_WARN("sd", "AnimationsExporter::exportNode(): Command Node not supported.");
            }
            break;
            default:
            {
                OSL_FAIL(
                    "sd unomodel: AnimationsExporter::exportNode(), invalid AnimationNodeType!");
            }
        }
    }
    catch (const RuntimeException&)
    {
        TOOLS_WARN_EXCEPTION("sd", "unomodel: AnimationsExporter");
    }
}

void AnimationsExporter::convertTiming(OStringBuffer& sTmp, const Any& rValue)
{
    if (!rValue.hasValue())
        return;

    if (auto pSequence = o3tl::tryAccess<Sequence<Any>>(rValue))
    {
        const sal_Int32 nLength = pSequence->getLength();
        sal_Int32 nElement;
        const Any* pAny = pSequence->getConstArray();

        OStringBuffer sTmp2;

        for (nElement = 0; nElement < nLength; nElement++, pAny++)
        {
            if (!sTmp.isEmpty())
                sTmp.append(';');
            convertTiming(sTmp2, *pAny);
            sTmp.append(sTmp2);
            sTmp2.setLength(0);
        }
    }
    else if (auto x = o3tl::tryAccess<double>(rValue))
    {
        sTmp.append(*x);
        sTmp.append('s');
    }
    else if (auto pTiming = o3tl::tryAccess<Timing>(rValue))
    {
        const auto svTiming = (*pTiming == Timing_MEDIA)
                                  ? constTimingToString.at(Timing_MEDIA)
                                  : constTimingToString.at(Timing_INDEFINITE);
        sTmp.append(svTiming);
    }
    else if (auto pEvent = o3tl::tryAccess<Event>(rValue))
    {
        OStringBuffer sTmp2;

        if (pEvent->Trigger != EventTrigger::NONE)
        {
            if (pEvent->Source.hasValue())
            {
                OStringBuffer aTriggerBuffer;
                // hash must not start with a digit or on client it is parsed as a time in seconds
                aTriggerBuffer.append("id");
                anim::convertTarget(aTriggerBuffer, pEvent->Source);
                OString sTriggerHash(aTriggerBuffer.makeStringAndClear());
                sTmp.append(sTriggerHash);
                sTmp.append('.');
                appendTrigger(pEvent->Source, sTriggerHash);
            }

            convertEnum(sTmp2, pEvent->Trigger, constEventTriggerToString);

            sTmp.append(sTmp2);
            sTmp2.setLength(0);
        }

        if (pEvent->Offset.hasValue())
        {
            convertTiming(sTmp2, pEvent->Offset);

            if (!sTmp.isEmpty())
                sTmp.append('+');

            sTmp.append(sTmp2);
            sTmp2.setLength(0);
        }
    }
    else
    {
        OSL_FAIL("sd.unomodel: AnimationsExporter::convertTiming, invalid value type!");
    }
}

void AnimationsExporter::appendTrigger(const css::uno::Any& rTarget, const OString& rTriggerHash)
{
    css::uno::Reference<css::uno::XInterface> xRef;
    rTarget >>= xRef;

    uno::Reference<drawing::XShape> xShape(xRef, uno::UNO_QUERY);
    if (!xShape.is())
    {
        if (auto xParagraphTarget = o3tl::tryAccess<css::presentation::ParagraphTarget>(rTarget))
        {
            xShape = xParagraphTarget->Shape;
        }
    }
    if (xShape.is())
    {
        auto* pObject = SdrObject::getSdrObjectFromXShape(xShape);
        maEventTriggerSet[pObject] = rTriggerHash;
    }
}

void AnimationsExporter::exportTriggersImpl(const uno::Reference<drawing::XShapes>& xShapes) const
{
    if (!xShapes.is())
        return;

    sal_Int32 nCount = xShapes->getCount();
    for (sal_Int32 i = 0; i < nCount; ++i)
    {
        auto xObject = xShapes->getByIndex(i);
        uno::Reference<drawing::XShape> xShape(xObject, uno::UNO_QUERY);
        if (!xShape.is())
            continue;

        auto* pObject = SdrObject::getSdrObjectFromXShape(xShape);
        if (maEventTriggerSet.find(pObject) == maEventTriggerSet.end())
            continue;
        {
            auto aShape = mrWriter.startStruct();
            mrWriter.put("hash", maEventTriggerSet.at(pObject));
            {
                auto const& rRectangle = pObject->GetLogicRect();
                auto aRectangle
                    = o3tl::convert(rRectangle, o3tl::Length::mm100, o3tl::Length::twip);
                auto aRect = mrWriter.startNode("bounds");
                mrWriter.put("x", aRectangle.Left());
                mrWriter.put("y", aRectangle.Top());
                mrWriter.put("width", aRectangle.GetWidth());
                mrWriter.put("height", aRectangle.GetHeight());
            }
        }
    }
}

void AnimationsExporter::exportTriggers() const
{
    uno::Reference<drawing::XShapes> const xShapes(mxDrawPage, uno::UNO_QUERY_THROW);
    if (!xShapes.is())
        return;

    auto aTriggerList = mrWriter.startArray("triggers");
    exportTriggersImpl(xShapes);
}

void AnimationsExporter::convertValue(XMLTokenEnum eAttributeName, OStringBuffer& sTmp,
                                      const Any& rValue) const
{
    if (!rValue.hasValue())
        return;

    if (auto pValuePair = o3tl::tryAccess<ValuePair>(rValue))
    {
        OStringBuffer sTmp2;
        convertValue(eAttributeName, sTmp, pValuePair->First);
        sTmp.append(',');
        convertValue(eAttributeName, sTmp2, pValuePair->Second);
        sTmp.append(sTmp2);
    }
    else if (auto pSequence = o3tl::tryAccess<Sequence<Any>>(rValue))
    {
        const sal_Int32 nLength = pSequence->getLength();
        sal_Int32 nElement;
        const Any* pAny = pSequence->getConstArray();

        OStringBuffer sTmp2;

        for (nElement = 0; nElement < nLength; nElement++, pAny++)
        {
            if (!sTmp.isEmpty())
                sTmp.append(';');
            convertValue(eAttributeName, sTmp2, *pAny);
            sTmp.append(sTmp2);
            sTmp2.setLength(0);
        }
    }
    else
    {
        switch (eAttributeName)
        {
            case XML_X:
            case XML_Y:
            case XML_WIDTH:
            case XML_HEIGHT:
            case XML_ANIMATETRANSFORM:
            case XML_ANIMATEMOTION:
            case XML_ANIMATEPHYSICS:
            {
                if (auto sValue = o3tl::tryAccess<OUString>(rValue))
                {
                    sTmp.append(sValue->toUtf8());
                }
                else if (auto aValue = o3tl::tryAccess<double>(rValue))
                {
                    sTmp.append(*aValue);
                }
                else
                {
                    OSL_FAIL("sd::AnimationsExporter::convertValue(), invalid value type!");
                }
                return;
            }
            case XML_SKEWX:
            case XML_ROTATE:
            case XML_OPACITY:
            case XML_TRANSITIONFILTER:
                if (auto aValue = o3tl::tryAccess<double>(rValue))
                {
                    sTmp.append(*aValue);
                }
                break;
            case XML_TEXT_ROTATION_ANGLE:
                if (auto aValue = o3tl::tryAccess<sal_Int16>(rValue))
                {
                    // on win and armv7 platforms compiler complains
                    // that append(sal_Int16) is ambiguous
                    sTmp.append(static_cast<sal_Int32>(*aValue));
                }
                break;
            case XML_FILL_COLOR:
            case XML_STROKE_COLOR:
            case XML_DIM:
            case XML_COLOR:
            {
                convertColor(sTmp, rValue);
            }
            break;
            case XML_FILL:
                if (auto aValue = o3tl::tryAccess<drawing::FillStyle>(rValue))
                {
                    convertEnum(sTmp, *aValue, constFillStyleToString);
                }
                break;
            case XML_STROKE:
                if (auto aValue = o3tl::tryAccess<drawing::LineStyle>(rValue))
                {
                    convertEnum(sTmp, *aValue, constLineStyleToString);
                }
                break;
            case XML_FONTSIZE:
                if (auto aValue = o3tl::tryAccess<double>(rValue))
                {
                    double fValue = *aValue * 100;
                    fValue += fValue > 0 ? 0.5 : -0.5;
                    auto nValue = static_cast<sal_Int32>(fValue);
                    sTmp.append(nValue); // percent
                }
                break;
            case XML_FONT_WEIGHT:
            case XML_FONT_STYLE:
            case XML_TEXT_UNDERLINE:
                SAL_WARN("sd", "AnimationsExporter::convertValue(): value type "
                                   << GetXMLToken(eAttributeName) << " not supported");
                break;
            case XML_VISIBILITY:
                if (auto aValue = o3tl::tryAccess<bool>(rValue))
                {
                    OUString sValue = *aValue ? GetXMLToken(XML_VISIBLE) : GetXMLToken(XML_HIDDEN);
                    sTmp.append(sValue.toUtf8());
                }
                break;
            default:
                OSL_FAIL("unomodel: AnimationsExporter::convertValue(), invalid AttributeName!");
        }
    }
}

void AnimationsExporter::exportContainer(const Reference<XTimeContainer>& xContainer)
{
    try
    {
        const sal_Int32 nNodeType = xContainer->getType();

        if (nNodeType == AnimationNodeType::ITERATE)
        {
            OStringBuffer sTmp;
            Reference<XIterateContainer> xIter(xContainer, UNO_QUERY_THROW);

            Any aTemp(xIter->getTarget());
            if (aTemp.hasValue())
            {
                anim::convertTarget(sTmp, aTemp);
                mrWriter.put("targetElement", sTmp.makeStringAndClear());
            }
            sal_Int16 nTemp = xIter->getSubItem();
            if (nTemp)
            {
                convertEnum(sTmp, nTemp, constSubItemToString);
                mrWriter.put("subItem", sTmp.makeStringAndClear());
            }
            nTemp = xIter->getIterateType();
            if (nTemp)
            {
                convertEnum(sTmp, nTemp, constIterateTypeToString);
                mrWriter.put("iterateType", sTmp.makeStringAndClear());
            }
            double fTemp = xIter->getIterateInterval();
            if (fTemp != 0)
            {
                OUStringBuffer buf;
                ::sax::Converter::convertDuration(buf, fTemp / (24 * 60 * 60));
                mrWriter.put("iterateInterval", sTmp.makeStringAndClear());
            }
        }

        auto anArray = mrWriter.startArray("children");

        Reference<XEnumerationAccess> xEnumerationAccess(xContainer, UNO_QUERY_THROW);
        Reference<XEnumeration> xEnumeration(xEnumerationAccess->createEnumeration(),
                                             css::uno::UNO_SET_THROW);
        while (xEnumeration->hasMoreElements())
        {
            Reference<XAnimationNode> xChildNode(xEnumeration->nextElement(), UNO_QUERY_THROW);
            exportNode(xChildNode);
        }
    }
    catch (const RuntimeException&)
    {
        TOOLS_WARN_EXCEPTION("sd", "unomodel: AnimationsExporter");
    }
}

void AnimationsExporter::exportAnimate(const Reference<XAnimate>& xAnimate)
{
    try
    {
        const sal_Int16 nNodeType = xAnimate->getType();

        OStringBuffer sTmp;
        sal_Int16 nTemp;
        bool bTemp;

        Any aTemp(xAnimate->getTarget());
        if (aTemp.hasValue())
        {
            anim::convertTarget(sTmp, aTemp);
            mrWriter.put("targetElement", sTmp.makeStringAndClear());
            SdrObject* pObject = getTargetObject(aTemp);
            if (pObject)
            {
                mrWriter.put("title", pObject->GetTitle());
            }
        }
        nTemp = xAnimate->getSubItem();
        if (nTemp)
        {
            convertEnum(sTmp, nTemp, constSubItemToString);
            mrWriter.put("subItem", sTmp.makeStringAndClear());
        }

        XMLTokenEnum eAttributeName = XML_TOKEN_INVALID;
        if (nNodeType == AnimationNodeType::TRANSITIONFILTER)
        {
            eAttributeName = XML_TRANSITIONFILTER;
        }
        else if (nNodeType == AnimationNodeType::ANIMATETRANSFORM)
        {
            eAttributeName = XML_ANIMATETRANSFORM;
        }
        else if (nNodeType == AnimationNodeType::ANIMATEMOTION)
        {
            eAttributeName = XML_ANIMATEMOTION;
        }
        else if (nNodeType == AnimationNodeType::ANIMATEPHYSICS)
        {
            eAttributeName = XML_ANIMATEPHYSICS;
        }
        else
        {
            OString sTemp(xAnimate->getAttributeName().toUtf8());
            if (!sTemp.isEmpty())
            {
                auto iterator = constAttributeNameToXMLEnum.find(sTemp);
                if (iterator != constAttributeNameToXMLEnum.end())
                {
                    eAttributeName = iterator->second;
                    mrWriter.put("attributeName", sTemp);
                }
                else
                {
                    mrWriter.put("attributeName", "invalid");
                }
            }
        }

        Sequence<Any> aValues(xAnimate->getValues());
        if (aValues.hasElements())
        {
            aTemp <<= aValues;
            convertValue(eAttributeName, sTmp, aTemp);
            mrWriter.put("values", sTmp.makeStringAndClear());
        }
        else
        {
            aTemp = xAnimate->getFrom();
            if (aTemp.hasValue())
            {
                convertValue(eAttributeName, sTmp, aTemp);
                mrWriter.put("from", sTmp.makeStringAndClear());
            }

            aTemp = xAnimate->getBy();
            if (aTemp.hasValue())
            {
                convertValue(eAttributeName, sTmp, aTemp);
                mrWriter.put("by", sTmp.makeStringAndClear());
            }

            aTemp = xAnimate->getTo();
            if (aTemp.hasValue())
            {
                convertValue(eAttributeName, sTmp, aTemp);
                mrWriter.put("to", sTmp.makeStringAndClear());
            }
        }

        if (nNodeType != AnimationNodeType::SET)
        {
            const Sequence<double> aKeyTimes(xAnimate->getKeyTimes());
            if (aKeyTimes.hasElements())
            {
                for (const auto& rKeyTime : aKeyTimes)
                {
                    if (!sTmp.isEmpty())
                        sTmp.append(';');

                    sTmp.append(rKeyTime);
                }
                mrWriter.put("keyTimes", sTmp.makeStringAndClear());
            }

            OUString sTemp(xAnimate->getFormula());
            if (!sTemp.isEmpty())
            {
                mrWriter.put("formula", sTemp);
            }

            if ((nNodeType != AnimationNodeType::TRANSITIONFILTER)
                && (nNodeType != AnimationNodeType::AUDIO))
            {
                // calcMode  = "discrete | linear | paced | spline"
                nTemp = xAnimate->getCalcMode();
                if (((nNodeType == AnimationNodeType::ANIMATEMOTION)
                     && (nTemp != AnimationCalcMode::PACED))
                    || ((nNodeType != AnimationNodeType::ANIMATEMOTION)
                        && (nTemp != AnimationCalcMode::LINEAR)))
                {
                    convertEnum(sTmp, nTemp, constCalcModeToString);
                    mrWriter.put("calcMode", sTmp.makeStringAndClear());
                }

                bTemp = xAnimate->getAccumulate();
                if (bTemp)
                {
                    mrWriter.put("accumulate", "sum");
                }

                nTemp = xAnimate->getAdditive();
                if (nTemp != AnimationAdditiveMode::REPLACE)
                {
                    convertEnum(sTmp, nTemp, constAdditiveModeToString);
                    mrWriter.put("additive", sTmp.makeStringAndClear());
                }
            }

            const Sequence<TimeFilterPair> aTimeFilter(xAnimate->getTimeFilter());
            if (aTimeFilter.hasElements())
            {
                for (const auto& rPair : aTimeFilter)
                {
                    if (!sTmp.isEmpty())
                        sTmp.append(';');

                    sTmp.append(OString::number(rPair.Time) + ","
                                + OString::number(rPair.Progress));
                }
                mrWriter.put("keySplines", sTmp.makeStringAndClear());
            }
        }

        switch (nNodeType)
        {
            case AnimationNodeType::ANIMATEMOTION:
            {
                Reference<XAnimateMotion> xAnimateMotion(xAnimate, UNO_QUERY_THROW);

                aTemp = xAnimateMotion->getPath();
                if (aTemp.hasValue())
                {
                    convertPath(sTmp, aTemp);
                    mrWriter.put("path", sTmp.makeStringAndClear());
                }
            }
            break;
            case AnimationNodeType::ANIMATEPHYSICS:
            {
                SAL_WARN(
                    "sd",
                    "unomodel: AnimationsExporter::exportAnimate(): AnimatePhysics not supported");
            }
            break;
            case AnimationNodeType::ANIMATECOLOR:
            {
                Reference<XAnimateColor> xAnimateColor(xAnimate, UNO_QUERY_THROW);

                nTemp = xAnimateColor->getColorInterpolation();
                mrWriter.put("colorInterpolation",
                             (nTemp == AnimationColorSpace::RGB) ? "rgb" : "hsl");

                bTemp = xAnimateColor->getDirection();
                mrWriter.put("colorInterpolationDirection",
                             bTemp ? "clockwise" : "counterClockwise");
            }
            break;
            case AnimationNodeType::ANIMATETRANSFORM:
            {
                mrWriter.put("attributeName", "transform");

                Reference<XAnimateTransform> xTransform(xAnimate, UNO_QUERY_THROW);
                nTemp = xTransform->getTransformType();
                convertEnum(sTmp, nTemp, constTransformTypeToString);
                mrWriter.put("transformType", sTmp.makeStringAndClear());
            }
            break;
            case AnimationNodeType::TRANSITIONFILTER:
            {
                Reference<XTransitionFilter> xTransitionFilter(xAnimate, UNO_QUERY);

                sal_Int16 nTransition = xTransitionFilter->getTransition();
                convertEnum(sTmp, nTransition, constTransitionTypeToString);
                mrWriter.put("transitionType", sTmp.makeStringAndClear());

                sal_Int16 nSubtype = xTransitionFilter->getSubtype();
                if (nSubtype != TransitionSubType::DEFAULT)
                {
                    convertEnum(sTmp, nSubtype, constTransitionSubTypeToString);
                    mrWriter.put("transitionSubType", sTmp.makeStringAndClear());
                }

                bTemp = xTransitionFilter->getMode();
                if (!bTemp)
                    mrWriter.put("transitionMode", "out");

                bTemp = xTransitionFilter->getDirection();
                if (!bTemp)
                    mrWriter.put("transitionDirection", "reverse");

                if ((nTransition == TransitionType::FADE)
                    && ((nSubtype == TransitionSubType::FADETOCOLOR)
                        || (nSubtype == TransitionSubType::FADEFROMCOLOR)))
                {
                    sal_Int32 nColor = xTransitionFilter->getFadeColor();
                    convertColor(sTmp, nColor);
                    mrWriter.put("transitionFadeColor", sTmp.makeStringAndClear());
                }
            }
            break;
            default:
            {
                SAL_WARN("sd",
                         "unomodel: AnimationsExporter::exportAnimate(): not supported node type: "
                             << nNodeType);
            }
        }
    }
    catch (const Exception&)
    {
        TOOLS_WARN_EXCEPTION("sd", "unomodel: AnimationsExporter");
    }
}

void GetDocStructureSlides(::tools::JsonWriter& rJsonWriter, const SdXImpressDocument* pDoc,
                           const std::map<OUString, OUString>& rArguments)
{
    auto it = rArguments.find(u"filter"_ustr);
    if (it != rArguments.end())
    {
        // If filter is present but we are filtering not to slide information
        if (!it->second.equals(u"slides"_ustr))
            return;
    }

    sal_uInt16 nPageCount = pDoc->GetDoc()->GetSdPageCount(PageKind::Standard);
    sal_uInt16 nMasterPageCount = pDoc->GetDoc()->GetMasterSdPageCount(PageKind::Standard);

    rJsonWriter.put("SlideCount", nPageCount);
    rJsonWriter.put("MasterSlideCount", nMasterPageCount);

    // write data of every master slide
    if (nMasterPageCount > 0)
    {
        auto aMasterPagesNode = rJsonWriter.startNode("MasterSlides");
        for (int nMPId = 0; nMPId < nMasterPageCount; nMPId++)
        {
            auto aMasterPageNode = rJsonWriter.startNode("MasterSlide " + std::to_string(nMPId));
            const OUString& aMName
                = pDoc->GetDoc()->GetMasterSdPage(nMPId, PageKind::Standard)->GetName();
            rJsonWriter.put("Name", aMName);
        }
    }

    // write data of every slide
    if (nPageCount > 0)
    {
        auto aPagesNode = rJsonWriter.startNode("Slides");
        for (int nPId = 0; nPId < nPageCount; nPId++)
        {
            auto aPageNode = rJsonWriter.startNode("Slide " + std::to_string(nPId));
            SdPage* pPageStandard = pDoc->GetDoc()->GetSdPage(nPId, PageKind::Standard);

            // Slide Name
            rJsonWriter.put("SlideName", pPageStandard->GetName());

            // MasterSlide Name
            const FmFormPage* pMasterPage
                = dynamic_cast<const FmFormPage*>(&pPageStandard->TRG_GetMasterPage());

            if (pMasterPage)
            {
                rJsonWriter.put("MasterSlideName", pMasterPage->GetName());
            }

            // Layout id, and name.
            AutoLayout nLayout = pPageStandard->GetAutoLayout();
            rJsonWriter.put("LayoutId", static_cast<int>(nLayout));
            rJsonWriter.put("LayoutName", SdPage::autoLayoutToString(nLayout));

            // Every Objects in the page
            int nObjCount = pPageStandard->GetObjCount();
            rJsonWriter.put("ObjectCount", nObjCount);

            if (nObjCount > 0)
            {
                auto aObjectsNode = rJsonWriter.startNode("Objects");
                for (int nOId = 0; nOId < nObjCount; nOId++)
                {
                    auto aObjectNode = rJsonWriter.startNode("Objects " + std::to_string(nOId));
                    SdrObject* pSdrObj = pPageStandard->GetObj(nOId);
                    SdrTextObj* pSdrTxtObj = DynCastSdrTextObj(pSdrObj);
                    if (pSdrTxtObj && pSdrTxtObj->HasText())
                    {
                        sal_Int32 nTextCount = pSdrTxtObj->getTextCount();
                        rJsonWriter.put("TextCount", nTextCount);
                        if (nTextCount > 0)
                        {
                            auto aTextsNode = rJsonWriter.startNode("Texts");
                            for (int nTId = 0; nTId < nTextCount; nTId++)
                            {
                                auto aTextNode
                                    = rJsonWriter.startNode("Text " + std::to_string(nTId));
                                SdrText* pSdrTxt = pSdrTxtObj->getText(nTId);
                                OutlinerParaObject* pOutlinerParaObject
                                    = pSdrTxt->GetOutlinerParaObject();

                                sal_Int32 nParaCount
                                    = pOutlinerParaObject->GetTextObject().GetParagraphCount();

                                rJsonWriter.put("ParaCount", nParaCount);
                                auto aParasNode = rJsonWriter.startArray("Paragraphs");
                                for (int nParaId = 0; nParaId < nParaCount; nParaId++)
                                {
                                    OUString aParaStr(
                                        pOutlinerParaObject->GetTextObject().GetText(nParaId));

                                    rJsonWriter.putSimpleValue(aParaStr);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

} // end anonymous namespace

SdUnoForbiddenCharsTable::SdUnoForbiddenCharsTable( SdrModel* pModel )
: SvxUnoForbiddenCharsTable( pModel->GetForbiddenCharsTable() ), mpModel( pModel )
{
    StartListening( *pModel );
}

void SdUnoForbiddenCharsTable::onChange()
{
    if( mpModel )
    {
        mpModel->ReformatAllTextObjects();
    }
}

SdUnoForbiddenCharsTable::~SdUnoForbiddenCharsTable()
{
    SolarMutexGuard g;

    if( mpModel )
        EndListening( *mpModel );
}

void SdUnoForbiddenCharsTable::Notify( SfxBroadcaster&, const SfxHint& rHint ) noexcept
{
    if (rHint.GetId() != SfxHintId::ThisIsAnSdrHint)
        return;
    const SdrHint* pSdrHint = static_cast<const SdrHint*>( &rHint );
    if( SdrHintKind::ModelCleared == pSdrHint->GetKind() )
    {
        mpModel = nullptr;
    }
}

const sal_uInt16 WID_MODEL_LANGUAGE           =  1;
const sal_uInt16 WID_MODEL_TABSTOP            =  2;
const sal_uInt16 WID_MODEL_VISAREA            =  3;
const sal_uInt16 WID_MODEL_MAPUNIT            =  4;
const sal_uInt16 WID_MODEL_FORBCHARS          =  5;
const sal_uInt16 WID_MODEL_CONTFOCUS          =  6;
const sal_uInt16 WID_MODEL_DSGNMODE           =  7;
const sal_uInt16 WID_MODEL_BASICLIBS          =  8;
const sal_uInt16 WID_MODEL_RUNTIMEUID         =  9;
const sal_uInt16 WID_MODEL_BUILDID            = 10;
const sal_uInt16 WID_MODEL_HASVALIDSIGNATURES = 11;
const sal_uInt16 WID_MODEL_DIALOGLIBS         = 12;
const sal_uInt16 WID_MODEL_FONTS              = 13;
const sal_uInt16 WID_MODEL_INTEROPGRABBAG     = 14;
const sal_uInt16 WID_MODEL_THEME = 15;
const sal_uInt16 WID_MODEL_ALLOWLINKUPDATE    = 16;

static const SvxItemPropertySet* ImplGetDrawModelPropertySet()
{
    // Attention: the first parameter HAS TO BE sorted!!!
    const static SfxItemPropertyMapEntry aDrawModelPropertyMap_Impl[] =
    {
        { u"BuildId"_ustr,                WID_MODEL_BUILDID,            ::cppu::UnoType<OUString>::get(),                      0, 0},
        { sUNO_Prop_CharLocale,           WID_MODEL_LANGUAGE,           ::cppu::UnoType<lang::Locale>::get(),                                  0, 0},
        { sUNO_Prop_TabStop,              WID_MODEL_TABSTOP,            ::cppu::UnoType<sal_Int32>::get(),                                     0, 0},
        { sUNO_Prop_VisibleArea,          WID_MODEL_VISAREA,            ::cppu::UnoType<awt::Rectangle>::get(),                                0, 0},
        { sUNO_Prop_MapUnit,              WID_MODEL_MAPUNIT,            ::cppu::UnoType<sal_Int16>::get(),                                     beans::PropertyAttribute::READONLY, 0},
        { sUNO_Prop_ForbiddenCharacters,  WID_MODEL_FORBCHARS,          cppu::UnoType<i18n::XForbiddenCharacters>::get(), beans::PropertyAttribute::READONLY, 0},
        { sUNO_Prop_AutomContFocus,       WID_MODEL_CONTFOCUS,          cppu::UnoType<bool>::get(),                                                 0, 0},
        { sUNO_Prop_ApplyFrmDsgnMode,     WID_MODEL_DSGNMODE,           cppu::UnoType<bool>::get(),                                                 0, 0},
        { u"BasicLibraries"_ustr,         WID_MODEL_BASICLIBS,          cppu::UnoType<script::XLibraryContainer>::get(),  beans::PropertyAttribute::READONLY, 0},
        { u"DialogLibraries"_ustr,        WID_MODEL_DIALOGLIBS,         cppu::UnoType<script::XLibraryContainer>::get(),  beans::PropertyAttribute::READONLY, 0},
        { sUNO_Prop_RuntimeUID,           WID_MODEL_RUNTIMEUID,         ::cppu::UnoType<OUString>::get(),                      beans::PropertyAttribute::READONLY, 0},
        { sUNO_Prop_HasValidSignatures,   WID_MODEL_HASVALIDSIGNATURES, ::cppu::UnoType<sal_Bool>::get(),                      beans::PropertyAttribute::READONLY, 0},
        { sUNO_Prop_AllowLinkUpdate,      WID_MODEL_ALLOWLINKUPDATE,    ::cppu::UnoType<sal_Bool>::get(),                      beans::PropertyAttribute::READONLY, 0},
        { u"Fonts"_ustr,                  WID_MODEL_FONTS,              cppu::UnoType<uno::Sequence<uno::Any>>::get(),                     beans::PropertyAttribute::READONLY, 0},
        { sUNO_Prop_InteropGrabBag,       WID_MODEL_INTEROPGRABBAG,     cppu::UnoType<uno::Sequence< beans::PropertyValue >>::get(),       0, 0},
        { sUNO_Prop_Theme,                WID_MODEL_THEME,              cppu::UnoType<util::XTheme>::get(),       0, 0},
    };
    static SvxItemPropertySet aDrawModelPropertySet_Impl( aDrawModelPropertyMap_Impl, SdrObject::GetGlobalDrawObjectItemPool() );
    return &aDrawModelPropertySet_Impl;
}

// this ctor is used from the DocShell
SdXImpressDocument::SdXImpressDocument(::sd::DrawDocShell* pShell, bool bClipBoard)
:   SfxBaseModel( pShell ),
    mpDocShell( pShell ),
    mpDoc( pShell ? pShell->GetDoc() : nullptr ),
    mbDisposed(false),
    mbImpressDoc( pShell && pShell->GetDoc() && pShell->GetDoc()->GetDocumentType() == DocumentType::Impress ),
    mbClipBoard( bClipBoard ),
    mpPropSet( ImplGetDrawModelPropertySet() ),
    mbPaintTextEdit( true )
{
    if( mpDoc )
    {
        StartListening( *mpDoc );
    }
    else
    {
        OSL_FAIL("DocShell is invalid");
    }
}

SdXImpressDocument::SdXImpressDocument(SdDrawDocument* pDoc, bool bClipBoard)
:   SfxBaseModel( nullptr ),
    mpDocShell( nullptr ),
    mpDoc( pDoc ),
    mbDisposed(false),
    mbImpressDoc( pDoc && pDoc->GetDocumentType() == DocumentType::Impress ),
    mbClipBoard( bClipBoard ),
    mpPropSet( ImplGetDrawModelPropertySet() ),
    mbPaintTextEdit( true )
{
    if( mpDoc )
    {
        StartListening( *mpDoc );
    }
    else
    {
        OSL_FAIL("SdDrawDocument is invalid");
    }
}

/***********************************************************************
*                                                                      *
***********************************************************************/
SdXImpressDocument::~SdXImpressDocument() noexcept
{
}

// XInterface
uno::Any SAL_CALL SdXImpressDocument::queryInterface( const uno::Type & rType )
{
    uno::Any aAny;

    if (rType == cppu::UnoType<lang::XServiceInfo>::get())
        aAny <<= uno::Reference<lang::XServiceInfo>(this);
    else if (rType == cppu::UnoType<beans::XPropertySet>::get())
        aAny <<= uno::Reference<beans::XPropertySet>(this);
    else if (rType == cppu::UnoType<lang::XMultiServiceFactory>::get())
        aAny <<= uno::Reference<lang::XMultiServiceFactory>(this);
    else if (rType == cppu::UnoType<drawing::XDrawPageDuplicator>::get())
        aAny <<= uno::Reference<drawing::XDrawPageDuplicator>(this);
    else if (rType == cppu::UnoType<drawing::XLayerSupplier>::get())
        aAny <<= uno::Reference<drawing::XLayerSupplier>(this);
    else if (rType == cppu::UnoType<drawing::XMasterPagesSupplier>::get())
        aAny <<= uno::Reference<drawing::XMasterPagesSupplier>(this);
    else if (rType == cppu::UnoType<drawing::XDrawPagesSupplier>::get())
        aAny <<= uno::Reference<drawing::XDrawPagesSupplier>(this);
    else if (rType == cppu::UnoType<presentation::XHandoutMasterSupplier>::get())
        aAny <<= uno::Reference<presentation::XHandoutMasterSupplier>(this);
    else if (rType == cppu::UnoType<document::XLinkTargetSupplier>::get())
        aAny <<= uno::Reference<document::XLinkTargetSupplier>(this);
    else if (rType == cppu::UnoType<style::XStyleFamiliesSupplier>::get())
        aAny <<= uno::Reference<style::XStyleFamiliesSupplier>(this);
    else if (rType == cppu::UnoType<css::ucb::XAnyCompareFactory>::get())
        aAny <<= uno::Reference<css::ucb::XAnyCompareFactory>(this);
    else if (rType == cppu::UnoType<view::XRenderable>::get())
        aAny <<= uno::Reference<view::XRenderable>(this);
    else if (mbImpressDoc && rType == cppu::UnoType<presentation::XPresentationSupplier>::get())
        aAny <<= uno::Reference< presentation::XPresentationSupplier >(this);
    else if (mbImpressDoc && rType == cppu::UnoType<presentation::XCustomPresentationSupplier>::get())
        aAny <<= uno::Reference< presentation::XCustomPresentationSupplier >(this);
    else
        return SfxBaseModel::queryInterface(rType);

    return aAny;
}

void SAL_CALL SdXImpressDocument::acquire() noexcept
{
    SfxBaseModel::acquire();
}

void SAL_CALL SdXImpressDocument::release() noexcept
{
    if (osl_atomic_decrement( &m_refCount ) != 0)
        return;

    // restore reference count:
    osl_atomic_increment( &m_refCount );
    if(!mbDisposed)
    {
        try
        {
            dispose();
        }
        catch (const uno::RuntimeException&)
        {
            // don't break throw ()
            TOOLS_WARN_EXCEPTION( "sd", "" );
        }
    }
    SfxBaseModel::release();
}

// XUnoTunnel
const css::uno::Sequence< sal_Int8 > & SdXImpressDocument::getUnoTunnelId() noexcept
{
    static const comphelper::UnoIdInit theSdXImpressDocumentUnoTunnelId;
    return theSdXImpressDocumentUnoTunnelId.getSeq();
}

sal_Int64 SAL_CALL SdXImpressDocument::getSomething( const css::uno::Sequence< sal_Int8 >& rIdentifier )
{
    if (comphelper::isUnoTunnelId<SdrModel>(rIdentifier))
        return comphelper::getSomething_cast(mpDoc);

    if (comphelper::isUnoTunnelId<SfxObjectShell>(rIdentifier))
        return comphelper::getSomething_cast(mpDocShell);

    return comphelper::getSomethingImpl(rIdentifier, this,
                                        comphelper::FallbackToGetSomethingOf<SfxBaseModel>{});
}

// XTypeProvider
uno::Sequence< uno::Type > SAL_CALL SdXImpressDocument::getTypes(  )
{
    ::SolarMutexGuard aGuard;

    if( !maTypeSequence.hasElements() )
    {
        uno::Sequence< uno::Type > aTypes( SfxBaseModel::getTypes() );
        aTypes = comphelper::concatSequences(aTypes,
            uno::Sequence {
                cppu::UnoType<beans::XPropertySet>::get(),
                cppu::UnoType<lang::XServiceInfo>::get(),
                cppu::UnoType<lang::XMultiServiceFactory>::get(),
                cppu::UnoType<drawing::XDrawPageDuplicator>::get(),
                cppu::UnoType<drawing::XLayerSupplier>::get(),
                cppu::UnoType<drawing::XMasterPagesSupplier>::get(),
                cppu::UnoType<drawing::XDrawPagesSupplier>::get(),
                cppu::UnoType<document::XLinkTargetSupplier>::get(),
                cppu::UnoType<style::XStyleFamiliesSupplier>::get(),
                cppu::UnoType<css::ucb::XAnyCompareFactory>::get(),
                cppu::UnoType<view::XRenderable>::get() });
        if( mbImpressDoc )
        {
            aTypes = comphelper::concatSequences(aTypes,
                uno::Sequence {
                    cppu::UnoType<presentation::XPresentationSupplier>::get(),
                    cppu::UnoType<presentation::XCustomPresentationSupplier>::get(),
                    cppu::UnoType<presentation::XHandoutMasterSupplier>::get() });
        }
        maTypeSequence = std::move(aTypes);
    }

    return maTypeSequence;
}

uno::Sequence< sal_Int8 > SAL_CALL SdXImpressDocument::getImplementationId(  )
{
    return css::uno::Sequence<sal_Int8>();
}

/***********************************************************************
*                                                                      *
***********************************************************************/
void SdXImpressDocument::Notify( SfxBroadcaster& rBC, const SfxHint& rHint )
{
    if( mpDoc )
    {
        if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
        {
            const SdrHint* pSdrHint = static_cast<const SdrHint*>( &rHint );
            if( hasEventListeners() )
            {
                document::EventObject aEvent;
                if( SvxUnoDrawMSFactory::createEvent( mpDoc, pSdrHint, aEvent ) )
                    notifyEvent( aEvent );
            }

            if( pSdrHint->GetKind() == SdrHintKind::ModelCleared )
            {
                if( mpDoc )
                    EndListening( *mpDoc );
                mpDoc = nullptr;
                mpDocShell = nullptr;
            }
        }
        else
        {
            // did our SdDrawDocument just died?
            if(rHint.GetId() == SfxHintId::Dying)
            {
                // yes, so we ask for a new one
                if( mpDocShell )
                {
                    SdDrawDocument *pNewDoc = mpDocShell->GetDoc();

                    // is there a new one?
                    if( pNewDoc != mpDoc )
                    {
                        mpDoc = pNewDoc;
                        if(mpDoc)
                            StartListening( *mpDoc );
                    }
                }
            }
        }
    }
    SfxBaseModel::Notify( rBC, rHint );
}

void SdXImpressDocument::getCommandValues(::tools::JsonWriter& rJsonWriter, std::string_view rCommand)
{
    static constexpr OStringLiteral aExtractDocStructure(".uno:ExtractDocumentStructure");

    std::map<OUString, OUString> aMap
        = SfxLokHelper::parseCommandParameters(OUString::fromUtf8(rCommand));

    if (o3tl::starts_with(rCommand, aExtractDocStructure))
    {
        auto commentsNode = rJsonWriter.startNode("DocStructure");
        GetDocStructureSlides(rJsonWriter, this, aMap);
    }
}

/******************************************************************************
*                                                                             *
******************************************************************************/
SdPage* SdXImpressDocument::InsertSdPage( sal_uInt16 nPage, bool bDuplicate )
{
    sal_uInt16 nPageCount = mpDoc->GetSdPageCount( PageKind::Standard );
    SdrLayerAdmin& rLayerAdmin = mpDoc->GetLayerAdmin();
    SdrLayerID aBckgrnd = rLayerAdmin.GetLayerID(sUNO_LayerName_background);
    SdrLayerID aBckgrndObj = rLayerAdmin.GetLayerID(sUNO_LayerName_background_objects);

    rtl::Reference<SdPage> pStandardPage;

    if( 0 == nPageCount )
    {
        // this is only used for clipboard where we only have one page
        pStandardPage = mpDoc->AllocSdPage(false);

        Size aDefSize(21000, 29700);   // A4 portrait orientation
        pStandardPage->SetSize( aDefSize );
        mpDoc->InsertPage(pStandardPage.get(), 0);
    }
    else
    {
        // here we determine the page after which we should insert
        SdPage* pPreviousStandardPage = mpDoc->GetSdPage( std::min( static_cast<sal_uInt16>(nPageCount - 1), nPage ), PageKind::Standard );
        SdrLayerIDSet aVisibleLayers = pPreviousStandardPage->TRG_GetMasterPageVisibleLayers();
        bool bIsPageBack = aVisibleLayers.IsSet( aBckgrnd );
        bool bIsPageObj = aVisibleLayers.IsSet( aBckgrndObj );

        // AutoLayouts must be ready
        mpDoc->StopWorkStartupDelay();

        /* First we create a standard page and then a notes page. It is
           guaranteed, that after a standard page the corresponding notes page
           follows. */

        sal_uInt16 nStandardPageNum = pPreviousStandardPage->GetPageNum() + 2;
        SdPage* pPreviousNotesPage = static_cast<SdPage*>( mpDoc->GetPage( nStandardPageNum - 1 ) );
        sal_uInt16 nNotesPageNum = nStandardPageNum + 1;

        /**************************************************************
        * standard page
        **************************************************************/
        if( bDuplicate )
            pStandardPage = static_cast<SdPage*>( pPreviousStandardPage->CloneSdrPage(*mpDoc).get() );
        else
            pStandardPage = mpDoc->AllocSdPage(false);

        pStandardPage->SetSize( pPreviousStandardPage->GetSize() );
        pStandardPage->SetBorder( pPreviousStandardPage->GetLeftBorder(),
                                    pPreviousStandardPage->GetUpperBorder(),
                                    pPreviousStandardPage->GetRightBorder(),
                                    pPreviousStandardPage->GetLowerBorder() );
        pStandardPage->SetOrientation( pPreviousStandardPage->GetOrientation() );
        pStandardPage->SetName(OUString());

        // insert page after current page
        mpDoc->InsertPage(pStandardPage.get(), nStandardPageNum);

        if( !bDuplicate )
        {
            // use MasterPage of the current page
            pStandardPage->TRG_SetMasterPage(pPreviousStandardPage->TRG_GetMasterPage());
            pStandardPage->SetLayoutName( pPreviousStandardPage->GetLayoutName() );
            pStandardPage->SetAutoLayout(AUTOLAYOUT_NONE, true );
        }

        aBckgrnd = rLayerAdmin.GetLayerID(sUNO_LayerName_background);
        aBckgrndObj = rLayerAdmin.GetLayerID(sUNO_LayerName_background_objects);
        aVisibleLayers.Set(aBckgrnd, bIsPageBack);
        aVisibleLayers.Set(aBckgrndObj, bIsPageObj);
        pStandardPage->TRG_SetMasterPageVisibleLayers(aVisibleLayers);

        /**************************************************************
        * notes page
        **************************************************************/
        rtl::Reference<SdPage> pNotesPage;

        if( bDuplicate )
            pNotesPage = static_cast<SdPage*>( pPreviousNotesPage->CloneSdrPage(*mpDoc).get() );
        else
            pNotesPage = mpDoc->AllocSdPage(false);

        pNotesPage->SetSize( pPreviousNotesPage->GetSize() );
        pNotesPage->SetBorder( pPreviousNotesPage->GetLeftBorder(),
                                pPreviousNotesPage->GetUpperBorder(),
                                pPreviousNotesPage->GetRightBorder(),
                                pPreviousNotesPage->GetLowerBorder() );
        pNotesPage->SetOrientation( pPreviousNotesPage->GetOrientation() );
        pNotesPage->SetName(OUString());
        pNotesPage->SetPageKind(PageKind::Notes);

        // insert page after current page
        mpDoc->InsertPage(pNotesPage.get(), nNotesPageNum);

        if( !bDuplicate )
        {
            // use MasterPage of the current page
            pNotesPage->TRG_SetMasterPage(pPreviousNotesPage->TRG_GetMasterPage());
            pNotesPage->SetLayoutName( pPreviousNotesPage->GetLayoutName() );
            pNotesPage->SetAutoLayout(AUTOLAYOUT_NOTES, true );
        }
    }

    SetModified();

    return pStandardPage.get();
}

void SdXImpressDocument::SetModified() noexcept
{
    if( mpDoc )
        mpDoc->SetChanged();
}

// XModel
void SAL_CALL SdXImpressDocument::lockControllers(  )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    mpDoc->setLock(true);
}

void SAL_CALL SdXImpressDocument::unlockControllers(  )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    if( mpDoc->isLocked() )
    {
        mpDoc->setLock(false);
    }
}

sal_Bool SAL_CALL SdXImpressDocument::hasControllersLocked(  )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    return mpDoc->isLocked();
}

uno::Reference < container::XIndexAccess > SAL_CALL SdXImpressDocument::getViewData()
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    uno::Reference < container::XIndexAccess > xRet( SfxBaseModel::getViewData() );

    if( !xRet.is() )
    {
        const std::vector<std::unique_ptr<sd::FrameView>> &rList = mpDoc->GetFrameViewList();

        if( !rList.empty() )
        {
            xRet = new comphelper::IndexedPropertyValuesContainer();

            uno::Reference < container::XIndexContainer > xCont( xRet, uno::UNO_QUERY );
            DBG_ASSERT( xCont.is(), "SdXImpressDocument::getViewData() failed for OLE object" );
            if( xCont.is() )
            {
                for( sal_uInt32 i = 0, n = rList.size(); i < n; i++ )
                {
                    ::sd::FrameView* pFrameView = rList[ i ].get();

                    uno::Sequence< beans::PropertyValue > aSeq;
                    pFrameView->WriteUserDataSequence( aSeq );
                    xCont->insertByIndex( i, uno::Any( aSeq ) );
                }
            }
        }
    }

    return xRet;
}

void SAL_CALL SdXImpressDocument::setViewData( const uno::Reference < container::XIndexAccess >& xData )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    SfxBaseModel::setViewData( xData );
    if( !(mpDocShell && (mpDocShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED) && xData.is()) )
        return;

    const sal_Int32 nCount = xData->getCount();

    std::vector<std::unique_ptr<sd::FrameView>> &rViews = mpDoc->GetFrameViewList();

    rViews.clear();

    uno::Sequence< beans::PropertyValue > aSeq;
    for( sal_Int32 nIndex = 0; nIndex < nCount; nIndex++ )
    {
        if( xData->getByIndex( nIndex ) >>= aSeq )
        {
            std::unique_ptr<::sd::FrameView> pFrameView(new ::sd::FrameView( mpDoc ));
            pFrameView->ReadUserDataSequence( aSeq );
            rViews.push_back( std::move(pFrameView) );
        }
    }
}

// XDrawPageDuplicator
uno::Reference< drawing::XDrawPage > SAL_CALL SdXImpressDocument::duplicate( const uno::Reference< drawing::XDrawPage >& xPage )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    // get pPage from xPage and determine the Id (nPos ) afterwards
    SvxDrawPage* pSvxPage = comphelper::getFromUnoTunnel<SvxDrawPage>( xPage );
    if( pSvxPage )
    {
        SdPage* pPage = static_cast<SdPage*>( pSvxPage->GetSdrPage() );
        sal_uInt16 nPos = pPage->GetPageNum();
        nPos = ( nPos - 1 ) / 2;
        pPage = InsertSdPage( nPos, true );
        if( pPage )
        {
            uno::Reference< drawing::XDrawPage > xDrawPage( pPage->getUnoPage(), uno::UNO_QUERY );
            return xDrawPage;
        }
    }

    uno::Reference< drawing::XDrawPage > xDrawPage;
    return xDrawPage;
}

// XDrawPagesSupplier
uno::Reference< drawing::XDrawPages > SAL_CALL SdXImpressDocument::getDrawPages()
{
    ::SolarMutexGuard aGuard;

    return getSdDrawPages();
}

rtl::Reference< SdDrawPagesAccess > SdXImpressDocument::getSdDrawPages()
{
    if( nullptr == mpDoc )
        throw lang::DisposedException();

    rtl::Reference< SdDrawPagesAccess > xDrawPages( mxDrawPagesAccess );

    if( !xDrawPages.is() )
    {
        initializeDocument();
        xDrawPages = new SdDrawPagesAccess(*this);
        mxDrawPagesAccess = xDrawPages.get();
    }

    return xDrawPages;
}

// XMasterPagesSupplier
uno::Reference< drawing::XDrawPages > SAL_CALL SdXImpressDocument::getMasterPages()
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    rtl::Reference< SdMasterPagesAccess > xMasterPages( mxMasterPagesAccess );

    if( !xMasterPages.is() )
    {
        if ( !hasControllersLocked() )
            initializeDocument();
        xMasterPages = new SdMasterPagesAccess(*this);
        mxMasterPagesAccess = xMasterPages.get();
    }

    return xMasterPages;
}

// XLayerManagerSupplier
uno::Reference< container::XNameAccess > SAL_CALL SdXImpressDocument::getLayerManager(  )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    rtl::Reference< SdLayerManager >  xLayerManager( mxLayerManager );

    if( !xLayerManager.is() )
    {
        xLayerManager = new SdLayerManager(*this);
        mxLayerManager = xLayerManager.get();
    }

    return xLayerManager;
}

// XCustomPresentationSupplier
uno::Reference< container::XNameContainer > SAL_CALL SdXImpressDocument::getCustomPresentations()
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    rtl::Reference< SdXCustomPresentationAccess >  xCustomPres( mxCustomPresentationAccess );

    if( !xCustomPres.is() )
    {
        xCustomPres = new SdXCustomPresentationAccess(*this);
        mxCustomPresentationAccess = xCustomPres.get();
    }

    return xCustomPres;
}

// XPresentationSupplier
uno::Reference< presentation::XPresentation > SAL_CALL SdXImpressDocument::getPresentation()
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    return mpDoc->getPresentation();
}

// XHandoutMasterSupplier
uno::Reference< drawing::XDrawPage > SAL_CALL SdXImpressDocument::getHandoutMasterPage()
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    uno::Reference< drawing::XDrawPage > xPage;

    initializeDocument();
    SdPage* pPage = mpDoc->GetMasterSdPage(0, PageKind::Handout);
    if (pPage)
        xPage.set(pPage->getUnoPage(), uno::UNO_QUERY);
    return xPage;
}

// XMultiServiceFactory ( SvxFmMSFactory )

css::uno::Reference<css::uno::XInterface> SdXImpressDocument::create(
    OUString const & aServiceSpecifier, OUString const & referer)
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    if( aServiceSpecifier == "com.sun.star.drawing.DashTable" )
    {
        if( !mxDashTable.is() )
            mxDashTable = SvxUnoDashTable_createInstance( mpDoc );

        return mxDashTable;
    }
    if( aServiceSpecifier == "com.sun.star.drawing.GradientTable" )
    {
        if( !mxGradientTable.is() )
            mxGradientTable = SvxUnoGradientTable_createInstance( mpDoc );

        return mxGradientTable;
    }
    if( aServiceSpecifier == "com.sun.star.drawing.HatchTable" )
    {
        if( !mxHatchTable.is() )
            mxHatchTable = SvxUnoHatchTable_createInstance( mpDoc );

        return mxHatchTable;
    }
    if( aServiceSpecifier == "com.sun.star.drawing.BitmapTable" )
    {
        if( !mxBitmapTable.is() )
            mxBitmapTable = SvxUnoBitmapTable_createInstance( mpDoc );

        return mxBitmapTable;
    }
    if( aServiceSpecifier == "com.sun.star.drawing.TransparencyGradientTable" )
    {
        if( !mxTransGradientTable.is() )
            mxTransGradientTable = SvxUnoTransGradientTable_createInstance( mpDoc );

        return mxTransGradientTable;
    }
    if( aServiceSpecifier == "com.sun.star.drawing.MarkerTable" )
    {
        if( !mxMarkerTable.is() )
            mxMarkerTable = SvxUnoMarkerTable_createInstance( mpDoc );

        return mxMarkerTable;
    }
    if( aServiceSpecifier == "com.sun.star.text.NumberingRules" )
    {
        return uno::Reference< uno::XInterface >( SvxCreateNumRule( mpDoc ), uno::UNO_QUERY );
    }
    if( aServiceSpecifier == "com.sun.star.drawing.Background" )
    {
        return uno::Reference<uno::XInterface>(cppu::getXWeak(new SdUnoPageBackground(mpDoc)));
    }

    if( aServiceSpecifier == "com.sun.star.drawing.Defaults" )
    {
        if( !mxDrawingPool.is() )
            mxDrawingPool = SdUnoCreatePool( mpDoc );

        return mxDrawingPool;

    }

    if ( aServiceSpecifier == sUNO_Service_ImageMapRectangleObject )
    {
        return SvUnoImageMapRectangleObject_createInstance( ImplGetSupportedMacroItems() );
    }

    if ( aServiceSpecifier == sUNO_Service_ImageMapCircleObject )
    {
        return SvUnoImageMapCircleObject_createInstance( ImplGetSupportedMacroItems() );
    }

    if ( aServiceSpecifier == sUNO_Service_ImageMapPolygonObject )
    {
        return SvUnoImageMapPolygonObject_createInstance( ImplGetSupportedMacroItems() );
    }

    if( aServiceSpecifier == "com.sun.star.document.Settings" ||
        ( !mbImpressDoc && ( aServiceSpecifier == "com.sun.star.drawing.DocumentSettings" ) ) ||
        (  mbImpressDoc && ( aServiceSpecifier == "com.sun.star.presentation.DocumentSettings" ) ) )
    {
        return sd::DocumentSettings_createInstance( this );
    }

    if( aServiceSpecifier == "com.sun.star.text.TextField.DateTime" ||
        aServiceSpecifier == "com.sun.star.text.textfield.DateTime" )
    {
        return cppu::getXWeak(new SvxUnoTextField(text::textfield::Type::DATE));
    }

    if( aServiceSpecifier == "com.sun.star.presentation.TextField.Header" ||
        aServiceSpecifier == "com.sun.star.presentation.textfield.Header" )
    {
        return cppu::getXWeak(new SvxUnoTextField(text::textfield::Type::PRESENTATION_HEADER));
    }

    if( aServiceSpecifier == "com.sun.star.presentation.TextField.Footer" ||
        aServiceSpecifier == "com.sun.star.presentation.textfield.Footer" )
    {
        return cppu::getXWeak(new SvxUnoTextField(text::textfield::Type::PRESENTATION_FOOTER));
    }

    if( aServiceSpecifier == "com.sun.star.presentation.TextField.DateTime" ||
        aServiceSpecifier == "com.sun.star.presentation.textfield.DateTime" )
    {
        return cppu::getXWeak(new SvxUnoTextField(text::textfield::Type::PRESENTATION_DATE_TIME));
    }

    if( aServiceSpecifier == "com.sun.star.text.TextField.PageName" ||
        aServiceSpecifier == "com.sun.star.text.textfield.PageName" )
    {
        return cppu::getXWeak(new SvxUnoTextField(text::textfield::Type::PAGE_NAME));
    }

    if (aServiceSpecifier == "com.sun.star.text.TextField.DocInfo.Custom" ||
        aServiceSpecifier == "com.sun.star.text.textfield.DocInfo.Custom")
    {
        return cppu::getXWeak(new SvxUnoTextField(text::textfield::Type::DOCINFO_CUSTOM));
    }

    if( aServiceSpecifier == "com.sun.star.xml.NamespaceMap" )
    {
        static const sal_uInt16 aWhichIds[] = { SDRATTR_XMLATTRIBUTES, EE_CHAR_XMLATTRIBS, EE_PARA_XMLATTRIBS, 0 };

        return svx::NamespaceMap_createInstance( aWhichIds, &mpDoc->GetItemPool() );
    }

    // Support creation of GraphicStorageHandler and EmbeddedObjectResolver
    if (aServiceSpecifier == "com.sun.star.document.ExportGraphicStorageHandler")
    {
        return cppu::getXWeak(new SvXMLGraphicHelper(SvXMLGraphicHelperMode::Write));
    }

    if (aServiceSpecifier == "com.sun.star.document.ImportGraphicStorageHandler")
    {
        return cppu::getXWeak(new SvXMLGraphicHelper(SvXMLGraphicHelperMode::Read));
    }

    if( aServiceSpecifier == "com.sun.star.document.ExportEmbeddedObjectResolver" )
    {
        comphelper::IEmbeddedHelper* pPersist = mpDoc->GetPersist();
        if( nullptr == pPersist )
            throw lang::DisposedException();

        return cppu::getXWeak(new SvXMLEmbeddedObjectHelper( *pPersist, SvXMLEmbeddedObjectHelperMode::Write ));
    }

    if( aServiceSpecifier == "com.sun.star.document.ImportEmbeddedObjectResolver" )
    {
        comphelper::IEmbeddedHelper* pPersist = mpDoc->GetPersist();
        if( nullptr == pPersist )
            throw lang::DisposedException();

        return cppu::getXWeak(new SvXMLEmbeddedObjectHelper( *pPersist, SvXMLEmbeddedObjectHelperMode::Read ));
    }

    uno::Reference< uno::XInterface > xRet;

    if (std::u16string_view aType; aServiceSpecifier.startsWith("com.sun.star.presentation.", &aType))
    {
        SdrObjKind nType;
        // create a shape wrapper
        if( o3tl::starts_with(aType, u"TitleTextShape" ) )
        {
            nType = SdrObjKind::Text;
        }
        else if( o3tl::starts_with(aType, u"OutlinerShape" ) )
        {
            nType = SdrObjKind::Text;
        }
        else if( o3tl::starts_with(aType, u"SubtitleShape" ) )
        {
            nType = SdrObjKind::Text;
        }
        else if( o3tl::starts_with(aType, u"GraphicObjectShape" ) )
        {
            nType = SdrObjKind::Graphic;
        }
        else if( o3tl::starts_with(aType, u"PageShape" ) )
        {
            nType = SdrObjKind::Page;
        }
        else if( o3tl::starts_with(aType, u"OLE2Shape" ) )
        {
            nType = SdrObjKind::OLE2;
        }
        else if( o3tl::starts_with(aType, u"ChartShape" ) )
        {
            nType = SdrObjKind::OLE2;
        }
        else if( o3tl::starts_with(aType, u"CalcShape" ) )
        {
            nType = SdrObjKind::OLE2;
        }
        else if( o3tl::starts_with(aType, u"TableShape" ) )
        {
            nType = SdrObjKind::Table;
        }
        else if( o3tl::starts_with(aType, u"OrgChartShape" ) )
        {
            nType = SdrObjKind::OLE2;
        }
        else if( o3tl::starts_with(aType, u"NotesShape" ) )
        {
            nType = SdrObjKind::Text;
        }
        else if( o3tl::starts_with(aType, u"HandoutShape" ) )
        {
            nType = SdrObjKind::Page;
        }
        else if( o3tl::starts_with(aType, u"FooterShape" ) )
        {
            nType = SdrObjKind::Text;
        }
        else if( o3tl::starts_with(aType, u"HeaderShape" ) )
        {
            nType = SdrObjKind::Text;
        }
        else if( o3tl::starts_with(aType, u"SlideNumberShape" ) )
        {
            nType = SdrObjKind::Text;
        }
        else if( o3tl::starts_with(aType, u"DateTimeShape" ) )
        {
            nType = SdrObjKind::Text;
        }
        else if( o3tl::starts_with(aType, u"MediaShape" ) )
        {
            nType = SdrObjKind::Media;
        }
        else
        {
            throw lang::ServiceNotRegisteredException();
        }

        // create the API wrapper
        rtl::Reference<SvxShape> pShape = CreateSvxShapeByTypeAndInventor(nType, SdrInventor::Default, referer);

        // set shape type
        if( pShape && !mbClipBoard )
            pShape->SetShapeType(aServiceSpecifier);

        xRet = cppu::getXWeak(pShape.get());
    }
    else if ( aServiceSpecifier == "com.sun.star.drawing.TableShape" )
    {
        rtl::Reference<SvxShape> pShape = CreateSvxShapeByTypeAndInventor( SdrObjKind::Table, SdrInventor::Default, referer );
        if( pShape && !mbClipBoard )
            pShape->SetShapeType(aServiceSpecifier);

        xRet = cppu::getXWeak(pShape.get());
    }
    else
    {
        xRet = SvxFmMSFactory::createInstance( aServiceSpecifier );
    }

    uno::Reference< drawing::XShape > xShape( xRet, uno::UNO_QUERY );
    SvxShape* pShape = xShape.is() ? comphelper::getFromUnoTunnel<SvxShape>(xShape) : nullptr;
    if (pShape)
    {
        xRet.clear();
        new SdXShape( pShape, this );
        xRet = xShape;
        xShape.clear();
    }

    return xRet;
}

uno::Reference< uno::XInterface > SAL_CALL SdXImpressDocument::createInstance( const OUString& aServiceSpecifier )
{
    return create(aServiceSpecifier, u""_ustr);
}

css::uno::Reference<css::uno::XInterface>
SdXImpressDocument::createInstanceWithArguments(
    OUString const & ServiceSpecifier,
    css::uno::Sequence<css::uno::Any> const & Arguments)
{
    OUString arg;
    if ((ServiceSpecifier == "com.sun.star.drawing.GraphicObjectShape"
         || ServiceSpecifier == "com.sun.star.drawing.AppletShape"
         || ServiceSpecifier == "com.sun.star.drawing.FrameShape"
         || ServiceSpecifier == "com.sun.star.drawing.OLE2Shape"
         || ServiceSpecifier == "com.sun.star.drawing.MediaShape"
         || ServiceSpecifier == "com.sun.star.drawing.PluginShape"
         || ServiceSpecifier == "com.sun.star.presentation.MediaShape")
        && Arguments.getLength() == 1 && (Arguments[0] >>= arg))
    {
        return create(ServiceSpecifier, arg);
    }
    return SvxFmMSFactory::createInstanceWithArguments(
        ServiceSpecifier, Arguments);
}

uno::Sequence< OUString > SAL_CALL SdXImpressDocument::getAvailableServiceNames()
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    const uno::Sequence< OUString > aSNS_ORG( SvxFmMSFactory::getAvailableServiceNames() );

    uno::Sequence< OUString > aSNS_Common{ u"com.sun.star.drawing.DashTable"_ustr,
                                           u"com.sun.star.drawing.GradientTable"_ustr,
                                           u"com.sun.star.drawing.HatchTable"_ustr,
                                           u"com.sun.star.drawing.BitmapTable"_ustr,
                                           u"com.sun.star.drawing.TransparencyGradientTable"_ustr,
                                           u"com.sun.star.drawing.MarkerTable"_ustr,
                                           u"com.sun.star.text.NumberingRules"_ustr,
                                           u"com.sun.star.drawing.Background"_ustr,
                                           u"com.sun.star.document.Settings"_ustr,
                                           sUNO_Service_ImageMapRectangleObject,
                                           sUNO_Service_ImageMapCircleObject,
                                           sUNO_Service_ImageMapPolygonObject,
                                           u"com.sun.star.xml.NamespaceMap"_ustr,

                                           // Support creation of GraphicStorageHandler and EmbeddedObjectResolver
                                           u"com.sun.star.document.ExportGraphicStorageHandler"_ustr,
                                           u"com.sun.star.document.ImportGraphicStorageHandler"_ustr,
                                           u"com.sun.star.document.ExportEmbeddedObjectResolver"_ustr,
                                           u"com.sun.star.document.ImportEmbeddedObjectResolver"_ustr,
                                           u"com.sun.star.drawing.TableShape"_ustr };

    uno::Sequence< OUString > aSNS_Specific;

    if(mbImpressDoc)
        aSNS_Specific = { u"com.sun.star.presentation.TitleTextShape"_ustr,
                          u"com.sun.star.presentation.OutlinerShape"_ustr,
                          u"com.sun.star.presentation.SubtitleShape"_ustr,
                          u"com.sun.star.presentation.GraphicObjectShape"_ustr,
                          u"com.sun.star.presentation.ChartShape"_ustr,
                          u"com.sun.star.presentation.PageShape"_ustr,
                          u"com.sun.star.presentation.OLE2Shape"_ustr,
                          u"com.sun.star.presentation.TableShape"_ustr,
                          u"com.sun.star.presentation.OrgChartShape"_ustr,
                          u"com.sun.star.presentation.NotesShape"_ustr,
                          u"com.sun.star.presentation.HandoutShape"_ustr,
                          u"com.sun.star.presentation.DocumentSettings"_ustr,
                          u"com.sun.star.presentation.FooterShape"_ustr,
                          u"com.sun.star.presentation.HeaderShape"_ustr,
                          u"com.sun.star.presentation.SlideNumberShape"_ustr,
                          u"com.sun.star.presentation.DateTimeShape"_ustr,
                          u"com.sun.star.presentation.CalcShape"_ustr,
                          u"com.sun.star.presentation.MediaShape"_ustr };
    else
        aSNS_Specific = { u"com.sun.star.drawing.DocumentSettings"_ustr };

    return comphelper::concatSequences( aSNS_ORG, aSNS_Common, aSNS_Specific );
}

// lang::XServiceInfo
OUString SAL_CALL SdXImpressDocument::getImplementationName()
{
    return u"SdXImpressDocument"_ustr;
    /* // Matching the .component information:
       return mbImpressDoc
           ? OUString("com.sun.star.comp.Draw.PresentationDocument")
           : OUString("com.sun.star.comp.Draw.DrawingDocument");
    */
}

sal_Bool SAL_CALL SdXImpressDocument::supportsService( const OUString& ServiceName )
{
    return cppu::supportsService(this, ServiceName);
}

uno::Sequence< OUString > SAL_CALL SdXImpressDocument::getSupportedServiceNames()
{
    ::SolarMutexGuard aGuard;

    return { u"com.sun.star.document.OfficeDocument"_ustr,
             u"com.sun.star.drawing.GenericDrawingDocument"_ustr,
             u"com.sun.star.drawing.DrawingDocumentFactory"_ustr,
             mbImpressDoc?u"com.sun.star.presentation.PresentationDocument"_ustr:u"com.sun.star.drawing.DrawingDocument"_ustr };
}

// XPropertySet
uno::Reference< beans::XPropertySetInfo > SAL_CALL SdXImpressDocument::getPropertySetInfo(  )
{
    ::SolarMutexGuard aGuard;
    return mpPropSet->getPropertySetInfo();
}

void SAL_CALL SdXImpressDocument::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    const SfxItemPropertyMapEntry* pEntry = mpPropSet->getPropertyMapEntry(aPropertyName);

    switch( pEntry ? pEntry->nWID : -1 )
    {
        case WID_MODEL_LANGUAGE:
        {
            lang::Locale aLocale;
            if(!(aValue >>= aLocale))
                throw lang::IllegalArgumentException();

            mpDoc->SetLanguage( LanguageTag::convertToLanguageType(aLocale), EE_CHAR_LANGUAGE );
            break;
        }
        case WID_MODEL_TABSTOP:
        {
            sal_Int32 nValue = 0;
            if(!(aValue >>= nValue) || nValue < 0 )
                throw lang::IllegalArgumentException();

            mpDoc->SetDefaultTabulator(static_cast<sal_uInt16>(nValue));
            break;
        }
        case WID_MODEL_VISAREA:
            {
                SfxObjectShell* pEmbeddedObj = mpDoc->GetDocSh();
                if( !pEmbeddedObj )
                    break;

                awt::Rectangle aVisArea;
                if( !(aValue >>= aVisArea) || (aVisArea.Width < 0) || (aVisArea.Height < 0) )
                    throw lang::IllegalArgumentException();

                sal_Int32 nRight, nTop;
                if (o3tl::checked_add(aVisArea.X, aVisArea.Width, nRight) || o3tl::checked_add(aVisArea.Y, aVisArea.Height, nTop))
                    throw lang::IllegalArgumentException();

                pEmbeddedObj->SetVisArea(::tools::Rectangle(aVisArea.X, aVisArea.Y, nRight, nTop));
            }
            break;
        case WID_MODEL_CONTFOCUS:
            {
                bool bFocus = false;
                if( !(aValue >>= bFocus ) )
                    throw lang::IllegalArgumentException();
                mpDoc->SetAutoControlFocus( bFocus );
            }
            break;
        case WID_MODEL_DSGNMODE:
            {
                bool bMode = false;
                if( !(aValue >>= bMode ) )
                    throw lang::IllegalArgumentException();
                mpDoc->SetOpenInDesignMode( bMode );
            }
            break;
        case WID_MODEL_BUILDID:
            aValue >>= maBuildId;
            return;
        case WID_MODEL_MAPUNIT:
        case WID_MODEL_BASICLIBS:
        case WID_MODEL_RUNTIMEUID: // is read-only
        case WID_MODEL_DIALOGLIBS:
        case WID_MODEL_FONTS:
            throw beans::PropertyVetoException();
        case WID_MODEL_INTEROPGRABBAG:
            setGrabBagItem(aValue);
            break;
        case WID_MODEL_THEME:
            getSdrModelFromUnoModel().setTheme(model::Theme::FromAny(aValue));
            break;
        default:
            throw beans::UnknownPropertyException( aPropertyName, static_cast<cppu::OWeakObject*>(this));
    }

    SetModified();
}

uno::Any SAL_CALL SdXImpressDocument::getPropertyValue( const OUString& PropertyName )
{
    ::SolarMutexGuard aGuard;

    uno::Any aAny;
    if( nullptr == mpDoc )
        throw lang::DisposedException();

    const SfxItemPropertyMapEntry* pEntry = mpPropSet->getPropertyMapEntry(PropertyName);

    switch( pEntry ? pEntry->nWID : -1 )
    {
        case WID_MODEL_LANGUAGE:
        {
            LanguageType eLang = mpDoc->GetLanguage( EE_CHAR_LANGUAGE );
            aAny <<= LanguageTag::convertToLocale( eLang);
            break;
        }
        case WID_MODEL_TABSTOP:
            aAny <<= static_cast<sal_Int32>(mpDoc->GetDefaultTabulator());
            break;
        case WID_MODEL_VISAREA:
            {
                SfxObjectShell* pEmbeddedObj = mpDoc->GetDocSh();
                if( !pEmbeddedObj )
                    break;

                const ::tools::Rectangle& aRect = pEmbeddedObj->GetVisArea();
                awt::Rectangle aVisArea( aRect.Left(), aRect.Top(), aRect.getOpenWidth(), aRect.getOpenHeight() );
                aAny <<= aVisArea;
            }
            break;
        case WID_MODEL_MAPUNIT:
            {
                SfxObjectShell* pEmbeddedObj = mpDoc->GetDocSh();
                if( !pEmbeddedObj )
                    break;

                sal_Int16 nMeasureUnit = 0;
                SvxMapUnitToMeasureUnit( pEmbeddedObj->GetMapUnit(), nMeasureUnit );
                aAny <<= nMeasureUnit;
        }
        break;
        case WID_MODEL_FORBCHARS:
        {
            aAny <<= getForbiddenCharsTable();
        }
        break;
        case WID_MODEL_CONTFOCUS:
            aAny <<= mpDoc->GetAutoControlFocus();
            break;
        case WID_MODEL_DSGNMODE:
            aAny <<= mpDoc->GetOpenInDesignMode();
            break;
        case WID_MODEL_BASICLIBS:
            aAny <<= mpDocShell->GetBasicContainer();
            break;
        case WID_MODEL_DIALOGLIBS:
            aAny <<= mpDocShell->GetDialogContainer();
            break;
        case WID_MODEL_RUNTIMEUID:
            aAny <<= getRuntimeUID();
            break;
        case WID_MODEL_BUILDID:
            return uno::Any( maBuildId );
        case WID_MODEL_HASVALIDSIGNATURES:
            aAny <<= hasValidSignatures();
            break;
        case WID_MODEL_ALLOWLINKUPDATE:
        {
            comphelper::EmbeddedObjectContainer& rEmbeddedObjectContainer = mpDocShell->getEmbeddedObjectContainer();
            aAny <<= rEmbeddedObjectContainer.getUserAllowsLinkUpdate();
            break;
        }
        case WID_MODEL_FONTS:
            {
                uno::Sequence<uno::Any> aSeq;
                int nSeqIndex = 0;

                sal_uInt16 const aWhichIds[] { EE_CHAR_FONTINFO, EE_CHAR_FONTINFO_CJK,
                                               EE_CHAR_FONTINFO_CTL };

                const SfxItemPool& rPool = mpDoc->GetPool();

                for(sal_uInt16 nWhichId : aWhichIds)
                {
                    ItemSurrogates aSurrogates = rPool.GetItemSurrogates(nWhichId);
                    const sal_uInt32 nItems(aSurrogates.size());

                    aSeq.realloc( aSeq.getLength() + nItems*5 + 5 );
                    auto pSeq = aSeq.getArray();

                    for (const SfxPoolItem* pItem : aSurrogates)
                    {
                        const SvxFontItem *pFont = static_cast<const SvxFontItem *>(pItem);

                        pSeq[nSeqIndex++] <<= pFont->GetFamilyName();
                        pSeq[nSeqIndex++] <<= pFont->GetStyleName();
                        pSeq[nSeqIndex++] <<= sal_Int16(pFont->GetFamily());
                        pSeq[nSeqIndex++] <<= sal_Int16(pFont->GetPitch());
                        pSeq[nSeqIndex++] <<= sal_Int16(pFont->GetCharSet());
                    }

                    const SvxFontItem& rFont = static_cast<const SvxFontItem&>(rPool.GetUserOrPoolDefaultItem( nWhichId ));

                    pSeq[nSeqIndex++] <<= rFont.GetFamilyName();
                    pSeq[nSeqIndex++] <<= rFont.GetStyleName();
                    pSeq[nSeqIndex++] <<= sal_Int16(rFont.GetFamily());
                    pSeq[nSeqIndex++] <<= sal_Int16(rFont.GetPitch());
                    pSeq[nSeqIndex++] <<= sal_Int16(rFont.GetCharSet());

                }

                aAny <<= aSeq;
                break;
            }
        case WID_MODEL_INTEROPGRABBAG:
            getGrabBagItem(aAny);
            break;
        case WID_MODEL_THEME:
            if (auto const& pTheme = getSdrModelFromUnoModel().getTheme())
                pTheme->ToAny(aAny);
            break;
        default:
            throw beans::UnknownPropertyException( PropertyName, static_cast<cppu::OWeakObject*>(this));
    }

    return aAny;
}

void SAL_CALL SdXImpressDocument::addPropertyChangeListener( const OUString& , const uno::Reference< beans::XPropertyChangeListener >&  ) {}
void SAL_CALL SdXImpressDocument::removePropertyChangeListener( const OUString& , const uno::Reference< beans::XPropertyChangeListener >&  ) {}
void SAL_CALL SdXImpressDocument::addVetoableChangeListener( const OUString& , const uno::Reference< beans::XVetoableChangeListener >&  ) {}
void SAL_CALL SdXImpressDocument::removeVetoableChangeListener( const OUString& , const uno::Reference< beans::XVetoableChangeListener >&  ) {}

// XLinkTargetSupplier
uno::Reference< container::XNameAccess > SAL_CALL SdXImpressDocument::getLinks()
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    rtl::Reference< SdDocLinkTargets > xLinks( mxLinks );
    if( !xLinks.is() )
    {
        xLinks = new SdDocLinkTargets( *this );
        mxLinks = xLinks.get();
    }
    return xLinks;
}

// XStyleFamiliesSupplier
uno::Reference< container::XNameAccess > SAL_CALL SdXImpressDocument::getStyleFamilies(  )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    uno::Reference< container::XNameAccess > xStyles( static_cast< OWeakObject* >( mpDoc->GetStyleSheetPool() ), css::uno::UNO_QUERY );
    return xStyles;
}

// XAnyCompareFactory
uno::Reference< css::ucb::XAnyCompare > SAL_CALL SdXImpressDocument::createAnyCompareByName( const OUString& )
{
    return SvxCreateNumRuleCompare();
}

// XRenderable
sal_Int32 SAL_CALL SdXImpressDocument::getRendererCount( const uno::Any& rSelection,
                                                         const uno::Sequence< beans::PropertyValue >&  )
{
    ::SolarMutexGuard aGuard;
    sal_Int32   nRet = 0;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    if (mpDocShell)
    {
        uno::Reference< frame::XModel > xModel;

        rSelection >>= xModel;

        if( xModel == mpDocShell->GetModel() )
            nRet = mpDoc->GetSdPageCount( PageKind::Standard );
        else
        {
            uno::Reference< drawing::XShapes > xShapes;

            rSelection >>= xShapes;

            if( xShapes.is() && xShapes->getCount() )
                nRet = 1;
        }
    }
    return nRet;
}

uno::Sequence< beans::PropertyValue > SAL_CALL SdXImpressDocument::getRenderer( sal_Int32 , const uno::Any& ,
                                                                                const uno::Sequence< beans::PropertyValue >& rxOptions )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    bool bExportNotesPages = false;
    for( const auto& rOption : rxOptions )
    {
        if ( rOption.Name == "ExportNotesPages" )
            rOption.Value >>= bExportNotesPages;
    }
    uno::Sequence< beans::PropertyValue > aRenderer;
    if (mpDocShell)
    {
        awt::Size aPageSize;
        if ( bExportNotesPages )
        {
            Size aNotesPageSize = mpDoc->GetSdPage( 0, PageKind::Notes )->GetSize();
            aPageSize = awt::Size( aNotesPageSize.Width(), aNotesPageSize.Height() );
        }
        else
        {
            const ::tools::Rectangle aVisArea( mpDocShell->GetVisArea( embed::Aspects::MSOLE_DOCPRINT ) );
            aPageSize = awt::Size( aVisArea.GetWidth(), aVisArea.GetHeight() );
        }
        aRenderer = { comphelper::makePropertyValue(u"PageSize"_ustr, aPageSize) };
    }
    return aRenderer;
}

namespace {

class ImplRenderPaintProc : public sdr::contact::ViewObjectContactRedirector
{
    const SdrLayerAdmin&    rLayerAdmin;
    SdrPageView*            pSdrPageView;

public:
    bool IsVisible  ( const SdrObject* pObj ) const;
    bool IsPrintable( const SdrObject* pObj ) const;

    ImplRenderPaintProc(const SdrLayerAdmin& rLA, SdrPageView* pView);

    // all default implementations just call the same methods at the original. To do something
    // different, override the method and at least do what the method does.
    virtual void createRedirectedPrimitive2DSequence(
        const sdr::contact::ViewObjectContact& rOriginal,
        const sdr::contact::DisplayInfo& rDisplayInfo,
        drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) override;
};

}

ImplRenderPaintProc::ImplRenderPaintProc(const SdrLayerAdmin& rLA, SdrPageView *const pView)
    : rLayerAdmin(rLA)
    , pSdrPageView(pView)
{
}

static sal_Int32 ImplPDFGetBookmarkPage( const OUString& rBookmark, SdDrawDocument const & rDoc )
{
    sal_Int32 nPage = -1;

    OUString aBookmark( rBookmark );

    if( rBookmark.startsWith("#") )
        aBookmark = rBookmark.copy( 1 );

    // is the bookmark a page ?
    bool        bIsMasterPage;
    sal_uInt16  nPgNum = rDoc.GetPageByName( aBookmark, bIsMasterPage );

    if ( nPgNum == SDRPAGE_NOTFOUND )
    {
        // is the bookmark an object ?
        SdrObject* pObj = rDoc.GetObj( aBookmark );
        if (pObj)
            nPgNum = pObj->getSdrPageFromSdrObject()->GetPageNum();
    }
    if ( nPgNum != SDRPAGE_NOTFOUND )
        nPage = ( nPgNum - 1 ) / 2;
    return nPage;
}

static void ImplPDFExportComments( const uno::Reference< drawing::XDrawPage >& xPage, vcl::PDFExtOutDevData& rPDFExtOutDevData )
{
    try
    {
        uno::Reference< office::XAnnotationAccess > xAnnotationAccess( xPage, uno::UNO_QUERY_THROW );
        uno::Reference< office::XAnnotationEnumeration > xAnnotationEnumeration( xAnnotationAccess->createAnnotationEnumeration() );

        while( xAnnotationEnumeration->hasMoreElements() )
        {
            uno::Reference<office::XAnnotation> xAnnotation(xAnnotationEnumeration->nextElement());

            geometry::RealPoint2D aRealPoint2D(xAnnotation->getPosition());
            geometry::RealSize2D aRealSize2D(xAnnotation->getSize());

            Point aPoint(aRealPoint2D.X * 100.0, aRealPoint2D.Y * 100.0);
            Size aSize(aRealSize2D.Width * 100.0, aRealSize2D.Height * 100.0);

            Point aPopupPoint(aPoint.X(), aPoint.Y());
            Size aPopupSize(aSize.Width() * 10.0, aSize.Height() * 10.0);

            uno::Reference<text::XText> xText(xAnnotation->getTextRange());

            vcl::pdf::PDFNote aNote;
            aNote.maTitle = xAnnotation->getAuthor();
            aNote.maContents = xText->getString();
            aNote.maModificationDate = xAnnotation->getDateTime();
            auto* pAnnotation = dynamic_cast<sd::Annotation*>(xAnnotation.get());

            if (pAnnotation && pAnnotation->getCreationInfo().meType != sdr::annotation::AnnotationType::None)
            {
                sdr::annotation::CreationInfo const& rCreation = pAnnotation->getCreationInfo();
                aNote.maPolygons = rCreation.maPolygons;
                aNote.maAnnotationColor = rCreation.maColor;
                aNote.maInteriorColor = rCreation.maFillColor;
                aNote.mfWidth = rCreation.mnWidth;
                switch (rCreation.meType)
                {
                    case sdr::annotation::AnnotationType::Square:
                        aNote.meType = vcl::pdf::PDFAnnotationSubType::Square; break;
                    case sdr::annotation::AnnotationType::Circle:
                        aNote.meType = vcl::pdf::PDFAnnotationSubType::Circle; break;
                    case sdr::annotation::AnnotationType::Polygon:
                        aNote.meType = vcl::pdf::PDFAnnotationSubType::Polygon; break;
                    case sdr::annotation::AnnotationType::Ink:
                        aNote.meType = vcl::pdf::PDFAnnotationSubType::Ink; break;
                    case sdr::annotation::AnnotationType::Highlight:
                        aNote.meType = vcl::pdf::PDFAnnotationSubType::Highlight; break;
                    case sdr::annotation::AnnotationType::Line:
                        aNote.meType = vcl::pdf::PDFAnnotationSubType::Line; break;
                    case sdr::annotation::AnnotationType::FreeText:
                        aNote.meType = vcl::pdf::PDFAnnotationSubType::FreeText; break;
                    default:
                        aNote.meType = vcl::pdf::PDFAnnotationSubType::Text;
                        break;
                }
            }

            rPDFExtOutDevData.CreateNote(::tools::Rectangle(aPoint, aSize), aNote,
                                         ::tools::Rectangle(aPopupPoint, aPopupSize));
        }
    }
    catch (const uno::Exception&)
    {
    }
}

static void ImplPDFExportShapeInteraction( const uno::Reference< drawing::XShape >& xShape, SdDrawDocument& rDoc, vcl::PDFExtOutDevData& rPDFExtOutDevData )
{
    if ( xShape->getShapeType() == "com.sun.star.drawing.GroupShape" )
    {
        uno::Reference< container::XIndexAccess > xIndexAccess( xShape, uno::UNO_QUERY );
        if ( xIndexAccess.is() )
        {
            sal_Int32 i, nCount = xIndexAccess->getCount();
            for ( i = 0; i < nCount; i++ )
            {
                uno::Reference< drawing::XShape > xSubShape( xIndexAccess->getByIndex( i ), uno::UNO_QUERY );
                if ( xSubShape.is() )
                    ImplPDFExportShapeInteraction( xSubShape, rDoc, rPDFExtOutDevData );
            }
        }
    }
    else
    {
        uno::Reference< beans::XPropertySet > xShapePropSet( xShape, uno::UNO_QUERY );
        if( xShapePropSet.is() )
        {
            Size        aPageSize( rDoc.GetSdPage( 0, PageKind::Standard )->GetSize() );
            Point aPoint( 0, 0 );
            ::tools::Rectangle   aPageRect( aPoint, aPageSize );

            awt::Point  aShapePos( xShape->getPosition() );
            awt::Size   aShapeSize( xShape->getSize() );
            ::tools::Rectangle   aLinkRect( Point( aShapePos.X, aShapePos.Y ), Size( aShapeSize.Width, aShapeSize.Height ) );

            // Handle linked videos.
            if (xShape->getShapeType() == "com.sun.star.drawing.MediaShape" || xShape->getShapeType() == "com.sun.star.presentation.MediaShape")
            {
                OUString title;
                xShapePropSet->getPropertyValue(u"Title"_ustr) >>= title;
                OUString description;
                xShapePropSet->getPropertyValue(u"Description"_ustr) >>= description;
                OUString const altText(title.isEmpty()
                    ? description
                    : description.isEmpty()
                        ? title
                        : OUString::Concat(title) + OUString::Concat("\n") + OUString::Concat(description));

                OUString aMediaURL;
                xShapePropSet->getPropertyValue(u"MediaURL"_ustr) >>= aMediaURL;
                if (!aMediaURL.isEmpty())
                {
                    SdrObject const*const pSdrObj(SdrObject::getSdrObjectFromXShape(xShape));
                    OUString const mimeType(xShapePropSet->getPropertyValue(u"MediaMimeType"_ustr).get<OUString>());
                    sal_Int32 nScreenId = rPDFExtOutDevData.CreateScreen(aLinkRect, altText, mimeType, rPDFExtOutDevData.GetCurrentPageNumber(), pSdrObj);
                    if (aMediaURL.startsWith("vnd.sun.star.Package:"))
                    {
                        OUString aTempFileURL;
                        xShapePropSet->getPropertyValue(u"PrivateTempFileURL"_ustr) >>= aTempFileURL;
                        rPDFExtOutDevData.SetScreenStream(nScreenId, aTempFileURL);
                    }
                    else
                        rPDFExtOutDevData.SetScreenURL(nScreenId, aMediaURL);
                }
            }

            presentation::ClickAction eCa;
            uno::Any aAny( xShapePropSet->getPropertyValue( u"OnClick"_ustr ) );
            if ( aAny >>= eCa )
            {
                OUString const actionName(SdResId(SdTPAction::GetClickActionSdResId(eCa)));
                switch ( eCa )
                {
                    case presentation::ClickAction_LASTPAGE :
                    {
                        sal_Int32 nCount = rDoc.GetSdPageCount( PageKind::Standard );
                        sal_Int32 nDestId = rPDFExtOutDevData.CreateDest( aPageRect, nCount - 1, vcl::pdf::PDFWriter::DestAreaType::FitRectangle );
                        sal_Int32 nLinkId = rPDFExtOutDevData.CreateLink(aLinkRect, actionName);
                        rPDFExtOutDevData.SetLinkDest( nLinkId, nDestId );
                    }
                    break;
                    case presentation::ClickAction_FIRSTPAGE :
                    {
                        sal_Int32 nDestId = rPDFExtOutDevData.CreateDest( aPageRect, 0, vcl::pdf::PDFWriter::DestAreaType::FitRectangle );
                        sal_Int32 nLinkId = rPDFExtOutDevData.CreateLink(aLinkRect, actionName);
                        rPDFExtOutDevData.SetLinkDest( nLinkId, nDestId );
                    }
                    break;
                    case presentation::ClickAction_PREVPAGE :
                    {
                        sal_Int32 nDestPage = rPDFExtOutDevData.GetCurrentPageNumber();
                        if ( nDestPage )
                            nDestPage--;
                        sal_Int32 nDestId = rPDFExtOutDevData.CreateDest( aPageRect, nDestPage, vcl::pdf::PDFWriter::DestAreaType::FitRectangle );
                        sal_Int32 nLinkId = rPDFExtOutDevData.CreateLink(aLinkRect, actionName);
                        rPDFExtOutDevData.SetLinkDest( nLinkId, nDestId );
                    }
                    break;
                    case presentation::ClickAction_NEXTPAGE :
                    {
                        sal_Int32 nDestPage = rPDFExtOutDevData.GetCurrentPageNumber() + 1;
                        sal_Int32 nLastPage = rDoc.GetSdPageCount( PageKind::Standard ) - 1;
                        if ( nDestPage > nLastPage )
                            nDestPage = nLastPage;
                        sal_Int32 nDestId = rPDFExtOutDevData.CreateDest( aPageRect, nDestPage, vcl::pdf::PDFWriter::DestAreaType::FitRectangle );
                        sal_Int32 nLinkId = rPDFExtOutDevData.CreateLink(aLinkRect, actionName);
                        rPDFExtOutDevData.SetLinkDest( nLinkId, nDestId );
                    }
                    break;

                    case presentation::ClickAction_PROGRAM :
                    case presentation::ClickAction_BOOKMARK :
                    case presentation::ClickAction_DOCUMENT :
                    {
                        OUString aBookmark;
                        xShapePropSet->getPropertyValue( u"Bookmark"_ustr ) >>= aBookmark;
                        if( !aBookmark.isEmpty() )
                        {
                            switch( eCa )
                            {
                                case presentation::ClickAction_DOCUMENT :
                                case presentation::ClickAction_PROGRAM :
                                {
                                    sal_Int32 nLinkId = rPDFExtOutDevData.CreateLink(aLinkRect, actionName);
                                    rPDFExtOutDevData.SetLinkURL( nLinkId, aBookmark );
                                }
                                break;
                                case presentation::ClickAction_BOOKMARK :
                                {
                                    sal_Int32 nPage = ImplPDFGetBookmarkPage( aBookmark, rDoc );
                                    if ( nPage != -1 )
                                    {
                                        sal_Int32 nDestId = rPDFExtOutDevData.CreateDest( aPageRect, nPage, vcl::pdf::PDFWriter::DestAreaType::FitRectangle );
                                        sal_Int32 nLinkId = rPDFExtOutDevData.CreateLink(aLinkRect, actionName);
                                        rPDFExtOutDevData.SetLinkDest( nLinkId, nDestId );
                                    }
                                }
                                break;
                                default:
                                    break;
                            }
                        }
                    }
                    break;

                    case presentation::ClickAction_STOPPRESENTATION :
                    case presentation::ClickAction_SOUND :
                    case presentation::ClickAction_INVISIBLE :
                    case presentation::ClickAction_VERB :
                    case presentation::ClickAction_VANISH :
                    case presentation::ClickAction_MACRO :
                    default :
                    break;
                }
            }
        }
    }
}

void ImplRenderPaintProc::createRedirectedPrimitive2DSequence(
    const sdr::contact::ViewObjectContact& rOriginal,
    const sdr::contact::DisplayInfo& rDisplayInfo,
    drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor)
{
    SdrObject* pObject = rOriginal.GetViewContact().TryToGetSdrObject();
    if(!pObject)
    {
        // not an object, maybe a page
        sdr::contact::ViewObjectContactRedirector::createRedirectedPrimitive2DSequence(rOriginal, rDisplayInfo, rVisitor);
        return;
    }
    SdrPage* pSdrPage(pObject->getSdrPageFromSdrObject());
    if(!pSdrPage)
        return;
    if(!pSdrPage->checkVisibility(rOriginal, rDisplayInfo, false))
        return;
    if(!IsVisible(pObject) || !IsPrintable(pObject))
        return;

    sdr::contact::ViewObjectContactRedirector::createRedirectedPrimitive2DSequence(rOriginal, rDisplayInfo, rVisitor);
}

bool ImplRenderPaintProc::IsVisible( const SdrObject* pObj ) const
{
    bool bVisible = true;
    SdrLayerID nLayerId = pObj->GetLayer();
    if( pSdrPageView )
    {
        const SdrLayer* pSdrLayer = rLayerAdmin.GetLayerPerID( nLayerId );
        if ( pSdrLayer )
        {
            const OUString& aLayerName = pSdrLayer->GetName();
            bVisible = pSdrPageView->IsLayerVisible( aLayerName );
        }
    }
    return bVisible;
}
bool ImplRenderPaintProc::IsPrintable( const SdrObject* pObj ) const
{
    bool bPrintable = true;
    SdrLayerID nLayerId = pObj->GetLayer();
    if( pSdrPageView )
    {
        const SdrLayer* pSdrLayer = rLayerAdmin.GetLayerPerID( nLayerId );
        if ( pSdrLayer )
        {
            const OUString& aLayerName = pSdrLayer->GetName();
            bPrintable = pSdrPageView->IsLayerPrintable( aLayerName );
        }
    }
    return bPrintable;

}

namespace
{
    sal_Int16 CalcOutputPageNum(vcl::PDFExtOutDevData const * pPDFExtOutDevData, SdDrawDocument const *pDoc, sal_Int16 nPageNumber)
    {
        //export all pages, simple one to one case
        if (pPDFExtOutDevData && pPDFExtOutDevData->GetIsExportHiddenSlides())
            return nPageNumber-1;
        //check all preceding pages, and only count non-hidden ones
        sal_Int16 nRet = 0;
        for (sal_Int16 i = 0; i < nPageNumber-1; ++i)
        {
           if (!pDoc->GetSdPage(i, PageKind::Standard)->IsExcluded())
                ++nRet;
        }
        return nRet;
    }
}

void SAL_CALL SdXImpressDocument::render( sal_Int32 nRenderer, const uno::Any& rSelection,
                                          const uno::Sequence< beans::PropertyValue >& rxOptions )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpDoc )
        throw lang::DisposedException();

    if (!mpDocShell)
        return;

    uno::Reference< awt::XDevice >  xRenderDevice;
    const sal_Int32                 nPageNumber = nRenderer + 1;
    PageKind                        ePageKind = PageKind::Standard;
    bool                        bExportNotesPages = false;

    for( const auto& rOption : rxOptions )
    {
        if ( rOption.Name == "RenderDevice" )
            rOption.Value >>= xRenderDevice;
        else if ( rOption.Name == "ExportNotesPages" )
        {
            rOption.Value >>= bExportNotesPages;
            if ( bExportNotesPages )
                ePageKind = PageKind::Notes;
        }
    }

    if( !(xRenderDevice.is() && nPageNumber && ( nPageNumber <= mpDoc->GetSdPageCount( ePageKind ) )) )
        return;

    VCLXDevice* pDevice = dynamic_cast<VCLXDevice*>( xRenderDevice.get() );
    VclPtr< OutputDevice> pOut = pDevice ? pDevice->GetOutputDevice() : VclPtr< OutputDevice >();

    if( !pOut )
        return;

    vcl::PDFExtOutDevData* pPDFExtOutDevData = dynamic_cast<vcl::PDFExtOutDevData* >( pOut->GetExtOutDevData() );

    if ( mpDoc->GetSdPage(static_cast<sal_Int16>(nPageNumber)-1, PageKind::Standard)->IsExcluded() &&
        !(pPDFExtOutDevData && pPDFExtOutDevData->GetIsExportHiddenSlides()) )
        return;

    if (pPDFExtOutDevData)
    {
        css::lang::Locale const docLocale(Application::GetSettings().GetLanguageTag().getLocale());
        pPDFExtOutDevData->SetDocumentLocale(docLocale);
    }

    ::sd::ClientView aView( mpDocShell, pOut );
    ::tools::Rectangle aVisArea( Point(), mpDoc->GetSdPage( static_cast<sal_uInt16>(nPageNumber) - 1, ePageKind )->GetSize() );
    vcl::Region                       aRegion( aVisArea );

    ::sd::ViewShell* pOldViewSh = mpDocShell->GetViewShell();
    ::sd::View* pOldSdView = pOldViewSh ? pOldViewSh->GetView() : nullptr;

    if  ( pOldSdView )
        pOldSdView->SdrEndTextEdit();

    aView.SetHlplVisible( false );
    aView.SetGridVisible( false );
    aView.SetBordVisible( false );
    aView.SetPageVisible( false );
    aView.SetGlueVisible( false );

    pOut->SetMapMode(MapMode(MapUnit::Map100thMM));
    pOut->IntersectClipRegion( aVisArea );

    uno::Reference< frame::XModel > xModel;
    rSelection >>= xModel;

    if( xModel == mpDocShell->GetModel() )
    {
        aView.ShowSdrPage( mpDoc->GetSdPage( static_cast<sal_uInt16>(nPageNumber) - 1, ePageKind ));
        SdrPageView* pPV = aView.GetSdrPageView();

        if( pOldSdView )
        {
            SdrPageView* pOldPV = pOldSdView->GetSdrPageView();
            if( pPV && pOldPV )
            {
                pPV->SetVisibleLayers( pOldPV->GetVisibleLayers() );
                pPV->SetPrintableLayers( pOldPV->GetPrintableLayers() );
            }
        }

        ImplRenderPaintProc aImplRenderPaintProc( mpDoc->GetLayerAdmin(),
            pPV);

        // background color for outliner :o
        SdPage* pPage = pPV ? static_cast<SdPage*>(pPV->GetPage()) : nullptr;
        if( pPage )
        {
            SdrOutliner& rOutl = mpDoc->GetDrawOutliner();
            bool bScreenDisplay(true);

            // #i75566# printing; suppress AutoColor BackgroundColor generation
            // for visibility reasons by giving GetPageBackgroundColor()
            // the needed hint
            // #i75566# PDF export; suppress AutoColor BackgroundColor generation (see printing)
            if (pOut && ((OUTDEV_PRINTER == pOut->GetOutDevType())
                    || (OUTDEV_PDF == pOut->GetOutDevType())))
                bScreenDisplay = false;

            // #i75566# Name change GetBackgroundColor -> GetPageBackgroundColor and
            // hint value if screen display. Only then the AutoColor mechanisms shall be applied
            rOutl.SetBackgroundColor( pPage->GetPageBackgroundColor( pPV, bScreenDisplay ) );
        }

        // produce link annots for media shapes before painting them
        if ( pPDFExtOutDevData && pPage )
        {
            try
            {
                uno::Any aAny;
                uno::Reference< drawing::XDrawPage > xPage( uno::Reference< drawing::XDrawPage >::query( pPage->getUnoPage() ) );
                if ( xPage.is() )
                {
                    if ( pPDFExtOutDevData->GetIsExportNotes() )
                        ImplPDFExportComments( xPage, *pPDFExtOutDevData );
                    uno::Reference< beans::XPropertySet > xPagePropSet( xPage, uno::UNO_QUERY );
                    if( xPagePropSet.is() )
                    {
                        // exporting object interactions to pdf

                        // if necessary, the master page interactions will be exported first
                        bool bIsBackgroundObjectsVisible = false;   // #i39428# IsBackgroundObjectsVisible not available for Draw
                        if ( mbImpressDoc && xPagePropSet->getPropertySetInfo()->hasPropertyByName( u"IsBackgroundObjectsVisible"_ustr ) )
                            xPagePropSet->getPropertyValue( u"IsBackgroundObjectsVisible"_ustr ) >>= bIsBackgroundObjectsVisible;
                        if ( bIsBackgroundObjectsVisible && !pPDFExtOutDevData->GetIsExportNotesPages() )
                        {
                            uno::Reference< drawing::XMasterPageTarget > xMasterPageTarget( xPage, uno::UNO_QUERY );
                            if ( xMasterPageTarget.is() )
                            {
                                uno::Reference< drawing::XDrawPage > xMasterPage = xMasterPageTarget->getMasterPage();
                                if ( xMasterPage.is() )
                                {
                                    sal_Int32 i, nCount = xMasterPage->getCount();
                                    for ( i = 0; i < nCount; i++ )
                                    {
                                        aAny = xMasterPage->getByIndex( i );
                                        uno::Reference< drawing::XShape > xShape;
                                        if ( aAny >>= xShape )
                                            ImplPDFExportShapeInteraction( xShape, *mpDoc, *pPDFExtOutDevData );
                                    }
                                }
                            }
                        }

                        // exporting slide page object interactions
                        sal_Int32 i, nCount = xPage->getCount();
                        for ( i = 0; i < nCount; i++ )
                        {
                            aAny = xPage->getByIndex( i );
                            uno::Reference< drawing::XShape > xShape;
                            if ( aAny >>= xShape )
                                ImplPDFExportShapeInteraction( xShape, *mpDoc, *pPDFExtOutDevData );
                        }

                        // exporting transition effects to pdf
                        if ( mbImpressDoc && !pPDFExtOutDevData->GetIsExportNotesPages() && pPDFExtOutDevData->GetIsExportTransitionEffects() )
                        {
                            static constexpr OUString sEffect( u"Effect"_ustr );
                            static constexpr OUString sSpeed ( u"Speed"_ustr );
                            sal_Int32 nTime = 800;
                            presentation::AnimationSpeed aAs;
                            if ( xPagePropSet->getPropertySetInfo( )->hasPropertyByName( sSpeed ) )
                            {
                                aAny = xPagePropSet->getPropertyValue( sSpeed );
                                if ( aAny >>= aAs )
                                {
                                    switch( aAs )
                                    {
                                        case presentation::AnimationSpeed_SLOW : nTime = 1500; break;
                                        case presentation::AnimationSpeed_FAST : nTime = 300; break;
                                        default:
                                        case presentation::AnimationSpeed_MEDIUM : nTime = 800;
                                    }
                                }
                            }
                            presentation::FadeEffect eFe;
                            vcl::pdf::PDFWriter::PageTransition eType = vcl::pdf::PDFWriter::PageTransition::Regular;
                            if ( xPagePropSet->getPropertySetInfo( )->hasPropertyByName( sEffect ) )
                            {
                                aAny = xPagePropSet->getPropertyValue( sEffect );
                                if ( aAny >>= eFe )
                                {
                                    switch( eFe )
                                    {
                                        case presentation::FadeEffect_HORIZONTAL_LINES :
                                        case presentation::FadeEffect_HORIZONTAL_CHECKERBOARD :
                                        case presentation::FadeEffect_HORIZONTAL_STRIPES : eType = vcl::pdf::PDFWriter::PageTransition::BlindsHorizontal; break;

                                        case presentation::FadeEffect_VERTICAL_LINES :
                                        case presentation::FadeEffect_VERTICAL_CHECKERBOARD :
                                        case presentation::FadeEffect_VERTICAL_STRIPES : eType = vcl::pdf::PDFWriter::PageTransition::BlindsVertical; break;

                                        case presentation::FadeEffect_UNCOVER_TO_RIGHT :
                                        case presentation::FadeEffect_UNCOVER_TO_UPPERRIGHT :
                                        case presentation::FadeEffect_ROLL_FROM_LEFT :
                                        case presentation::FadeEffect_FADE_FROM_UPPERLEFT :
                                        case presentation::FadeEffect_MOVE_FROM_UPPERLEFT :
                                        case presentation::FadeEffect_FADE_FROM_LEFT :
                                        case presentation::FadeEffect_MOVE_FROM_LEFT : eType = vcl::pdf::PDFWriter::PageTransition::WipeLeftToRight; break;

                                        case presentation::FadeEffect_UNCOVER_TO_BOTTOM :
                                        case presentation::FadeEffect_UNCOVER_TO_LOWERRIGHT :
                                        case presentation::FadeEffect_ROLL_FROM_TOP :
                                        case presentation::FadeEffect_FADE_FROM_UPPERRIGHT :
                                        case presentation::FadeEffect_MOVE_FROM_UPPERRIGHT :
                                        case presentation::FadeEffect_FADE_FROM_TOP :
                                        case presentation::FadeEffect_MOVE_FROM_TOP : eType = vcl::pdf::PDFWriter::PageTransition::WipeTopToBottom; break;

                                        case presentation::FadeEffect_UNCOVER_TO_LEFT :
                                        case presentation::FadeEffect_UNCOVER_TO_LOWERLEFT :
                                        case presentation::FadeEffect_ROLL_FROM_RIGHT :

                                        case presentation::FadeEffect_FADE_FROM_LOWERRIGHT :
                                        case presentation::FadeEffect_MOVE_FROM_LOWERRIGHT :
                                        case presentation::FadeEffect_FADE_FROM_RIGHT :
                                        case presentation::FadeEffect_MOVE_FROM_RIGHT : eType = vcl::pdf::PDFWriter::PageTransition::WipeRightToLeft; break;

                                        case presentation::FadeEffect_UNCOVER_TO_TOP :
                                        case presentation::FadeEffect_UNCOVER_TO_UPPERLEFT :
                                        case presentation::FadeEffect_ROLL_FROM_BOTTOM :
                                        case presentation::FadeEffect_FADE_FROM_LOWERLEFT :
                                        case presentation::FadeEffect_MOVE_FROM_LOWERLEFT :
                                        case presentation::FadeEffect_FADE_FROM_BOTTOM :
                                        case presentation::FadeEffect_MOVE_FROM_BOTTOM : eType = vcl::pdf::PDFWriter::PageTransition::WipeBottomToTop; break;

                                        case presentation::FadeEffect_OPEN_VERTICAL : eType = vcl::pdf::PDFWriter::PageTransition::SplitHorizontalInward; break;
                                        case presentation::FadeEffect_CLOSE_HORIZONTAL : eType = vcl::pdf::PDFWriter::PageTransition::SplitHorizontalOutward; break;

                                        case presentation::FadeEffect_OPEN_HORIZONTAL : eType = vcl::pdf::PDFWriter::PageTransition::SplitVerticalInward; break;
                                        case presentation::FadeEffect_CLOSE_VERTICAL : eType = vcl::pdf::PDFWriter::PageTransition::SplitVerticalOutward; break;

                                        case presentation::FadeEffect_FADE_TO_CENTER : eType = vcl::pdf::PDFWriter::PageTransition::BoxInward; break;
                                        case presentation::FadeEffect_FADE_FROM_CENTER : eType = vcl::pdf::PDFWriter::PageTransition::BoxOutward; break;

                                        case presentation::FadeEffect_NONE : eType = vcl::pdf::PDFWriter::PageTransition::Regular; break;

                                        case presentation::FadeEffect_RANDOM :
                                        case presentation::FadeEffect_DISSOLVE :
                                        default: eType = vcl::pdf::PDFWriter::PageTransition::Dissolve; break;
                                    }
                                }
                            }

                            if ( xPagePropSet->getPropertySetInfo( )->hasPropertyByName( sEffect ) ||
                                xPagePropSet->getPropertySetInfo( )->hasPropertyByName( sSpeed ) )
                            {
                                pPDFExtOutDevData->SetPageTransition( eType, nTime );
                            }
                        }
                    }
                }
            }
            catch (const uno::Exception&)
            {
            }
        }

        aView.SdrPaintView::CompleteRedraw(pOut, aRegion, &aImplRenderPaintProc);

        if (pPDFExtOutDevData && pPage)
        {
            try
            {
                Size        aPageSize( mpDoc->GetSdPage( 0, PageKind::Standard )->GetSize() );
                Point aPoint( 0, 0 );
                ::tools::Rectangle   aPageRect( aPoint, aPageSize );

                // resolving links found in this page by the method ImpEditEngine::Paint
                std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks();
                for ( const auto& rBookmark : rBookmarks )
                {
                    sal_Int32 nPage = ImplPDFGetBookmarkPage( rBookmark.aBookmark, *mpDoc );
                    if ( nPage != -1 )
                    {
                        if ( rBookmark.nLinkId != -1 )
                            pPDFExtOutDevData->SetLinkDest( rBookmark.nLinkId, pPDFExtOutDevData->CreateDest( aPageRect, nPage, vcl::pdf::PDFWriter::DestAreaType::FitRectangle ) );
                        else
                            pPDFExtOutDevData->DescribeRegisteredDest( rBookmark.nDestId, aPageRect, nPage, vcl::pdf::PDFWriter::DestAreaType::FitRectangle );
                    }
                    else
                        pPDFExtOutDevData->SetLinkURL( rBookmark.nLinkId, rBookmark.aBookmark );
                }
                rBookmarks.clear();
                //---> #i56629, #i40318
                //get the page name, will be used as outline element in PDF bookmark pane
                OUString aPageName = mpDoc->GetSdPage( static_cast<sal_uInt16>(nPageNumber) - 1 , PageKind::Standard )->GetName();
                if( !aPageName.isEmpty() )
                {
                    // Destination PageNum
                    const sal_Int32 nDestPageNum = CalcOutputPageNum(pPDFExtOutDevData, mpDoc, nPageNumber);

                    // insert the bookmark to this page into the NamedDestinations
                    if( pPDFExtOutDevData->GetIsExportNamedDestinations() )
                        pPDFExtOutDevData->CreateNamedDest(aPageName, aPageRect, nDestPageNum);

                    // add the name to the outline, (almost) same code as in sc/source/ui/unoobj/docuno.cxx
                    // issue #i40318.

                    if( pPDFExtOutDevData->GetIsExportBookmarks() )
                    {
                        // Destination Export
                        const sal_Int32 nDestId =
                            pPDFExtOutDevData->CreateDest(aPageRect , nDestPageNum);

                        // Create a new outline item:
                        pPDFExtOutDevData->CreateOutlineItem( -1 , aPageName, nDestId );
                    }
                }
                //<--- #i56629, #i40318
            }
            catch (const uno::Exception&)
            {
            }

        }
    }
    else
    {
        uno::Reference< drawing::XShapes > xShapes;
        rSelection >>= xShapes;

        if( xShapes.is() && xShapes->getCount() )
        {
            SdrPageView* pPV = nullptr;

            ImplRenderPaintProc  aImplRenderPaintProc( mpDoc->GetLayerAdmin(),
                            pOldSdView ? pOldSdView->GetSdrPageView() : nullptr);

            for( sal_uInt32 i = 0, nCount = xShapes->getCount(); i < nCount; i++ )
            {
                uno::Reference< drawing::XShape > xShape;
                xShapes->getByIndex( i ) >>= xShape;

                if( xShape.is() )
                {
                    SdrObject* pObj = SdrObject::getSdrObjectFromXShape( xShape );
                    if( pObj && pObj->getSdrPageFromSdrObject()
                        && aImplRenderPaintProc.IsVisible( pObj )
                            && aImplRenderPaintProc.IsPrintable( pObj ) )
                    {
                        if( !pPV )
                            pPV = aView.ShowSdrPage( pObj->getSdrPageFromSdrObject() );

                        if( pPV )
                            aView.MarkObj( pObj, pPV );
                    }
                }
            }
            aView.DrawMarkedObj(*pOut);
        }
    }
}

DrawViewShell* SdXImpressDocument::GetViewShell()
{
    if (!mpDocShell)
    {
        return nullptr;
    }

    DrawViewShell* pViewSh = dynamic_cast<DrawViewShell*>(mpDocShell->GetViewShell());
    if (!pViewSh)
    {
        SAL_WARN("sd", "DrawViewShell not available!");
        return nullptr;
    }
    return pViewSh;
}

void SdXImpressDocument::paintTile( VirtualDevice& rDevice,
                            int nOutputWidth, int nOutputHeight,
                            int nTilePosX, int nTilePosY,
                            ::tools::Long nTileWidth, ::tools::Long nTileHeight )
{
    DrawViewShell* pViewSh = GetViewShell();
    if (!pViewSh)
        return;

    // we need to skip tile invalidation for controls on rendering
    comphelper::LibreOfficeKit::setTiledPainting(true);

    // Setup drawing layer to work properly. Since we use a custom VirtualDevice
    // for the drawing, SdrPaintView::BeginCompleteRedraw() will call FindPaintWindow()
    // unsuccessfully and use a temporary window that doesn't keep state. So patch
    // the existing SdrPageWindow to use a temporary, and this way the state will be kept.
    // Well, at least that's how I understand it based on Writer's RenderContextGuard,
    // as the drawing layer classes lack documentation.
    SdrPageWindow* patchedPageWindow = nullptr;
    SdrPaintWindow* previousPaintWindow = nullptr;
    std::unique_ptr<SdrPaintWindow> temporaryPaintWindow;
    if(SdrView* pDrawView = pViewSh->GetDrawView())
    {
        if(SdrPageView* pSdrPageView = pDrawView->GetSdrPageView())
        {
            pSdrPageView->SetApplicationDocumentColor(pViewSh->GetViewOptions().mnDocBackgroundColor);
            patchedPageWindow = pSdrPageView->FindPageWindow(*getDocWindow()->GetOutDev());
            temporaryPaintWindow.reset(new SdrPaintWindow(*pDrawView, rDevice));
            if (patchedPageWindow)
                previousPaintWindow = patchedPageWindow->patchPaintWindow(*temporaryPaintWindow);
        }
    }

    // Scaling. Must convert from pixels to twips. We know
    // that VirtualDevices use a DPI of 96.
    // We specifically calculate these scales first as we're still
    // in TWIPs, and might as well minimize the number of conversions.
    const Fraction scale = conversionFract(o3tl::Length::px, o3tl::Length::twip);
    Fraction scaleX = Fraction(nOutputWidth, nTileWidth) * scale;
    Fraction scaleY = Fraction(nOutputHeight, nTileHeight) * scale;

    // svx seems to be the only component that works natively in
    // 100th mm rather than TWIP. It makes most sense just to
    // convert here and in getDocumentSize, and leave the tiled
    // rendering API working in TWIPs.
    ::tools::Long nTileWidthHMM = convertTwipToMm100( nTileWidth );
    ::tools::Long nTileHeightHMM = convertTwipToMm100( nTileHeight );
    int nTilePosXHMM = convertTwipToMm100( nTilePosX );
    int nTilePosYHMM = convertTwipToMm100( nTilePosY );

    MapMode aMapMode = rDevice.GetMapMode();
    aMapMode.SetMapUnit( MapUnit::Map100thMM );
    aMapMode.SetOrigin( Point( -nTilePosXHMM,
                               -nTilePosYHMM) );
    aMapMode.SetScaleX( scaleX );
    aMapMode.SetScaleY( scaleY );

    rDevice.SetMapMode( aMapMode );

    rDevice.SetOutputSizePixel( Size(nOutputWidth, nOutputHeight), /*bErase*/false );

    Point aPoint(nTilePosXHMM, nTilePosYHMM);
    Size aSize(nTileWidthHMM, nTileHeightHMM);
    ::tools::Rectangle aRect(aPoint, aSize);

    SdrView* pView = pViewSh->GetDrawView();
    if (comphelper::LibreOfficeKit::isActive())
        pView->SetPaintTextEdit(mbPaintTextEdit);

    pViewSh->GetView()->CompleteRedraw(&rDevice, vcl::Region(aRect));

    if (comphelper::LibreOfficeKit::isActive())
        pView->SetPaintTextEdit(true);

    LokChartHelper::PaintAllChartsOnTile(rDevice, nOutputWidth, nOutputHeight,
                                         nTilePosX, nTilePosY, nTileWidth, nTileHeight);
    LokStarMathHelper::PaintAllInPlaceOnTile(rDevice, nOutputWidth, nOutputHeight, nTilePosX,
                                             nTilePosY, nTileWidth, nTileHeight);

    if(patchedPageWindow != nullptr)
        patchedPageWindow->unpatchPaintWindow(previousPaintWindow);

    // Draw Form controls
    SdrView* pDrawView = pViewSh->GetDrawView();
    SdrPageView* pPageView = pDrawView->GetSdrPageView();
    if (pPageView != nullptr)
    {
        SdrPage* pPage = pPageView->GetPage();
        ::sd::Window* pActiveWin = pViewSh->GetActiveWindow();
        ::tools::Rectangle aTileRect(Point(nTilePosX, nTilePosY), Size(nTileWidth, nTileHeight));
        Size aOutputSize(nOutputWidth, nOutputHeight);
        LokControlHandler::paintControlTile(pPage, pDrawView, *pActiveWin, rDevice, aOutputSize, aTileRect);
    }

    comphelper::LibreOfficeKit::setTiledPainting(false);
}

OString SdXImpressDocument::getViewRenderState(SfxViewShell* pViewShell)
{
    OStringBuffer aState;
    DrawViewShell* pView = nullptr;

    if (ViewShellBase* pShellBase = dynamic_cast<ViewShellBase*>(pViewShell))
        pView = dynamic_cast<DrawViewShell*>(pShellBase->GetMainViewShell().get());
    else
        pView = GetViewShell();

    if (pView)
    {
        const SdViewOptions& pVOpt = pView->GetViewOptions();
        if (mpDoc && mpDoc->GetOnlineSpell())
            aState.append('S');
        if (!ThemeColors::UseOnlyWhiteDocBackground())
        {
            if (pVOpt.mnDocBackgroundColor
                == svtools::ColorConfig::GetDefaultColor(svtools::DOCCOLOR))
                aState.append('D');
        }
        aState.append(';');

        OString aThemeName = OUStringToOString(pVOpt.msColorSchemeName, RTL_TEXTENCODING_UTF8);
        aState.append(aThemeName);
    }
    return aState.makeStringAndClear();
}

void SdXImpressDocument::selectPart(int nPart, int nSelect)
{
    DrawViewShell* pViewSh = GetViewShell();
    if (!pViewSh)
        return;

    pViewSh->SelectPage(nPart, nSelect);
}

void SdXImpressDocument::moveSelectedParts(int nPosition, bool bDuplicate)
{
    // Duplicating is currently unsupported.
    if (bDuplicate)
        return;

    DrawViewShell* pViewSh = GetViewShell();
    if (!pViewSh)
        return;

    auto pSlideSorter
        = sd::slidesorter::SlideSorterViewShell::GetSlideSorter(pViewSh->GetViewShellBase());
    sd::slidesorter::SharedPageSelection pSelectedPage
        = pSlideSorter ? pSlideSorter->GetPageSelection() : nullptr;
    if (!pSelectedPage)
        return;
    mpDoc->MovePages(nPosition, *pSelectedPage);
}

OUString SdXImpressDocument::getPartInfo(int nPart)
{
    DrawViewShell* pViewSh = GetViewShell();
    if (!pViewSh)
        return OUString();

    SdPage* pSdPage = mpDoc->GetSdPage(nPart, pViewSh->GetPageKind());
    const sal_Int16 nMasterPageCount= pViewSh->GetDoc()->GetMasterSdPageCount(pViewSh->GetPageKind());

    ::tools::JsonWriter jsonWriter;

    jsonWriter.put("masterPageCount", nMasterPageCount);
    jsonWriter.put("mode", getEditMode());
    jsonWriter.put("gridSnapEnabled", pViewSh->GetDrawView()->IsGridSnap());
    jsonWriter.put("gridVisible", pViewSh->GetDrawView()->IsGridVisible());

    // Below information is useful when grid snapping is enabled. It let's to calculate the points we can snap to.
    const Size gridCoarse = pViewSh->GetDrawView()->GetGridCoarse();
    const Size innerDots = pViewSh->GetDrawView()->GetGridFine();
    jsonWriter.put("gridCoarseWidth", gridCoarse.getWidth());
    jsonWriter.put("gridCoarseHeight", gridCoarse.getHeight());
    jsonWriter.put("innerSpacesX", innerDots.getWidth() ? gridCoarse.getWidth() / innerDots.getWidth() : 0);
    jsonWriter.put("innerSpacesY", innerDots.getHeight() ? gridCoarse.getHeight() / innerDots.getHeight() : 0);

    if (pSdPage)
        pSdPage->GetPageInfo(jsonWriter);
    else
        SAL_WARN("sd", "getPartInfo request for SdPage " << nPart << " that does not exist!");

    return OStringToOUString(jsonWriter.finishAndGetAsOString(), RTL_TEXTENCODING_UTF8);
}

void SdXImpressDocument::setPart( int nPart, bool bAllowChangeFocus )
{
    DrawViewShell* pViewSh = GetViewShell();
    if (!pViewSh)
        return;

    pViewSh->SwitchPage( nPart, bAllowChangeFocus );
}

int SdXImpressDocument::getParts()
{
    if (!mpDoc)
        return 0;

    if (isMasterViewMode())
        return mpDoc->GetMasterSdPageCount(PageKind::Standard);

    return mpDoc->GetSdPageCount(PageKind::Standard);
}

int SdXImpressDocument::getPart()
{
    DrawViewShell* pViewSh = GetViewShell();
    if (!pViewSh)
        return 0;

    return pViewSh->GetViewShellBase().getPart();
}

OUString SdXImpressDocument::getPartName(int nPart)
{
    SdPage* pPage;
    if (isMasterViewMode())
        pPage = mpDoc->GetMasterSdPage(nPart, PageKind::Standard);
    else
        pPage = mpDoc->GetSdPage(nPart, PageKind::Standard);

    if (!pPage)
    {
        SAL_WARN("sd", "DrawViewShell not available!");
        return OUString();
    }

    return pPage->GetName();
}

OUString SdXImpressDocument::getPartHash(int nPart)
{
    SdPage* pPage;
    if (isMasterViewMode())
        pPage = mpDoc->GetMasterSdPage(nPart, PageKind::Standard);
    else
        pPage = mpDoc->GetSdPage(nPart, PageKind::Standard);

    if (!pPage)
    {
        SAL_WARN("sd", "DrawViewShell not available!");
        return OUString();
    }

    uno::Reference<drawing::XDrawPage> xDrawPage(pPage->getUnoPage(), uno::UNO_QUERY);
    return OUString::fromUtf8(GetInterfaceHash(xDrawPage));
}

bool SdXImpressDocument::isMasterViewMode()
{
    DrawViewShell* pViewSh = GetViewShell();
    if (!pViewSh)
        return false;

    if (pViewSh->GetDispatcher())
    {
        SfxPoolItemHolder aResult;
        pViewSh->GetDispatcher()->QueryState(SID_SLIDE_MASTER_MODE, aResult);
        const SfxBoolItem* isMasterViewMode(static_cast<const SfxBoolItem*>(aResult.getItem()));
        if (isMasterViewMode && isMasterViewMode->GetValue())
            return true;
    }
    return false;
}

VclPtr<vcl::Window> SdXImpressDocument::getDocWindow()
{
    SolarMutexGuard aGuard;
    DrawViewShell* pViewShell = GetViewShell();
    if (!pViewShell)
        return {};

    if (VclPtr<vcl::Window> pWindow = SfxLokHelper::getInPlaceDocWindow(pViewShell->GetViewShell()))
        return pWindow;

    return pViewShell->GetActiveWindow();
}

void SdXImpressDocument::setPartMode( int nPartMode )
{
    DrawViewShell* pViewSh = GetViewShell();
    if (!pViewSh)
        return;

    PageKind aPageKind( PageKind::Standard );
    switch ( nPartMode )
    {
    case LOK_PARTMODE_SLIDES:
        break;
    case LOK_PARTMODE_NOTES:
        aPageKind = PageKind::Notes;
        break;
    }
    pViewSh->SetPageKind( aPageKind );
    //TODO do the same as setEditMode and then can probably remove the TODOs
    //from doc_setPartMode
}

int SdXImpressDocument::getEditMode()
{
    DrawViewShell* pViewSh = GetViewShell();
    if (!pViewSh)
        return 0;

    return pViewSh->GetViewShellBase().getEditMode();
}

void SdXImpressDocument::setEditMode(int nMode)
{
    SolarMutexGuard aGuard;

    DrawViewShell* pViewSh = GetViewShell();
    if (!pViewSh)
        return;

    pViewSh->GetViewShellBase().setEditMode(nMode);
}

Size SdXImpressDocument::getDocumentSize()
{
    DrawViewShell* pViewSh = GetViewShell();
    if (!pViewSh)
        return Size();

    SdrView *pSdrView = pViewSh->GetView();
    if (!pSdrView)
        return Size();

    SdrPageView* pCurPageView = pSdrView->GetSdrPageView();
    if (!pCurPageView)
        return Size();

    Size aSize = pCurPageView->GetPageRect().GetSize();
    // Convert the size in 100th mm to TWIP
    // See paintTile above for further info.
    return o3tl::convert(aSize, o3tl::Length::mm100, o3tl::Length::twip);
}

void SdXImpressDocument::getPostIts(::tools::JsonWriter& rJsonWriter)
{
    auto commentsNode = rJsonWriter.startNode("comments");
    if (!mpDoc)
        return;
    // Return annotations on master pages too ?
    const sal_uInt16 nMaxPages = mpDoc->GetPageCount();
    for (sal_uInt16 nPage = 0; nPage < nMaxPages; ++nPage)
    {
        SdrPage* pPage = mpDoc->GetPage(nPage);

        for (auto const& xAnnotation : pPage->getAnnotations())
        {
            sal_uInt32 nID = xAnnotation->GetId();
            OString nodeName = "comment" + OString::number(nID);
            auto commentNode = rJsonWriter.startNode(nodeName);
            rJsonWriter.put("id", nID);
            rJsonWriter.put("author", xAnnotation->getAuthor());
            rJsonWriter.put("dateTime", utl::toISO8601(xAnnotation->getDateTime()));
            uno::Reference<text::XText> xText(xAnnotation->getTextRange());
            rJsonWriter.put("text", xText->getString());
            rJsonWriter.put("parthash", pPage->GetUniqueID());
            geometry::RealPoint2D const aPoint = xAnnotation->getPosition();
            geometry::RealSize2D const aSize = xAnnotation->getSize();
            ::tools::Rectangle aRectangle(Point(aPoint.X * 100.0, aPoint.Y * 100.0), Size(aSize.Width * 100.0, aSize.Height * 100.0));
            aRectangle = o3tl::toTwips(aRectangle, o3tl::Length::mm100);
            OString sRectangle = aRectangle.toString();
            rJsonWriter.put("rectangle", sRectangle.getStr());
        }
    }
}

void SdXImpressDocument::initializeForTiledRendering(const css::uno::Sequence<css::beans::PropertyValue>& rArguments)
{
    SolarMutexGuard aGuard;

    OUString sThemeName;
    OUString sBackgroundThemeName;

    if (DrawViewShell* pViewShell = GetViewShell())
    {
        DrawView* pDrawView = pViewShell->GetDrawView();
        for (const beans::PropertyValue& rValue : rArguments)
        {
            if (rValue.Name == ".uno:ShowBorderShadow" && rValue.Value.has<bool>())
                pDrawView->SetPageShadowVisible(rValue.Value.get<bool>());
            else if (rValue.Name == ".uno:Author" && rValue.Value.has<OUString>())
                pDrawView->SetAuthor(rValue.Value.get<OUString>());
            else if (rValue.Name == ".uno:SpellOnline" && rValue.Value.has<bool>())
                mpDoc->SetOnlineSpell(rValue.Value.get<bool>());
            else if (rValue.Name == ".uno:ChangeTheme" && rValue.Value.has<OUString>())
                sThemeName = rValue.Value.get<OUString>();
            else if (rValue.Name == ".uno:InvertBackground" && rValue.Value.has<OUString>())
                sBackgroundThemeName = rValue.Value.get<OUString>();
        }

        // Disable comments if requested
        SdOptions* pOptions = SdModule::get()->GetSdOptions(mpDoc->GetDocumentType());
        pOptions->SetShowComments(comphelper::LibreOfficeKit::isTiledAnnotations());

        pViewShell->SetRuler(false);
        pViewShell->SetScrollBarsVisible(false);

        if (sd::Window* pWindow = pViewShell->GetActiveWindow())
        {
            // get the full page size in pixels
            pWindow->EnableMapMode();
            Size aSize(pWindow->LogicToPixel(pDrawView->GetSdrPageView()->GetPage()->GetSize()));
            // Disable map mode, so that it's possible to send mouse event
            // coordinates in logic units
            pWindow->EnableMapMode(false);

            // arrange UI elements again with new view size
            pViewShell->GetParentWindow()->SetSizePixel(aSize);
            pViewShell->Resize();
        }

        // Forces all images to be swapped in synchronously, this
        // ensures that images are available when paintTile is called
        // (whereas with async loading images start being loaded after
        //  we have painted the tile, resulting in an invalidate, followed
        //  by the tile being rerendered - which is wasteful and ugly).
        pDrawView->SetSwapAsynchron(false);
    }

    // when the "This document may contain formatting or content that cannot
    // be saved..." dialog appears, it is auto-cancelled with tiled rendering,
    // causing 'Save' being disabled; so let's always save to the original
    // format
    auto xChanges = comphelper::ConfigurationChanges::create();
    officecfg::Office::Common::Save::Document::WarnAlienFormat::set(false, xChanges);

    if (!o3tl::IsRunningUnitTest() || !comphelper::LibreOfficeKit::isActive())
        officecfg::Office::Impress::MultiPaneGUI::SlideSorterBar::Visible::ImpressView::set(true,xChanges);
    xChanges->commit();

    // if we know what theme the user wants, then we can dispatch that now early
    if (!sThemeName.isEmpty())
    {
        css::uno::Sequence<css::beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
        {
            { "NewTheme", uno::Any(sThemeName) }
        }));
        comphelper::dispatchCommand(u".uno:ChangeTheme"_ustr, aPropertyValues);
    }
    if (!sBackgroundThemeName.isEmpty())
    {
        css::uno::Sequence<css::beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
        {
            { "NewTheme", uno::Any(sBackgroundThemeName) }
        }));
        comphelper::dispatchCommand(".uno:InvertBackground", aPropertyValues);
    }
}

void SdXImpressDocument::postKeyEvent(int nType, int nCharCode, int nKeyCode)
{
    SolarMutexGuard aGuard;
    SfxLokHelper::postKeyEventAsync(getDocWindow(), nType, nCharCode, nKeyCode);
}

void SdXImpressDocument::postMouseEvent(int nType, int nX, int nY, int nCount, int nButtons, int nModifier)
{
    SolarMutexGuard aGuard;

    DrawViewShell* pViewShell = GetViewShell();
    if (!pViewShell)
        return;

    constexpr double fScale = o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::px);

    if (SfxLokHelper::testInPlaceComponentMouseEventHit(
            pViewShell->GetViewShell(), nType, nX, nY, nCount, nButtons, nModifier, fScale, fScale))
        return;

    // try to forward mouse event to control
    const Point aPointTwip(nX, nY);
    const Point aPointHMM = o3tl::convert(aPointTwip, o3tl::Length::twip, o3tl::Length::mm100);
    SdrView* pDrawView = pViewShell->GetDrawView();
    SdrPageView* pPageView = pDrawView->GetSdrPageView();
    if (!pPageView)
    {
        return;
    }

    SdrPage* pPage = pPageView->GetPage();
    ::sd::Window* pActiveWin = pViewShell->GetActiveWindow();
    if (!pActiveWin)
    {
        return;
    }

    if (LokControlHandler::postMouseEvent(pPage, pDrawView, *pActiveWin, nType, aPointHMM, nCount, nButtons, nModifier))
            return;

    LokMouseEventData aMouseEventData(nType, aPointHMM, nCount, MouseEventModifiers::SIMPLECLICK,
                                      nButtons, nModifier);
    SfxLokHelper::postMouseEventAsync(pViewShell->GetActiveWindow(), aMouseEventData);
}

void SdXImpressDocument::setTextSelection(int nType, int nX, int nY)
{
    SolarMutexGuard aGuard;

    DrawViewShell* pViewShell = GetViewShell();
    if (!pViewShell)
        return;

    LokChartHelper aChartHelper(pViewShell->GetViewShell());
    if (aChartHelper.setTextSelection(nType, nX, nY))
        return;

    Point aPoint(convertTwipToMm100(nX), convertTwipToMm100(nY));
    switch (nType)
    {
    case LOK_SETTEXTSELECTION_START:
        pViewShell->SetCursorMm100Position(aPoint, /*bPoint=*/false, /*bClearMark=*/false);
        break;
    case LOK_SETTEXTSELECTION_END:
        pViewShell->SetCursorMm100Position(aPoint, /*bPoint=*/true, /*bClearMark=*/false);
        break;
    case LOK_SETTEXTSELECTION_RESET:
        pViewShell->SetCursorMm100Position(aPoint, /*bPoint=*/true, /*bClearMark=*/true);
        break;
    default:
        assert(false);
        break;
    }
}

uno::Reference<datatransfer::XTransferable> SdXImpressDocument::getSelection()
{
    SolarMutexGuard aGuard;

    DrawViewShell* pViewShell = GetViewShell();
    if (!pViewShell)
        return uno::Reference<datatransfer::XTransferable>();

    return pViewShell->GetSelectionTransferable();
}

void SdXImpressDocument::setGraphicSelection(int nType, int nX, int nY)
{
    SolarMutexGuard aGuard;

    DrawViewShell* pViewShell = GetViewShell();
    if (!pViewShell)
        return;

    constexpr double fScale = o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::px);

    LokChartHelper aChartHelper(pViewShell->GetViewShell());
    if (aChartHelper.setGraphicSelection(nType, nX, nY, fScale, fScale))
        return;

    Point aPoint(convertTwipToMm100(nX), convertTwipToMm100(nY));
    switch (nType)
    {
    case LOK_SETGRAPHICSELECTION_START:
        pViewShell->SetGraphicMm100Position(/*bStart=*/true, aPoint);
        break;
    case LOK_SETGRAPHICSELECTION_END:
        pViewShell->SetGraphicMm100Position(/*bStart=*/false, aPoint);
        break;
    default:
        assert(false);
        break;
    }
}

void SdXImpressDocument::resetSelection()
{
    SolarMutexGuard aGuard;

    DrawViewShell* pViewShell = GetViewShell();
    if (!pViewShell)
        return;

    SdrView* pSdrView = pViewShell->GetView();
    if (!pSdrView)
        return;

    if (pSdrView->IsTextEdit())
    {
        // Reset the editeng selection.
        pSdrView->UnmarkAll();
        // Finish editing.
        pSdrView->SdrEndTextEdit();
    }
    // Reset graphic selection.
    pSdrView->UnmarkAll();
}

void SdXImpressDocument::setClientVisibleArea(const ::tools::Rectangle& rRectangle)
{
    SolarMutexGuard aGuard;

    DrawViewShell* pViewShell = GetViewShell();
    if (!pViewShell)
        return;

    pViewShell->GetViewShellBase().setLOKVisibleArea(rRectangle);
}

void SdXImpressDocument::setClipboard(const uno::Reference<datatransfer::clipboard::XClipboard>& xClipboard)
{
    SolarMutexGuard aGuard;

    DrawViewShell* pViewShell = GetViewShell();
    if (!pViewShell)
        return;

    pViewShell->GetActiveWindow()->SetClipboard(xClipboard);
}

bool SdXImpressDocument::isMimeTypeSupported()
{
    SolarMutexGuard aGuard;
    DrawViewShell* pViewShell = GetViewShell();
    if (!pViewShell)
        return false;

    TransferableDataHelper aDataHelper(TransferableDataHelper::CreateFromSystemClipboard(pViewShell->GetActiveWindow()));
    return EditEngine::HasValidData(aDataHelper.GetTransferable());
}

PointerStyle SdXImpressDocument::getPointer()
{
    SolarMutexGuard aGuard;
    DrawViewShell* pViewShell = GetViewShell();
    if (!pViewShell)
        return PointerStyle::Arrow;

    Window* pWindow = pViewShell->GetActiveWindow();
    if (!pWindow)
        return PointerStyle::Arrow;

    return pWindow->GetPointer();
}

uno::Reference< i18n::XForbiddenCharacters > SdXImpressDocument::getForbiddenCharsTable()
{
    rtl::Reference<SdUnoForbiddenCharsTable> xRef = mxForbiddenCharacters.get();
    if( !xRef )
    {
        xRef = new SdUnoForbiddenCharsTable( mpDoc );
        mxForbiddenCharacters = xRef.get();
    }
    return xRef;
}

void SdXImpressDocument::initializeDocument()
{
    if( mbClipBoard )
        return;

    switch( mpDoc->GetPageCount() )
    {
    case 1:
    {
        // nasty hack to detect clipboard document
        mbClipBoard = true;
        break;
    }
    case 0:
    {
        mpDoc->CreateFirstPages();
        mpDoc->StopWorkStartupDelay();
        break;
    }
    }
}

static
void getShapeClickAction(const uno::Reference<drawing::XShape> &xShape, ::tools::JsonWriter& rJsonWriter)
{
    bool bIsShapeVisible = true;
    uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
    if (!xShapeProps)
        return;

    if (!xShapeProps->getPropertySetInfo()->hasPropertyByName( u"Visible"_ustr ))
        return;
    xShapeProps->getPropertyValue("Visible") >>= bIsShapeVisible;

    if (!bIsShapeVisible)
        return;

    if (!xShapeProps->getPropertySetInfo()->hasPropertyByName( u"OnClick"_ustr ))
        return;

    presentation::ClickAction eClickAction = presentation::ClickAction_NONE;
    xShapeProps->getPropertyValue(u"OnClick"_ustr) >>= eClickAction;

    if (eClickAction != presentation::ClickAction_NONE)
    {
        auto aShape = rJsonWriter.startStruct();

        sal_Int32 nVerb = 0;
        OUString sBookmark;

        if (xShapeProps->getPropertySetInfo()->hasPropertyByName( u"Bookmark"_ustr ))
            xShapeProps->getPropertyValue(u"Bookmark"_ustr) >>= sBookmark;

        {
            auto* pObject = SdrObject::getSdrObjectFromXShape(xShape);
            auto const& rRectangle = pObject->GetLogicRect();
            auto aRectangle = o3tl::convert(rRectangle, o3tl::Length::mm100, o3tl::Length::twip);
            auto aRect = rJsonWriter.startNode("bounds");
            rJsonWriter.put("x", aRectangle.Left());
            rJsonWriter.put("y", aRectangle.Top());
            rJsonWriter.put("width", aRectangle.GetWidth());
            rJsonWriter.put("height", aRectangle.GetHeight());
        }

        {
            auto aInteraction = rJsonWriter.startNode("clickAction");
            switch (eClickAction)
            {
            case presentation::ClickAction_BOOKMARK:
                rJsonWriter.put("action", "bookmark");
                rJsonWriter.put("bookmark", sBookmark);
                break;
            case presentation::ClickAction_DOCUMENT:
                rJsonWriter.put("action", "document");
                rJsonWriter.put("document", sBookmark);
                break;

            case presentation::ClickAction_PREVPAGE:
                rJsonWriter.put("action", "prevpage");
                break;
            case presentation::ClickAction_NEXTPAGE:
                rJsonWriter.put("action", "nextpage");
                break;

            case presentation::ClickAction_FIRSTPAGE:
                rJsonWriter.put("action", "firstpage");
                break;
            case presentation::ClickAction_LASTPAGE:
                rJsonWriter.put("action", "lastpage");
                break;

            case presentation::ClickAction_SOUND:
                rJsonWriter.put("action", "sound");
                rJsonWriter.put("sound", sBookmark);
                break;

            case presentation::ClickAction_VERB:
                rJsonWriter.put("action", "verb");
                xShapeProps->getPropertyValue(u"Verb"_ustr) >>= nVerb;
                rJsonWriter.put("verb", nVerb);
                break;

            case presentation::ClickAction_PROGRAM:
                rJsonWriter.put("action", "program");
                rJsonWriter.put("program", sBookmark);
                break;

            case presentation::ClickAction_MACRO:
                rJsonWriter.put("action", "macro");
                rJsonWriter.put("macro", sBookmark);
                break;

            case presentation::ClickAction_STOPPRESENTATION:
                rJsonWriter.put("action", "stoppresentation");
                break;

            default:
                break;
            }
        }
    }
}

OString SdXImpressDocument::getPresentationInfo(bool bAllyState) const
{
    ::tools::JsonWriter aJsonWriter;

    try
    {
        rtl::Reference<SdDrawPagesAccess> xDrawPages = const_cast<SdXImpressDocument*>(this)->getSdDrawPages();
        // size in twips
        Size aDocSize = const_cast<SdXImpressDocument*>(this)->getDocumentSize();
        aJsonWriter.put("docWidth", aDocSize.getWidth());
        aJsonWriter.put("docHeight", aDocSize.getHeight());

        sd::PresentationSettings const& rSettings = mpDoc->getPresentationSettings();

        const bool bIsEndless = rSettings.mbEndless;
        aJsonWriter.put("isEndless", bIsEndless);

        if (bIsEndless) {
            const sal_Int32 nPauseTimeout = rSettings.mnPauseTimeout;
            aJsonWriter.put("loopAndRepeatDuration", nPauseTimeout);
        }

        auto aSlideList = aJsonWriter.startArray("slides");
        sal_Int32 nSlideCount = xDrawPages->getCount();
        for (sal_Int32 i = 0; i < nSlideCount; ++i)
        {
            SdGenericDrawPage* pSlide(xDrawPages->getDrawPageByIndex(i));
            SdPage* pPage = SdPage::getImplementation(pSlide);
            if (!pPage)
            {
                SAL_WARN("sd", "slide: " << i << " is disposed, skipping");
                continue;
            }

            bool bIsVisible = true; // default visible
            pSlide->getPropertyValue("Visible") >>= bIsVisible;

            if (!bIsVisible)
            {
                auto aSlideNode = aJsonWriter.startStruct();
                std::string sSlideHash = GetInterfaceHash(cppu::getXWeak(pSlide));
                aJsonWriter.put("hash", sSlideHash);
                aJsonWriter.put("index", i);
                aJsonWriter.put("hidden", true);
                aJsonWriter.put("uniqueID", pPage->GetUniqueID());
            }
            else
            {
                auto aSlideNode = aJsonWriter.startStruct();
                std::string sSlideHash = GetInterfaceHash(cppu::getXWeak(pSlide));
                aJsonWriter.put("hash", sSlideHash);
                aJsonWriter.put("index", i);
                aJsonWriter.put("uniqueID", pPage->GetUniqueID());

                auto aName = SdDrawPage::getPageApiNameFromUiName(pPage->GetName());
                aJsonWriter.put("name", aName);

                if (bAllyState)
                {
                    OUStringBuffer aHtml;
                    SdrOutliner* pOutliner = mpDoc->GetInternalOutliner();
                    if (pOutliner)
                    {
                        SdHTMLFilter::ExportPage(pOutliner, pPage, aHtml);
                        aJsonWriter.put("a11y", aHtml.makeStringAndClear());
                        pOutliner->Clear();
                    }
                }

                bool bIsDrawPageEmpty = pSlide->getCount() == 0;
                aJsonWriter.put("empty", bIsDrawPageEmpty);

                // Notes
                SdPage* pNotesPage = mpDoc->GetSdPage((pPage->GetPageNum() - 1) >> 1, PageKind::Notes);
                if (pNotesPage)
                {
                    SdrObject* pItObj = nullptr;
                    SdrObject* pNotes = pNotesPage->GetPresObj(PresObjKind::Notes);

                    if (!pNotes)
                    {
                        for (size_t nNote = 0; nNote < pNotesPage->GetObjCount(); nNote++)
                        {
                            pItObj = pNotesPage->GetObj(nNote);

                            if (pItObj && pItObj->HasText() &&
                                (pItObj->GetObjIdentifier() == SdrObjKind::Text
                                 || pItObj->GetObjIdentifier() == SdrObjKind::TitleText
                                 || pItObj->GetObjIdentifier() == SdrObjKind::OutlineText))
                            {
                                pNotes = pItObj;
                                break;
                            }
                        }
                    }

                    if (pNotes)
                    {
                        OUStringBuffer strNotes;
                        OutlinerParaObject* pPara = pNotes->GetOutlinerParaObject();
                        if (pPara)
                        {
                            const EditTextObject& rText = pPara->GetTextObject();
                            for (sal_Int32 nNote = 0; nNote < rText.GetParagraphCount(); nNote++)
                            {
                                strNotes.append(rText.GetText(nNote));
                            }
                            aJsonWriter.put("notes", strNotes.makeStringAndClear());
                        }
                    }
                }

                SdMasterPage* pMasterPage = nullptr;
                SdDrawPage* pMasterPageTarget(dynamic_cast<SdDrawPage*>(pSlide));
                if (pMasterPageTarget)
                {
                    pMasterPage = pMasterPageTarget->getSdMasterPage();
                    if (pMasterPage)
                    {
                        std::string sMPHash = GetInterfaceHash(cppu::getXWeak(pMasterPage));
                        aJsonWriter.put("masterPage", sMPHash);

                        bool bBackgroundObjectsVisibility = true; // default visible
                        pSlide->getPropertyValue("IsBackgroundObjectsVisible") >>= bBackgroundObjectsVisibility;
                        aJsonWriter.put("masterPageObjectsVisibility", bBackgroundObjectsVisibility);
                    }
                }

                bool bBackgroundVisibility = true; // default visible
                pSlide->getPropertyValue("IsBackgroundVisible")  >>= bBackgroundVisibility;
                if (bBackgroundVisibility)
                {
                    SlideBackgroundInfo aSlideBackgroundInfo(pSlide, static_cast<SvxDrawPage*>(pMasterPage));
                    if (aSlideBackgroundInfo.hasBackground())
                    {
                        auto aBackgroundNode = aJsonWriter.startNode("background");
                        aJsonWriter.put("isCustom", aSlideBackgroundInfo.slideHasOwnBackground());
                        if (aSlideBackgroundInfo.isSolidColor())
                        {
                            aJsonWriter.put("fillColor", aSlideBackgroundInfo.getFillColorAsRGBA());
                        }
                    }
                }

                {
                    auto aVideoList = aJsonWriter.startArray("videos");
                    SdrObjListIter aIterator(pPage, SdrIterMode::DeepWithGroups);
                    while (aIterator.IsMore())
                    {
                        auto* pObject = aIterator.Next();
                        if (pObject->GetObjIdentifier() == SdrObjKind::Media)
                        {
                            auto aVideosNode = aJsonWriter.startStruct();
                            auto* pMediaObject = static_cast<SdrMediaObj*>(pObject);
                            auto const& rRectangle = pMediaObject->GetLogicRect();
                            auto aRectangle = o3tl::convert(rRectangle, o3tl::Length::mm100, o3tl::Length::twip);
                            aJsonWriter.put("id", reinterpret_cast<sal_uInt64>(pMediaObject));
                            aJsonWriter.put("url", pMediaObject->getTempURL());
                            aJsonWriter.put("x", aRectangle.Left());
                            aJsonWriter.put("y", aRectangle.Top());
                            aJsonWriter.put("width", aRectangle.GetWidth());
                            aJsonWriter.put("height", aRectangle.GetHeight());
                        }
                    }
                }

                uno::Reference<drawing::XShapes> const xShapes(cppu::getXWeak(pSlide), uno::UNO_QUERY_THROW);
                if (xShapes.is())
                {
                    auto aInteractions = aJsonWriter.startArray("interactions");
                    auto count = xShapes->getCount();
                    for (auto j = 0; j < count; j++)
                    {
                        auto xObject = xShapes->getByIndex(j);
                        uno::Reference<drawing::XShape> xShape(xObject, uno::UNO_QUERY);
                        if (!xShape.is())
                        {
                            continue;
                        }

                        getShapeClickAction(xShape, aJsonWriter);
                    }
                }

                sal_Int32 nTransitionType = 0;
                pSlide->getPropertyValue("TransitionType") >>= nTransitionType;

                if (nTransitionType != 0)
                {
                    auto iterator = constTransitionTypeToString.find(nTransitionType);

                    if (iterator != constTransitionTypeToString.end())
                    {
                        aJsonWriter.put("transitionType", iterator->second);

                        sal_Int32 nTransitionSubtype = 0;
                        pSlide->getPropertyValue("TransitionSubtype") >>= nTransitionSubtype;

                        auto iteratorSubType = constTransitionSubTypeToString.find(nTransitionSubtype);
                        if (iteratorSubType != constTransitionSubTypeToString.end())
                        {
                            aJsonWriter.put("transitionSubtype", iteratorSubType->second);
                        }
                        else
                        {
                            SAL_WARN("sd", "Transition sub-type unknown: " << nTransitionSubtype);
                        }

                        if (bAllyState)
                        {
                            const TransitionPresetList& rList = TransitionPreset::getTransitionPresetList();
                            auto aTransition = std::find_if(rList.begin(), rList.end(),
                                                            [nTransitionType,
                                                             nTransitionSubtype](const TransitionPresetPtr& rxPreset) {
                                                                return (rxPreset->getTransition() == nTransitionType)
                                                                    && (rxPreset->getSubtype() == nTransitionSubtype);
                                                            });

                            if (aTransition != rList.end())
                            {
                                aJsonWriter.put("transitionLabel", (*aTransition)->getSetLabel());
                            }
                        }

                        bool nTransitionDirection = false;
                        pSlide->getPropertyValue("TransitionDirection") >>= nTransitionDirection;
                        aJsonWriter.put("transitionDirection", nTransitionDirection);

                        // fade color
                        if ((nTransitionType == TransitionType::FADE)
                                && ((nTransitionSubtype == TransitionSubType::FADETOCOLOR)
                                    || (nTransitionSubtype == TransitionSubType::FADEFROMCOLOR)
                                    || (nTransitionSubtype == TransitionSubType::FADEOVERCOLOR)))
                        {
                            sal_Int32 nFadeColor = 0;
                            pSlide->getPropertyValue("TransitionFadeColor") >>= nFadeColor;
                            OUStringBuffer sTmpBuf;
                            ::sax::Converter::convertColor(sTmpBuf, nFadeColor);
                            aJsonWriter.put("transitionFadeColor", sTmpBuf.makeStringAndClear());
                        }
                    }

                    double nTransitionDuration(0.0);
                    if( pSlide->getPropertySetInfo()->hasPropertyByName( "TransitionDuration" ) &&
                        (pSlide->getPropertyValue( "TransitionDuration" ) >>= nTransitionDuration ) && nTransitionDuration != 0.0 )
                    {
                        // convert transitionDuration time to ms
                        aJsonWriter.put("transitionDuration", nTransitionDuration * 1000);
                    }
                }

                sal_Int32 nChange(0);
                if( pSlide->getPropertySetInfo()->hasPropertyByName( "Change" ) &&
                        (pSlide->getPropertyValue( "Change" ) >>= nChange ) && nChange == 1 )
                {
                    double fSlideDuration(0);
                    if( pSlide->getPropertySetInfo()->hasPropertyByName( "HighResDuration" ) &&
                            (pSlide->getPropertyValue( "HighResDuration" ) >>= fSlideDuration) )
                    {
                        // convert slide duration time to ms
                        aJsonWriter.put("nextSlideDuration", fSlideDuration * 1000);
                    }
                }


                AnimationsExporter aAnimationExporter(aJsonWriter, pSlide);
                if (aAnimationExporter.hasEffects())
                {
                    {
                        auto aAnimationsNode = aJsonWriter.startNode("animations");
                        aAnimationExporter.exportAnimations();
                    }
                    aAnimationExporter.exportTriggers();
                }
            }
        }
    }
    catch (uno::Exception& )
    {
        TOOLS_WARN_EXCEPTION("sd", "SdXImpressDocument::getSlideShowInfo ... maybe some property can't be retrieved");
    }
    return aJsonWriter.finishAndGetAsOString();
}

namespace
{
bool isRequestedSlideValid(SdDrawDocument* mpDoc, sal_Int32 nSlideNumber, const std::string& slideHash)
{
    try
    {
        SdXImpressDocument* pDoc = mpDoc->getUnoModel();
        rtl::Reference<SdDrawPagesAccess> xDrawPages = pDoc->getSdDrawPages();
        SdGenericDrawPage* pSlide(xDrawPages->getDrawPageByIndex(nSlideNumber));
        if (pSlide)
        {
            return slideHash == GetInterfaceHash(cppu::getXWeak(pSlide));
        }
    }
    catch (uno::Exception&)
    {
        TOOLS_WARN_EXCEPTION( "sd", "SdXImpressDocument::createLOKSlideRenderer: failed" );
    }
    return false;
}
}

bool SdXImpressDocument::createSlideRenderer(
    const OString& rSlideHash,
    sal_Int32 nSlideNumber, sal_Int32& nViewWidth, sal_Int32& nViewHeight,
    bool bRenderBackground, bool bRenderMasterPage)
{
    std::string sSlideHash(rSlideHash);
    if (!isRequestedSlideValid(mpDoc, nSlideNumber, sSlideHash))
        return false;

    SdPage* pPage = mpDoc->GetSdPage(sal_uInt16(nSlideNumber), PageKind::Standard);
    if (!pPage)
        return false;

    mpSlideshowLayerRenderer.reset(new SlideshowLayerRenderer(*pPage, rSlideHash, bRenderBackground, bRenderMasterPage));
    Size aDesiredSize(nViewWidth, nViewHeight);
    Size aCalculatedSize = mpSlideshowLayerRenderer->calculateAndSetSizePixel(aDesiredSize);
    nViewWidth = aCalculatedSize.Width();
    nViewHeight = aCalculatedSize.Height();
    return true;
}

void SdXImpressDocument::postSlideshowCleanup()
{
    DrawViewShell* pViewSh = GetViewShell();
    if (!pViewSh)
        return;

    pViewSh->destroyXSlideShowInstance();
}

bool SdXImpressDocument::renderNextSlideLayer(unsigned char* pBuffer, bool& bIsBitmapLayer, double& rScale, OUString& rJsonMsg)
{
    bool bDone = true;

    if (!mpSlideshowLayerRenderer)
        return bDone;

    OString sMsg;
    bool bOK = mpSlideshowLayerRenderer->render(pBuffer, bIsBitmapLayer, rScale, sMsg);

    if (bOK)
    {
        rJsonMsg = OUString::fromUtf8(sMsg);
        bDone = false;
    }

    return bDone;
}

SdrModel& SdXImpressDocument::getSdrModelFromUnoModel() const
{
    OSL_ENSURE(GetDoc(), "No SdrModel in draw/Impress, should not happen");
    return *GetDoc(); // TTTT should be reference
}

void SAL_CALL SdXImpressDocument::dispose()
{
    if( mbDisposed )
        return;

    ::SolarMutexGuard aGuard;

    if( mpDoc )
    {
        EndListening( *mpDoc );
        mpDoc = nullptr;
    }

    // Call the base class dispose() before setting the mbDisposed flag
    // to true.  The reason for this is that if close() has not yet been
    // called this is done in SfxBaseModel::dispose().  At the end of
    // that dispose() is called again.  It is important to forward this
    // second dispose() to the base class, too.
    // As a consequence the following code has to be able to be run twice.
    SfxBaseModel::dispose();
    mbDisposed = true;

    rtl::Reference< SdDocLinkTargets > xLinks( mxLinks );
    if( xLinks.is() )
    {
        xLinks->dispose();
        xLinks = nullptr;
    }

    rtl::Reference< SdDrawPagesAccess > xDrawPagesAccess( mxDrawPagesAccess );
    if( xDrawPagesAccess.is() )
    {
        xDrawPagesAccess->dispose();
        xDrawPagesAccess = nullptr;
    }

    rtl::Reference< SdMasterPagesAccess > xMasterPagesAccess( mxMasterPagesAccess );
    if( xDrawPagesAccess.is() )
    {
        xMasterPagesAccess->dispose();
        xMasterPagesAccess = nullptr;
    }

    rtl::Reference< SdLayerManager > xLayerManager( mxLayerManager );
    if( xLayerManager.is() )
    {
        xLayerManager->dispose();
        xLayerManager = nullptr;
    }

    mxDashTable = nullptr;
    mxGradientTable = nullptr;
    mxHatchTable = nullptr;
    mxBitmapTable = nullptr;
    mxTransGradientTable = nullptr;
    mxMarkerTable = nullptr;
    mxDrawingPool = nullptr;
    mpDocShell = nullptr;
}

SdDrawPagesAccess::SdDrawPagesAccess( SdXImpressDocument& rMyModel )  noexcept
:   mpModel( &rMyModel)
{
}

SdDrawPagesAccess::~SdDrawPagesAccess() noexcept
{
}

// XIndexAccess
sal_Int32 SAL_CALL SdDrawPagesAccess::getCount()
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpModel )
        throw lang::DisposedException();

    return mpModel->mpDoc->GetSdPageCount( PageKind::Standard );
}

uno::Any SAL_CALL SdDrawPagesAccess::getByIndex( sal_Int32 Index )
{
    uno::Reference< drawing::XDrawPage > xDrawPage( getDrawPageByIndex(Index) );
    return uno::Any(xDrawPage);
}

SdGenericDrawPage* SdDrawPagesAccess::getDrawPageByIndex( sal_Int32 Index )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpModel )
        throw lang::DisposedException();

    if( (Index < 0) || (Index >= mpModel->mpDoc->GetSdPageCount( PageKind::Standard ) ) )
        throw lang::IndexOutOfBoundsException();

    SdPage* pPage = mpModel->mpDoc->GetSdPage( static_cast<sal_uInt16>(Index), PageKind::Standard );
    if( pPage )
        return dynamic_cast<SdGenericDrawPage*>( pPage->getUnoPage().get() );

    return nullptr;
}

// XNameAccess
uno::Any SAL_CALL SdDrawPagesAccess::getByName( const OUString& aName )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpModel )
        throw lang::DisposedException();

    if( !aName.isEmpty() )
    {
        const sal_uInt16 nCount = mpModel->mpDoc->GetSdPageCount( PageKind::Standard );
        sal_uInt16 nPage;
        for( nPage = 0; nPage < nCount; nPage++ )
        {
            SdPage* pPage = mpModel->mpDoc->GetSdPage( nPage, PageKind::Standard );
            if(nullptr == pPage)
                continue;

            if( aName == SdDrawPage::getPageApiName( pPage ) )
            {
                uno::Any aAny;
                uno::Reference< drawing::XDrawPage >  xDrawPage( pPage->getUnoPage(), uno::UNO_QUERY );
                aAny <<= xDrawPage;
                return aAny;
            }
        }
    }

    throw container::NoSuchElementException();
}

uno::Sequence< OUString > SAL_CALL SdDrawPagesAccess::getElementNames()
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpModel )
        throw lang::DisposedException();

    const sal_uInt16 nCount = mpModel->mpDoc->GetSdPageCount( PageKind::Standard );
    uno::Sequence< OUString > aNames( nCount );
    OUString* pNames = aNames.getArray();

    sal_uInt16 nPage;
    for( nPage = 0; nPage < nCount; nPage++ )
    {
        SdPage* pPage = mpModel->mpDoc->GetSdPage( nPage, PageKind::Standard );
        *pNames++ = SdDrawPage::getPageApiName( pPage );
    }

    return aNames;
}

sal_Bool SAL_CALL SdDrawPagesAccess::hasByName( const OUString& aName )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpModel )
        throw lang::DisposedException();

    const sal_uInt16 nCount = mpModel->mpDoc->GetSdPageCount( PageKind::Standard );
    sal_uInt16 nPage;
    for( nPage = 0; nPage < nCount; nPage++ )
    {
        SdPage* pPage = mpModel->mpDoc->GetSdPage( nPage, PageKind::Standard );
        if(nullptr == pPage)
            continue;

        if( aName == SdDrawPage::getPageApiName( pPage ) )
            return true;
    }

    return false;
}

// XElementAccess
uno::Type SAL_CALL SdDrawPagesAccess::getElementType()
{
    return cppu::UnoType<drawing::XDrawPage>::get();
}

sal_Bool SAL_CALL SdDrawPagesAccess::hasElements()
{
    return getCount() > 0;
}

// XDrawPages

/**
 * Creates a new page with model at the specified position.
 * @returns corresponding SdDrawPage
 */
uno::Reference< drawing::XDrawPage > SAL_CALL SdDrawPagesAccess::insertNewByIndex( sal_Int32 nIndex )
{
    ::SolarMutexGuard aGuard;
    comphelper::ProfileZone aZone("insertNewByIndex");

    if( nullptr == mpModel )
        throw lang::DisposedException();

    if( mpModel->mpDoc )
    {
        SdPage* pPage = mpModel->InsertSdPage( static_cast<sal_uInt16>(nIndex), false );
        if( pPage )
        {
            uno::Reference< drawing::XDrawPage > xDrawPage( pPage->getUnoPage(), uno::UNO_QUERY );
            return xDrawPage;
        }
    }
    uno::Reference< drawing::XDrawPage > xDrawPage;
    return xDrawPage;
}

/**
 * Removes the specified SdDrawPage from the model and the internal list. It
 * only works, if there is at least one *normal* page in the model after
 * removing this page.
 */
void SAL_CALL SdDrawPagesAccess::remove( const uno::Reference< drawing::XDrawPage >& xPage )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpModel || mpModel->mpDoc == nullptr )
        throw lang::DisposedException();

    SdDrawDocument& rDoc = *mpModel->mpDoc;

    sal_uInt16 nPageCount = rDoc.GetSdPageCount( PageKind::Standard );
    if( nPageCount > 1 )
    {
        // get pPage from xPage and determine the Id (nPos ) afterwards
        SdDrawPage* pSvxPage = comphelper::getFromUnoTunnel<SdDrawPage>( xPage );
        if( pSvxPage )
        {
            SdPage* pPage = static_cast<SdPage*>(pSvxPage->GetSdrPage());
            if(pPage && ( pPage->GetPageKind() == PageKind::Standard ) )
            {
                sal_uInt16 nPage = pPage->GetPageNum();

                SdPage* pNotesPage = static_cast< SdPage* >( rDoc.GetPage( nPage+1 ) );

                bool bUndo = rDoc.IsUndoEnabled();
                if( bUndo )
                {
                    // Add undo actions and delete the pages.  The order of adding
                    // the undo actions is important.
                    rDoc.BegUndo( SdResId( STR_UNDO_DELETEPAGES ) );
                    if (pNotesPage)
                    {
                        rDoc.AddUndo(rDoc.GetSdrUndoFactory().CreateUndoDeletePage(*pNotesPage));
                    }
                    rDoc.AddUndo(rDoc.GetSdrUndoFactory().CreateUndoDeletePage(*pPage));
                }

                rDoc.RemovePage( nPage ); // the page
                if (pNotesPage)
                {
                    rDoc.RemovePage( nPage ); // the notes page
                }

                if( bUndo )
                {
                    rDoc.EndUndo();
                }
            }
        }
    }

    mpModel->SetModified();
}

// XServiceInfo

OUString SAL_CALL SdDrawPagesAccess::getImplementationName(  )
{
    return u"SdDrawPagesAccess"_ustr;
}

sal_Bool SAL_CALL SdDrawPagesAccess::supportsService( const OUString& ServiceName )
{
    return cppu::supportsService(this, ServiceName);
}

uno::Sequence< OUString > SAL_CALL SdDrawPagesAccess::getSupportedServiceNames(  )
{
    return { u"com.sun.star.drawing.DrawPages"_ustr };
}

// XComponent
void SAL_CALL SdDrawPagesAccess::dispose(  )
{
    mpModel = nullptr;
}

void SAL_CALL SdDrawPagesAccess::addEventListener( const uno::Reference< lang::XEventListener >&  )
{
    OSL_FAIL( "not implemented!" );
}

void SAL_CALL SdDrawPagesAccess::removeEventListener( const uno::Reference< lang::XEventListener >&  )
{
    OSL_FAIL( "not implemented!" );
}


SdMasterPagesAccess::SdMasterPagesAccess( SdXImpressDocument& rMyModel ) noexcept
:   mpModel(&rMyModel)
{
}

SdMasterPagesAccess::~SdMasterPagesAccess() noexcept
{
}

// XComponent
void SAL_CALL SdMasterPagesAccess::dispose(  )
{
    mpModel = nullptr;
}

void SAL_CALL SdMasterPagesAccess::addEventListener( const uno::Reference< lang::XEventListener >&  )
{
    OSL_FAIL( "not implemented!" );
}

void SAL_CALL SdMasterPagesAccess::removeEventListener( const uno::Reference< lang::XEventListener >&  )
{
    OSL_FAIL( "not implemented!" );
}

// XIndexAccess
sal_Int32 SAL_CALL SdMasterPagesAccess::getCount()
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpModel->mpDoc )
        throw lang::DisposedException();

    return mpModel->mpDoc->GetMasterSdPageCount(PageKind::Standard);
}

/**
 * Provides a drawing::XDrawPage interface for accessing the Masterpage at the
 * specified position in the model.
 */
uno::Any SAL_CALL SdMasterPagesAccess::getByIndex( sal_Int32 Index )
{
    ::SolarMutexGuard aGuard;
    comphelper::ProfileZone aZone("SdMasterPagesAccess::getByIndex");

    if( nullptr == mpModel )
        throw lang::DisposedException();

    uno::Any aAny;

    if( (Index < 0) || (Index >= mpModel->mpDoc->GetMasterSdPageCount( PageKind::Standard ) ) )
        throw lang::IndexOutOfBoundsException();

    SdPage* pPage = mpModel->mpDoc->GetMasterSdPage( static_cast<sal_uInt16>(Index), PageKind::Standard );
    if( pPage )
    {
        uno::Reference< drawing::XDrawPage >  xDrawPage( pPage->getUnoPage(), uno::UNO_QUERY );
        aAny <<= xDrawPage;
    }

    return aAny;
}

// XElementAccess
uno::Type SAL_CALL SdMasterPagesAccess::getElementType()
{
    return cppu::UnoType<drawing::XDrawPage>::get();
}

sal_Bool SAL_CALL SdMasterPagesAccess::hasElements()
{
    return getCount() > 0;
}

// XDrawPages
uno::Reference< drawing::XDrawPage > SAL_CALL SdMasterPagesAccess::insertNewByIndex( sal_Int32 nInsertPos )
{
    return insertNewImpl(nInsertPos, std::nullopt);
}

// XDrawPages2
uno::Reference< drawing::XDrawPage > SAL_CALL SdMasterPagesAccess::insertNamedNewByIndex( sal_Int32 nInsertPos, const OUString& sName )
{
    return insertNewImpl(nInsertPos, sName);
}

uno::Reference< drawing::XDrawPage > SdMasterPagesAccess::insertNewImpl( sal_Int32 nInsertPos, std::optional<OUString> oPageName )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpModel )
        throw lang::DisposedException();

    uno::Reference< drawing::XDrawPage > xDrawPage;

    SdDrawDocument* pDoc = mpModel->mpDoc;
    if( pDoc )
    {
        // calculate internal index and check for range errors
        const sal_Int32 nMPageCount = pDoc->GetMasterPageCount();
        nInsertPos = nInsertPos * 2 + 1;
        if( nInsertPos < 0 || nInsertPos > nMPageCount )
            nInsertPos = nMPageCount;

        // now generate a unique name for the new masterpage
        OUString aPrefix;
        if (oPageName)
            aPrefix = *oPageName;
        else
        {
            const OUString aStdPrefix( SdResId(STR_LAYOUT_DEFAULT_NAME) );
            aPrefix = aStdPrefix;

            bool bUnique = true;

            std::vector<OUString> aPageNames;
            for (sal_Int32 nMaster = 1; nMaster < nMPageCount; ++nMaster)
            {
                const SdPage* pPage = static_cast<const SdPage*>(pDoc->GetMasterPage(static_cast<sal_uInt16>(nMaster)));
                if (!pPage)
                    continue;
                aPageNames.push_back(pPage->GetName());
                if (aPageNames.back() == aPrefix)
                    bUnique = false;
            }

            sal_Int32 i = 0;
            while (!bUnique)
            {
                aPrefix = aStdPrefix + " " + OUString::number(++i);
                bUnique = std::find(aPageNames.begin(), aPageNames.end(), aPrefix) == aPageNames.end();
            }
        }
        OUString aLayoutName = aPrefix + SD_LT_SEPARATOR + STR_LAYOUT_OUTLINE;

        // create styles
        static_cast<SdStyleSheetPool*>(pDoc->GetStyleSheetPool())->CreateLayoutStyleSheets( aPrefix );

        // get the first page for initial size and border settings
        SdPage* pPage = mpModel->mpDoc->GetSdPage( sal_uInt16(0), PageKind::Standard );
        SdPage* pRefNotesPage = mpModel->mpDoc->GetSdPage( sal_uInt16(0), PageKind::Notes);

        // create and insert new draw masterpage
        rtl::Reference<SdPage> pMPage = mpModel->mpDoc->AllocSdPage(true);
        pMPage->SetSize( pPage->GetSize() );
        pMPage->SetBorder( pPage->GetLeftBorder(),
                           pPage->GetUpperBorder(),
                           pPage->GetRightBorder(),
                           pPage->GetLowerBorder() );
        if (oPageName)
            // no need to update the page URLs on a brand new page
            pMPage->SetName(*oPageName, /*bUpdatePageRelativeURLs*/false);
        pMPage->SetLayoutName( aLayoutName );
        pDoc->InsertMasterPage(pMPage.get(),  static_cast<sal_uInt16>(nInsertPos));

        {
            // ensure default MasterPage fill
            pMPage->EnsureMasterPageDefaultBackground();
        }

        xDrawPage.set( pMPage->getUnoPage(), uno::UNO_QUERY );

        // create and insert new notes masterpage
        rtl::Reference<SdPage> pMNotesPage = mpModel->mpDoc->AllocSdPage(true);
        pMNotesPage->SetSize( pRefNotesPage->GetSize() );
        pMNotesPage->SetPageKind(PageKind::Notes);
        pMNotesPage->SetBorder( pRefNotesPage->GetLeftBorder(),
                                pRefNotesPage->GetUpperBorder(),
                                pRefNotesPage->GetRightBorder(),
                                pRefNotesPage->GetLowerBorder() );
        pMNotesPage->SetLayoutName( aLayoutName );
        pDoc->InsertMasterPage(pMNotesPage.get(),  static_cast<sal_uInt16>(nInsertPos) + 1);
        pMNotesPage->SetAutoLayout(AUTOLAYOUT_NOTES, true, true);
        mpModel->SetModified();
    }

    return xDrawPage;
}

/**
 * Removes the specified SdMasterPage from the model and the internal list. It
 * only works, if there is no *normal* page using this page as MasterPage in
 * the model.
 */
void SAL_CALL SdMasterPagesAccess::remove( const uno::Reference< drawing::XDrawPage >& xPage )
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpModel || mpModel->mpDoc == nullptr )
        throw lang::DisposedException();

    SdMasterPage* pSdPage = comphelper::getFromUnoTunnel<SdMasterPage>( xPage );
    if(pSdPage == nullptr)
        return;

    SdPage* pPage = dynamic_cast< SdPage* > (pSdPage->GetSdrPage());

    DBG_ASSERT( pPage && pPage->IsMasterPage(), "SdMasterPage is not masterpage?");

    if( !pPage || !pPage->IsMasterPage() || (mpModel->mpDoc->GetMasterPageUserCount(pPage) > 0))
        return; //Todo: this should be excepted

    // only standard pages can be removed directly
    if( pPage->GetPageKind() != PageKind::Standard )
        return;

    sal_uInt16 nPage = pPage->GetPageNum();

    SdDrawDocument& rDoc = *mpModel->mpDoc;

    SdPage* pNotesPage = static_cast< SdPage* >( rDoc.GetMasterPage( nPage+1 ) );

    bool bUndo = rDoc.IsUndoEnabled();
    if( bUndo )
    {
        // Add undo actions and delete the pages.  The order of adding
        // the undo actions is important.
        rDoc.BegUndo( SdResId( STR_UNDO_DELETEPAGES ) );
        rDoc.AddUndo(rDoc.GetSdrUndoFactory().CreateUndoDeletePage(*pNotesPage));
        rDoc.AddUndo(rDoc.GetSdrUndoFactory().CreateUndoDeletePage(*pPage));
    }

    // remove both pages
    rDoc.RemoveMasterPage( nPage );
    rDoc.RemoveMasterPage( nPage );

    if( bUndo )
    {
        rDoc.EndUndo();
    }
}

// XServiceInfo

OUString SAL_CALL SdMasterPagesAccess::getImplementationName(  )
{
    return u"SdMasterPagesAccess"_ustr;
}

sal_Bool SAL_CALL SdMasterPagesAccess::supportsService( const OUString& ServiceName )
{
    return cppu::supportsService(this, ServiceName);
}

uno::Sequence< OUString > SAL_CALL SdMasterPagesAccess::getSupportedServiceNames(  )
{
    return { u"com.sun.star.drawing.MasterPages"_ustr };
}

SdDocLinkTargets::SdDocLinkTargets(SdXImpressDocument& rMyModel)
    : mpModel(&rMyModel)
{
    for (sal_uInt16 i=0; i < SdLinkTargetType::Count; i++)
        aNames[i] = SdResId(aTypeResIds[i]);
}

SdDocLinkTargets::~SdDocLinkTargets() noexcept
{
}

// XComponent
void SAL_CALL SdDocLinkTargets::dispose(  )
{
    mpModel = nullptr;
}

void SAL_CALL SdDocLinkTargets::addEventListener( const uno::Reference< lang::XEventListener >&  )
{
    OSL_FAIL( "not implemented!" );
}

void SAL_CALL SdDocLinkTargets::removeEventListener( const uno::Reference< lang::XEventListener >&  )
{
    OSL_FAIL( "not implemented!" );
}

// XNameAccess
uno::Any SAL_CALL SdDocLinkTargets::getByName( const OUString& aName )
{
    if (mpModel)
    {
        for (sal_uInt16 i=0; i < SdLinkTargetType::Count; i++)
            if ( aNames[i] == aName )
                return uno::Any(uno::Reference< beans::XPropertySet >(new SdDocLinkTargetType( mpModel, i )));
    }

    throw container::NoSuchElementException();
}

uno::Sequence< OUString > SAL_CALL SdDocLinkTargets::getElementNames()
{
    uno::Sequence<OUString> aRet(SdLinkTargetType::Count);
    OUString* pArray = aRet.getArray();
    for (sal_uInt16 i=0; i < SdLinkTargetType::Count; i++)
        pArray[i] = aNames[i];
    return aRet;
}

sal_Bool SAL_CALL SdDocLinkTargets::hasByName( const OUString& aName )
{
    for (const auto & i : aNames)
        if ( i == aName )
            return true;
    return false;
}

// container::XElementAccess
uno::Type SAL_CALL SdDocLinkTargets::getElementType()
{
    return cppu::UnoType<beans::XPropertySet>::get();
}

sal_Bool SAL_CALL SdDocLinkTargets::hasElements()
{
    return true;
}

SdPage* SdDocLinkTarget::FindPage( std::u16string_view rName ) const
{
    SdDrawDocument* pDoc = mpModel->GetDoc();
    if( pDoc == nullptr )
        return nullptr;

    const sal_uInt16 nMaxPages = pDoc->GetPageCount();
    const sal_uInt16 nMaxMasterPages = pDoc->GetMasterPageCount();

    sal_uInt16 nPage;
    SdPage* pPage;

    const bool bDraw = pDoc->GetDocumentType() == DocumentType::Draw;

    // standard pages
    for( nPage = 0; nPage < nMaxPages; nPage++ )
    {
        pPage = static_cast<SdPage*>(pDoc->GetPage( nPage ));
        if( (pPage->GetName() == rName) && (!bDraw || (pPage->GetPageKind() == PageKind::Standard)) )
            return pPage;
    }

    // master pages
    for( nPage = 0; nPage < nMaxMasterPages; nPage++ )
    {
        pPage = static_cast<SdPage*>(pDoc->GetMasterPage( nPage ));
        if( (pPage->GetName() == rName) && (!bDraw || (pPage->GetPageKind() == PageKind::Standard)) )
            return pPage;
    }

    return nullptr;
}

// XServiceInfo
OUString SAL_CALL SdDocLinkTargets::getImplementationName()
{
    return u"SdDocLinkTargets"_ustr;
}

sal_Bool SAL_CALL SdDocLinkTargets::supportsService( const OUString& ServiceName )
{
    return cppu::supportsService( this, ServiceName );
}

uno::Sequence< OUString > SAL_CALL SdDocLinkTargets::getSupportedServiceNames()
{
    return { u"com.sun.star.document.LinkTargets"_ustr };
}

SdDocLinkTargetType::SdDocLinkTargetType(SdXImpressDocument* pModel, sal_uInt16 nT)
    : mpModel(pModel)
    , mnType(nT)
{
    maName = SdResId(aTypeResIds[nT]);
}

// beans::XPropertySet

uno::Reference< beans::XPropertySetInfo > SAL_CALL SdDocLinkTargetType::getPropertySetInfo()
{
    static uno::Reference< beans::XPropertySetInfo > aRef;//(new SfxItemPropertySetInfo( lcl_GetLinkTargetMap() ));
    return aRef;
}

void SAL_CALL SdDocLinkTargetType::setPropertyValue(const OUString& /* aPropertyName */,
            const uno::Any& /* aValue */)
{
    //  everything is read-only
}

uno::Any SAL_CALL SdDocLinkTargetType::getPropertyValue(const OUString& PropertyName)
{
    uno::Any aRet;
    if ( PropertyName == "LinkDisplayName" )
        aRet <<= maName;

    return aRet;
}

void SAL_CALL SdDocLinkTargetType::addPropertyChangeListener( const OUString&,
                            const uno::Reference<beans::XPropertyChangeListener>&)
{ OSL_FAIL("not implemented"); }

void SAL_CALL SdDocLinkTargetType::removePropertyChangeListener( const OUString&,
                        const uno::Reference<beans::XPropertyChangeListener>&)
{ OSL_FAIL("not implemented"); }

void SAL_CALL SdDocLinkTargetType::addVetoableChangeListener( const OUString&,
                        const uno::Reference<beans::XVetoableChangeListener>&)
{ OSL_FAIL("not implemented"); }

void SAL_CALL SdDocLinkTargetType::removeVetoableChangeListener( const OUString&,
                        const uno::Reference<beans::XVetoableChangeListener>&)
{ OSL_FAIL("not implemented"); }

// document::XLinkTargetSupplier

uno::Reference< container::XNameAccess > SAL_CALL SdDocLinkTargetType::getLinks()
{
    return new SdDocLinkTarget( mpModel, mnType );
}

// XServiceInfo
OUString SAL_CALL SdDocLinkTargetType::getImplementationName()
{
    return u"SdDocLinkTargetType"_ustr;
}

sal_Bool SAL_CALL SdDocLinkTargetType::supportsService( const OUString& ServiceName )
{
    return cppu::supportsService( this, ServiceName );
}

uno::Sequence< OUString > SAL_CALL SdDocLinkTargetType::getSupportedServiceNames()
{
    return { u"com.sun.star.document.LinkTargetSupplier"_ustr };
}

SdDocLinkTarget::SdDocLinkTarget( SdXImpressDocument* pModel, sal_uInt16 nT )
    : mpModel(pModel)
    , mnType(nT)
{
}

// container::XNameAccess

uno::Any SAL_CALL SdDocLinkTarget::getByName(const OUString& aName)
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpModel )
        throw lang::DisposedException();

    SdPage* pPage = FindPage( aName );

    if( pPage == nullptr )
        throw container::NoSuchElementException();

    uno::Any aAny;

    uno::Reference< beans::XPropertySet > xProps( pPage->getUnoPage(), uno::UNO_QUERY );
    if( xProps.is() )
        aAny <<= xProps;

    return aAny;
}

uno::Sequence<OUString> SAL_CALL SdDocLinkTarget::getElementNames()
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpModel )
        throw lang::DisposedException();

    SdDrawDocument* pDoc = mpModel->GetDoc();
    if( pDoc == nullptr )
    {
        return { };
    }

    if( pDoc->GetDocumentType() == DocumentType::Draw )
    {
        const sal_uInt16 nMaxPages = pDoc->GetSdPageCount( PageKind::Standard );
        const sal_uInt16 nMaxMasterPages = pDoc->GetMasterSdPageCount( PageKind::Standard );

        uno::Sequence< OUString > aSeq( mnType == SdLinkTargetType::Page ? nMaxPages : nMaxMasterPages );
        OUString* pStr = aSeq.getArray();

        sal_uInt16 nPage;
        if (mnType == SdLinkTargetType::Page)
        {
            // standard pages
            for( nPage = 0; nPage < nMaxPages; nPage++ )
                *pStr++ = pDoc->GetSdPage( nPage, PageKind::Standard )->GetName();
        }
        else
        {
            // master pages
            for( nPage = 0; nPage < nMaxMasterPages; nPage++ )
                *pStr++ = pDoc->GetMasterSdPage( nPage, PageKind::Standard )->GetName();
        }
        return aSeq;
    }
    else
    {
        PageKind eKind;
        switch (mnType)
        {
            case SdLinkTargetType::Notes:
                eKind = PageKind::Notes;
                break;
            case SdLinkTargetType::Handout:
                eKind = PageKind::Handout;
                break;
            default:
                eKind = PageKind::Standard;
                break;
        }
        const sal_uInt16 nMaxPages = pDoc->GetSdPageCount( eKind );
        const sal_uInt16 nMaxMasterPages = pDoc->GetMasterPageCount();

        uno::Sequence< OUString > aSeq( mnType == SdLinkTargetType::MasterPage ? nMaxMasterPages : nMaxPages );
        OUString* pStr = aSeq.getArray();

        sal_uInt16 nPage;
        switch (mnType)
        {
            case SdLinkTargetType::Page:
            {
                for( nPage = 0; nPage < nMaxPages; nPage++ )
                    *pStr++ = pDoc->GetSdPage( nPage, PageKind::Standard )->GetName();
                break;
            }
            case SdLinkTargetType::Notes:
            {
                for( nPage = 0; nPage < nMaxPages; nPage++ )
                    *pStr++ = pDoc->GetSdPage( nPage, PageKind::Notes )->GetName();
                break;
            }
            case SdLinkTargetType::Handout:
            {
                for( nPage = 0; nPage < nMaxPages; nPage++ )
                    *pStr++ = pDoc->GetSdPage( nPage, PageKind::Handout )->GetName();
                break;
            }
            case SdLinkTargetType::MasterPage:
            {
                for( nPage = 0; nPage < nMaxMasterPages; nPage++ )
                    *pStr++ = static_cast<SdPage*>(pDoc->GetMasterPage( nPage ))->GetName();
                break;
            }
        }
        return aSeq;
    }
}

sal_Bool SAL_CALL SdDocLinkTarget::hasByName(const OUString& aName)
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpModel )
        throw lang::DisposedException();

    return FindPage( aName ) != nullptr;
}

// container::XElementAccess

uno::Type SAL_CALL SdDocLinkTarget::getElementType()
{
    return cppu::UnoType<beans::XPropertySet>::get();
}

sal_Bool SAL_CALL SdDocLinkTarget::hasElements()
{
    ::SolarMutexGuard aGuard;

    if( nullptr == mpModel )
        throw lang::DisposedException();

    return mpModel->GetDoc() != nullptr;
}

// XServiceInfo
OUString SAL_CALL SdDocLinkTarget::getImplementationName()
{
    return u"SdDocLinkTarget"_ustr;
}

sal_Bool SAL_CALL SdDocLinkTarget::supportsService( const OUString& ServiceName )
{
    return cppu::supportsService( this, ServiceName );
}

uno::Sequence< OUString > SAL_CALL SdDocLinkTarget::getSupportedServiceNames()
{
    return { u"com.sun.star.document.LinkTargets"_ustr };
}

rtl::Reference< SdXImpressDocument > SdXImpressDocument::GetModel( SdDrawDocument const & rDocument )
{
    rtl::Reference< SdXImpressDocument > xRet;
    ::sd::DrawDocShell* pDocShell(rDocument.GetDocSh());
    if( pDocShell )
    {
        uno::Reference<frame::XModel> xModel(pDocShell->GetModel());

        xRet.set( dynamic_cast< SdXImpressDocument* >( xModel.get() ) );
    }

    return xRet;
}

void NotifyDocumentEvent(SdDrawDocument const & rDocument, const OUString& rEventName)
{
    rtl::Reference<SdXImpressDocument> xModel(SdXImpressDocument::GetModel(rDocument));

    if (xModel.is())
    {
        uno::Reference<uno::XInterface> xSource(static_cast<uno::XWeak*>(xModel.get()));
        NotifyDocumentEvent(rDocument, rEventName, xSource);
    }
}

void NotifyDocumentEvent(SdDrawDocument const & rDocument, const OUString& rEventName, const uno::Reference<uno::XInterface>& xSource)
{
    rtl::Reference<SdXImpressDocument> xModel(SdXImpressDocument::GetModel(rDocument));

    if (xModel.is())
    {
        css::document::EventObject aEvent(xSource, rEventName);
        xModel->notifyEvent(aEvent);
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
