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

#include <textboxhelper.hxx>
#include <dcontact.hxx>
#include <fmtcntnt.hxx>
#include <fmtanchr.hxx>
#include <fmtcnct.hxx>
#include <fmtornt.hxx>
#include <fmtfsize.hxx>
#include <doc.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentSettingAccess.hxx>
#include <IDocumentState.hxx>
#include <docsh.hxx>
#include <unocoll.hxx>
#include <unoframe.hxx>
#include <unodraw.hxx>
#include <unotextrange.hxx>
#include <cmdid.h>
#include <unomid.h>
#include <unoprnms.hxx>
#include <mvsave.hxx>
#include <fmtsrnd.hxx>
#include <fmtfollowtextflow.hxx>
#include <frmfmt.hxx>
#include <frameformats.hxx>
#include <dflyobj.hxx>
#include <swtable.hxx>

#include <editeng/unoprnms.hxx>
#include <editeng/memberids.h>
#include <svx/svdoashp.hxx>
#include <svx/svdpage.hxx>
#include <svl/itemiter.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <sal/log.hxx>
#include <tools/UnitConversion.hxx>
#include <svx/swframetypes.hxx>
#include <drawdoc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <IDocumentDrawModelAccess.hxx>
#include <frmatr.hxx>
#include <unotextbodyhf.hxx>

#include <com/sun/star/document/XActionLockable.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <com/sun/star/text/SizeType.hpp>
#include <com/sun/star/text/WrapTextMode.hpp>
#include <com/sun/star/text/XTextDocument.hpp>
#include <com/sun/star/text/XTextFrame.hpp>
#include <com/sun/star/table/BorderLine2.hpp>
#include <com/sun/star/text/WritingMode.hpp>
#include <com/sun/star/text/WritingMode2.hpp>
#include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
#include <com/sun/star/style/ParagraphAdjust.hpp>
#include <unotxdoc.hxx>

using namespace com::sun::star;

void SwTextBoxHelper::create(SwFrameFormat* pShape, SdrObject* pObject, bool bCopyText)
{
    assert(pShape);
    assert(pObject);
    assert(pShape == ::FindFrameFormat(pObject));

    // If TextBox wasn't enabled previously
    if (pShape->GetOtherTextBoxFormats() && pShape->GetOtherTextBoxFormats()->GetTextBox(pObject))
        return;

    uno::Reference<drawing::XShape> xShape(pObject->getUnoShape());
    // Store the current text content of the shape
    OUString sCopyableText;

    if (bCopyText)
    {
        if (pObject)
        {
            uno::Reference<text::XText> xSrcCnt(xShape, uno::UNO_QUERY);
            auto xCur = xSrcCnt->createTextCursor();
            xCur->gotoStart(false);
            xCur->gotoEnd(true);
            sCopyableText = xCur->getText()->getString();
        }
    }

    // Create the associated TextFrame and insert it into the document.
    rtl::Reference<SwXTextFrame> xTextFrame
        = SwXTextFrame::CreateXTextFrame(pShape->GetDoc(), nullptr);

    uno::Reference<text::XTextRange> xAnchor;
    uno::Reference<text::XTextContent> xAnchorProvider(xShape, uno::UNO_QUERY);
    assert(xAnchorProvider.is());
    if (xAnchorProvider.is())
        xAnchor = xAnchorProvider->getAnchor();

    uno::Reference<text::XTextContentAppend> xTextContentAppend;
    if (xAnchor)
        xTextContentAppend.set(xAnchor->getText(), uno::UNO_QUERY);

    if (!xTextContentAppend)
    {
        if (SwDocShell* pShell = pShape->GetDoc().GetDocShell())
        {
            rtl::Reference<SwXTextDocument> xTextDocument(pShell->GetBaseModel());
            xTextContentAppend = xTextDocument->getBodyText();
        }
    }

    if (xAnchor)
    {
        // insertTextContentWithProperties would fail if xAnchor is in a different XText
        assert(xAnchor->getText() == xTextContentAppend);
        xTextContentAppend->insertTextContentWithProperties(
            uno::Reference<text::XTextContent>(cppu::getXWeak(xTextFrame.get()), uno::UNO_QUERY),
            {}, xAnchor);
    }
    else
    {
        xTextContentAppend->appendTextContent(
            uno::Reference<text::XTextContent>(cppu::getXWeak(xTextFrame.get()), uno::UNO_QUERY),
            uno::Sequence<beans::PropertyValue>());
    }

    // Link FLY and DRAW formats, so it becomes a text box (needed for syncProperty calls).
    SwFrameFormat* pFormat = xTextFrame->GetFrameFormat();

    assert(nullptr != dynamic_cast<SwDrawFrameFormat*>(pShape));
    assert(nullptr != dynamic_cast<SwFlyFrameFormat*>(pFormat));

    if (!pShape->GetOtherTextBoxFormats())
    {
        auto pTextBox = std::make_shared<SwTextBoxNode>(SwTextBoxNode(pShape));
        pTextBox->AddTextBox(pObject, pFormat);
        pShape->SetOtherTextBoxFormats(pTextBox);
        pFormat->SetOtherTextBoxFormats(pTextBox);
    }
    else
    {
        auto& pTextBox = pShape->GetOtherTextBoxFormats();
        pTextBox->AddTextBox(pObject, pFormat);
        pFormat->SetOtherTextBoxFormats(pTextBox);
    }
    // Initialize properties.
    uno::Any aEmptyBorder{ table::BorderLine2() };
    xTextFrame->setPropertyValue(UNO_NAME_TOP_BORDER, aEmptyBorder);
    xTextFrame->setPropertyValue(UNO_NAME_BOTTOM_BORDER, aEmptyBorder);
    xTextFrame->setPropertyValue(UNO_NAME_LEFT_BORDER, aEmptyBorder);
    xTextFrame->setPropertyValue(UNO_NAME_RIGHT_BORDER, aEmptyBorder);

    xTextFrame->setPropertyValue(UNO_NAME_FILL_TRANSPARENCE, uno::Any(sal_Int32(100)));

    xTextFrame->setPropertyValue(UNO_NAME_SIZE_TYPE, uno::Any(text::SizeType::FIX));

    xTextFrame->setPropertyValue(UNO_NAME_SURROUND, uno::Any(text::WrapTextMode_THROUGH));

    assert(!xTextFrame->getName().isEmpty());

    // Link its text range to the original shape.
    SwUnoInternalPaM aInternalPaM(pShape->GetDoc());
    if (sw::XTextRangeToSwPaM(aInternalPaM, xTextFrame))
    {
        SwAttrSet aSet(pShape->GetAttrSet());
        SwFormatContent aContent(aInternalPaM.GetPointNode().StartOfSectionNode());
        aSet.Put(aContent);
        pShape->SetFormatAttr(aSet);
    }

    DoTextBoxZOrderCorrection(pShape, pObject);

    // Also initialize the properties, which are not constant, but inherited from the shape's ones.
    syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_SIZE, uno::Any(xShape->getSize()), pObject);

    uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);
    syncProperty(pShape, RES_FOLLOW_TEXT_FLOW, MID_FOLLOW_TEXT_FLOW,
                 xShapePropertySet->getPropertyValue(UNO_NAME_IS_FOLLOWING_TEXT_FLOW), pObject);
    syncProperty(pShape, RES_ANCHOR, MID_ANCHOR_ANCHORTYPE,
                 xShapePropertySet->getPropertyValue(UNO_NAME_ANCHOR_TYPE), pObject);
    syncProperty(pShape, RES_HORI_ORIENT, MID_HORIORIENT_ORIENT,
                 xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT), pObject);
    syncProperty(pShape, RES_HORI_ORIENT, MID_HORIORIENT_RELATION,
                 xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT_RELATION), pObject);
    syncProperty(pShape, RES_VERT_ORIENT, MID_VERTORIENT_ORIENT,
                 xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT), pObject);
    syncProperty(pShape, RES_VERT_ORIENT, MID_VERTORIENT_RELATION,
                 xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT_RELATION), pObject);
    syncProperty(pShape, RES_HORI_ORIENT, MID_HORIORIENT_POSITION,
                 xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT_POSITION), pObject);
    syncProperty(pShape, RES_VERT_ORIENT, MID_VERTORIENT_POSITION,
                 xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT_POSITION), pObject);
    syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_IS_AUTO_HEIGHT,
                 xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT), pObject);
    // tdf#162075 shape word wrap to frame width type on shape creation.
    bool bTextWordwrap = xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_WORDWRAP).get<bool>();
    syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_WIDTH_TYPE,
                 uno::Any(bTextWordwrap ? text::SizeType::FIX : text::SizeType::MIN), pObject);
    syncProperty(pShape, RES_TEXT_VERT_ADJUST, 0,
                 xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_VERT_ADJUST), pObject);
    text::WritingMode eMode;
    if (xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_WRITINGMODE) >>= eMode)
        syncProperty(pShape, RES_FRAMEDIR, 0, uno::Any(sal_Int16(eMode)), pObject);

    changeAnchor(pShape, pObject);
    syncTextBoxSize(pShape, pObject);

    // Check if the shape had text before and move it to the new textframe
    if (!bCopyText || sCopyableText.isEmpty())
        return;

    if (pObject)
    {
        auto pSourceText = DynCastSdrTextObj(pObject);

        xTextFrame->setString(sCopyableText);

        if (pSourceText)
            pSourceText->SetText(OUString());

        pShape->GetDoc().getIDocumentState().SetModified();
    }
}

void SwTextBoxHelper::set(SwFrameFormat* pShapeFormat, SdrObject* pObj,
                          const uno::Reference<text::XTextFrame>& xNew)
{
    // Do not set invalid data
    assert(pShapeFormat && pObj && xNew);
    // Firstly find the format of the new textbox.
    SwFrameFormat* pFormat = nullptr;
    if (auto pTextFrame = dynamic_cast<SwXTextFrame*>(xNew.get()))
        pFormat = pTextFrame->GetFrameFormat();
    if (!pFormat)
        return;

    // If there is a format, check if the shape already has a textbox assigned to.
    if (auto& pTextBoxNode = pShapeFormat->GetOtherTextBoxFormats())
    {
        // If it has a texbox, destroy it.
        if (pTextBoxNode->GetTextBox(pObj))
            pTextBoxNode->DelTextBox(pObj, true);
        // And set the new one.
        pTextBoxNode->AddTextBox(pObj, pFormat);
        pFormat->SetOtherTextBoxFormats(pTextBoxNode);
    }
    else
    {
        // If the shape do not have a texbox node and textbox,
        // create that for the shape.
        auto pTextBox = std::make_shared<SwTextBoxNode>(SwTextBoxNode(pShapeFormat));
        pTextBox->AddTextBox(pObj, pFormat);
        pShapeFormat->SetOtherTextBoxFormats(pTextBox);
        pFormat->SetOtherTextBoxFormats(pTextBox);
    }
    // Initialize its properties
    uno::Reference<beans::XPropertySet> xPropertySet(xNew, uno::UNO_QUERY);
    uno::Any aEmptyBorder{ table::BorderLine2() };
    xPropertySet->setPropertyValue(UNO_NAME_TOP_BORDER, aEmptyBorder);
    xPropertySet->setPropertyValue(UNO_NAME_BOTTOM_BORDER, aEmptyBorder);
    xPropertySet->setPropertyValue(UNO_NAME_LEFT_BORDER, aEmptyBorder);
    xPropertySet->setPropertyValue(UNO_NAME_RIGHT_BORDER, aEmptyBorder);
    xPropertySet->setPropertyValue(UNO_NAME_FILL_TRANSPARENCE, uno::Any(sal_Int32(100)));
    xPropertySet->setPropertyValue(UNO_NAME_SIZE_TYPE, uno::Any(text::SizeType::FIX));
    xPropertySet->setPropertyValue(UNO_NAME_SURROUND, uno::Any(text::WrapTextMode_THROUGH));
    // Add a new name to it
    uno::Reference<container::XNamed> xNamed(xNew, uno::UNO_QUERY);
    assert(!xNamed->getName().isEmpty());
    (void)xNamed;
    // And sync. properties.
    uno::Reference<drawing::XShape> xShape(pObj->getUnoShape(), uno::UNO_QUERY);
    syncProperty(pShapeFormat, RES_FRM_SIZE, MID_FRMSIZE_SIZE, uno::Any(xShape->getSize()), pObj);
    uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);
    syncProperty(pShapeFormat, RES_ANCHOR, MID_ANCHOR_ANCHORTYPE,
                 xShapePropertySet->getPropertyValue(UNO_NAME_ANCHOR_TYPE), pObj);
    syncProperty(pShapeFormat, RES_HORI_ORIENT, MID_HORIORIENT_ORIENT,
                 xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT), pObj);
    syncProperty(pShapeFormat, RES_HORI_ORIENT, MID_HORIORIENT_RELATION,
                 xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT_RELATION), pObj);
    syncProperty(pShapeFormat, RES_VERT_ORIENT, MID_VERTORIENT_ORIENT,
                 xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT), pObj);
    syncProperty(pShapeFormat, RES_VERT_ORIENT, MID_VERTORIENT_RELATION,
                 xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT_RELATION), pObj);
    syncProperty(pShapeFormat, RES_HORI_ORIENT, MID_HORIORIENT_POSITION,
                 xShapePropertySet->getPropertyValue(UNO_NAME_HORI_ORIENT_POSITION), pObj);
    syncProperty(pShapeFormat, RES_VERT_ORIENT, MID_VERTORIENT_POSITION,
                 xShapePropertySet->getPropertyValue(UNO_NAME_VERT_ORIENT_POSITION), pObj);
    syncProperty(pShapeFormat, RES_FRM_SIZE, MID_FRMSIZE_IS_AUTO_HEIGHT,
                 xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT), pObj);
    drawing::TextVerticalAdjust aVertAdj = drawing::TextVerticalAdjust_CENTER;
    if ((uno::Reference<beans::XPropertyState>(xShape, uno::UNO_QUERY_THROW))
            ->getPropertyState(UNO_NAME_TEXT_VERT_ADJUST)
        != beans::PropertyState::PropertyState_DEFAULT_VALUE)
    {
        aVertAdj = xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_VERT_ADJUST)
                       .get<drawing::TextVerticalAdjust>();
    }
    xPropertySet->setPropertyValue(UNO_NAME_TEXT_VERT_ADJUST, uno::Any(aVertAdj));
    text::WritingMode eMode;
    if (xShapePropertySet->getPropertyValue(UNO_NAME_TEXT_WRITINGMODE) >>= eMode)
        syncProperty(pShapeFormat, RES_FRAMEDIR, 0, uno::Any(sal_Int16(eMode)), pObj);

    // Do sync for the new textframe.
    synchronizeGroupTextBoxProperty(&changeAnchor, pShapeFormat, pObj);
    synchronizeGroupTextBoxProperty(&syncTextBoxSize, pShapeFormat, pObj);

    updateTextBoxMargin(pObj);
}

void SwTextBoxHelper::destroy(const SwFrameFormat* pShape, const SdrObject* pObject)
{
    // If a TextBox was enabled previously
    auto& pTextBox = pShape->GetOtherTextBoxFormats();
    if (pTextBox)
    {
        // Unlink the TextBox's text range from the original shape.
        // Delete the associated TextFrame.
        pTextBox->DelTextBox(pObject, true);
    }
}

bool SwTextBoxHelper::isTextBox(const SwFrameFormat* pFormat, sal_uInt16 nType,
                                const SdrObject* pObject)
{
    DBG_TESTSOLARMUTEX();
    assert(nType == RES_FLYFRMFMT || nType == RES_DRAWFRMFMT);
    if (!pFormat || pFormat->Which() != nType)
        return false;

    auto& pTextBox = pFormat->GetOtherTextBoxFormats();
    if (!pTextBox)
        return false;

    if (nType == RES_DRAWFRMFMT)
    {
        if (pObject)
            return pTextBox->GetTextBox(pObject);
        if (auto pObj = pFormat->FindRealSdrObject())
            return pTextBox->GetTextBox(pObj);
    }

    if (nType == RES_FLYFRMFMT)
    {
        return pTextBox->GetOwnerShape();
    }

    return false;
}

bool SwTextBoxHelper::hasTextFrame(const SdrObject* pObj)
{
    if (!pObj)
        return false;

    uno::Reference<drawing::XShape> xShape(pObj->getWeakUnoShape().get(), uno::UNO_QUERY);
    if (!xShape)
        return false;
    return SwTextBoxHelper::getOtherTextBoxFormat(xShape);
}

sal_Int32 SwTextBoxHelper::getCount(SdrPage const* pPage)
{
    sal_Int32 nRet = 0;
    for (const rtl::Reference<SdrObject>& p : *pPage)
    {
        assert(p);
        if (p->IsTextBox())
            continue;
        ++nRet;
    }
    return nRet;
}

sal_Int32 SwTextBoxHelper::getCount(const SwDoc& rDoc)
{
    sal_Int32 nRet = 0;
    for (const sw::SpzFrameFormat* pFormat : *rDoc.GetSpzFrameFormats())
    {
        if (isTextBox(pFormat, RES_FLYFRMFMT))
            ++nRet;
    }
    return nRet;
}

uno::Any SwTextBoxHelper::getByIndex(SdrPage const* pPage, sal_Int32 nIndex)
{
    if (nIndex < 0)
        throw lang::IndexOutOfBoundsException();

    sal_Int32 nCount = 0; // Current logical index.
    for (const rtl::Reference<SdrObject>& p : *pPage)
    {
        assert(p);
        if (p->IsTextBox())
            continue;
        if (nCount == nIndex)
            return uno::Any(p->getUnoShape());
        ++nCount;
    }

    throw lang::IndexOutOfBoundsException();
}

sal_Int32 SwTextBoxHelper::getOrdNum(const SdrObject* pObject)
{
    if (const SdrPage* pPage = pObject->getSdrPageFromSdrObject())
    {
        sal_Int32 nOrder = 0; // Current logical order.
        for (const rtl::Reference<SdrObject>& p : *pPage)
        {
            assert(p);
            if (p->IsTextBox())
                continue;
            if (p == pObject)
                return nOrder;
            ++nOrder;
        }
    }

    SAL_WARN("sw.core", "SwTextBoxHelper::getOrdNum: no page or page doesn't contain the object");
    return pObject->GetOrdNum();
}

void SwTextBoxHelper::getShapeWrapThrough(const SwFrameFormat* pTextBox, bool& rWrapThrough)
{
    SwFrameFormat* pShape = SwTextBoxHelper::getOtherTextBoxFormat(pTextBox, RES_FLYFRMFMT);
    if (pShape)
        rWrapThrough = pShape->GetSurround().GetSurround() == css::text::WrapTextMode_THROUGH;
}

SwFrameFormat* SwTextBoxHelper::getOtherTextBoxFormat(const SwFrameFormat* pFormat,
                                                      sal_uInt16 nType, const SdrObject* pObject)
{
    SolarMutexGuard aGuard;
    if (!isTextBox(pFormat, nType, pObject))
        return nullptr;

    if (nType == RES_DRAWFRMFMT)
    {
        if (pObject)
            return pFormat->GetOtherTextBoxFormats()->GetTextBox(pObject);
        if (pFormat->FindRealSdrObject())
            return pFormat->GetOtherTextBoxFormats()->GetTextBox(pFormat->FindRealSdrObject());
        return nullptr;
    }
    if (nType == RES_FLYFRMFMT)
    {
        return pFormat->GetOtherTextBoxFormats()->GetOwnerShape();
    }
    return nullptr;
}

SwFrameFormat* SwTextBoxHelper::getOtherTextBoxFormat(uno::Reference<drawing::XShape> const& xShape)
{
    auto pShape = dynamic_cast<SwXShape*>(xShape.get());
    if (!pShape)
        return nullptr;

    SwFrameFormat* pFormat = pShape->GetFrameFormat();
    return getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT,
                                 SdrObject::getSdrObjectFromXShape(xShape));
}

uno::Reference<text::XTextFrame>
SwTextBoxHelper::getUnoTextFrame(uno::Reference<drawing::XShape> const& xShape)
{
    if (xShape)
    {
        auto pFrameFormat = SwTextBoxHelper::getOtherTextBoxFormat(xShape);
        if (pFrameFormat)
        {
            auto pSdrObj = pFrameFormat->FindSdrObject();
            if (pSdrObj)
            {
                return { pSdrObj->getUnoShape(), uno::UNO_QUERY };
            }
        }
    }
    return {};
}

template <typename T>
static void lcl_queryInterface(const SwFrameFormat* pShape, uno::Any& rAny, SdrObject* pObj)
{
    if (SwFrameFormat* pFormat
        = SwTextBoxHelper::getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj))
    {
        uno::Reference<T> const xInterface(
            getXWeak(SwXTextFrame::CreateXTextFrame(pFormat->GetDoc(), pFormat).get()),
            uno::UNO_QUERY);
        rAny <<= xInterface;
    }
}

uno::Any SwTextBoxHelper::queryInterface(const SwFrameFormat* pShape, const uno::Type& rType,
                                         SdrObject* pObj)
{
    uno::Any aRet;

    if (rType == cppu::UnoType<css::text::XTextAppend>::get())
    {
        lcl_queryInterface<text::XTextAppend>(pShape, aRet, pObj);
    }
    else if (rType == cppu::UnoType<css::text::XText>::get())
    {
        lcl_queryInterface<text::XText>(pShape, aRet, pObj);
    }
    else if (rType == cppu::UnoType<css::text::XTextRange>::get())
    {
        lcl_queryInterface<text::XTextRange>(pShape, aRet, pObj);
    }

    return aRet;
}

tools::Rectangle SwTextBoxHelper::getRelativeTextRectangle(SdrObject* pShape)
{
    tools::Rectangle aRet;
    aRet.SetEmpty();

    assert(pShape);

    auto pCustomShape = dynamic_cast<SdrObjCustomShape*>(pShape);
    if (pCustomShape)
    {
        // Need to temporarily release the lock acquired in
        // SdXMLShapeContext::AddShape(), otherwise we get an empty rectangle,
        // see EnhancedCustomShapeEngine::getTextBounds().
        uno::Reference<document::XActionLockable> xLockable(pCustomShape->getUnoShape(),
                                                            uno::UNO_QUERY);
        sal_Int16 nLocks = 0;
        if (xLockable.is())
            nLocks = xLockable->resetActionLocks();
        pCustomShape->GetTextBounds(aRet);
        if (nLocks)
            xLockable->setActionLocks(nLocks);
    }
    else if (pShape)
    {
        // fallback - get *any* bound rect we can possibly get hold of
        aRet = pShape->GetCurrentBoundRect();
    }

    if (pShape)
    {
        // Relative, so count the logic (reference) rectangle, see the EnhancedCustomShape2d ctor.
        Point aPoint(pShape->GetSnapRect().Center());
        Size aSize(pShape->GetLogicRect().GetSize());
        aPoint.AdjustX(-(aSize.Width() / 2));
        aPoint.AdjustY(-(aSize.Height() / 2));
        tools::Rectangle aLogicRect(aPoint, aSize);
        aRet.Move(-1 * aLogicRect.Left(), -1 * aLogicRect.Top());
    }

    return aRet;
}

void SwTextBoxHelper::syncProperty(SwFrameFormat* pShape, std::u16string_view rPropertyName,
                                   const css::uno::Any& rValue, SdrObject* pObj)
{
    // Textframes does not have valid horizontal adjust property, so map it to paragraph adjust property
    if (rPropertyName == UNO_NAME_TEXT_HORZADJUST)
    {
        SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj);
        if (!pFormat)
            return;

        auto xTextFrame = SwXTextFrame::CreateXTextFrame(pFormat->GetDoc(), pFormat);
        uno::Reference<text::XTextCursor> xCursor = xTextFrame->getText()->createTextCursor();

        // Select all paragraphs in the textframe
        xCursor->gotoStart(false);
        xCursor->gotoEnd(true);
        uno::Reference<beans::XPropertySet> xFrameParaProps(xCursor, uno::UNO_QUERY);

        // And simply map the property
        const auto eValue = rValue.get<drawing::TextHorizontalAdjust>();
        switch (eValue)
        {
            case drawing::TextHorizontalAdjust::TextHorizontalAdjust_CENTER:
                xFrameParaProps->setPropertyValue(
                    UNO_NAME_PARA_ADJUST,
                    uno::Any(style::ParagraphAdjust::ParagraphAdjust_CENTER)); //3
                break;
            case drawing::TextHorizontalAdjust::TextHorizontalAdjust_LEFT:
                xFrameParaProps->setPropertyValue(
                    UNO_NAME_PARA_ADJUST,
                    uno::Any(style::ParagraphAdjust::ParagraphAdjust_LEFT)); //0
                break;
            case drawing::TextHorizontalAdjust::TextHorizontalAdjust_RIGHT:
                xFrameParaProps->setPropertyValue(
                    UNO_NAME_PARA_ADJUST,
                    uno::Any(style::ParagraphAdjust::ParagraphAdjust_RIGHT)); //1
                break;
            default:
                SAL_WARN("sw.core",
                         "SwTextBoxHelper::syncProperty: unhandled TextHorizontalAdjust: "
                             << static_cast<sal_Int32>(eValue));
                break;
        }
        return;
    }

    if (rPropertyName == u"CustomShapeGeometry")
    {
        // CustomShapeGeometry changes the textbox position offset and size, so adjust both.
        syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_SIZE, uno::Any());

        SdrObject* pObject = pObj ? pObj : pShape->FindRealSdrObject();
        if (pObject)
        {
            tools::Rectangle aRectangle(pObject->GetSnapRect());
            syncProperty(pShape, RES_HORI_ORIENT, MID_HORIORIENT_POSITION,
                         uno::Any(static_cast<sal_Int32>(convertTwipToMm100(aRectangle.Left()))));
            syncProperty(pShape, RES_VERT_ORIENT, MID_VERTORIENT_POSITION,
                         uno::Any(static_cast<sal_Int32>(convertTwipToMm100(aRectangle.Top()))));
        }

        SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj);
        if (!pFormat)
            return;

        // Older documents or documents in ODF strict do not have WritingMode, but have used the
        // TextRotateAngle values -90 and -270 to emulate these text directions of frames.
        // ToDo: Is TextPreRotateAngle needed for diagrams or can it be removed?
        comphelper::SequenceAsHashMap aCustomShapeGeometry(rValue);
        auto it = aCustomShapeGeometry.find(u"TextPreRotateAngle"_ustr);
        if (it == aCustomShapeGeometry.end())
        {
            it = aCustomShapeGeometry.find(u"TextRotateAngle"_ustr);
        }

        if (it != aCustomShapeGeometry.end())
        {
            auto nAngle = it->second.has<sal_Int32>() ? it->second.get<sal_Int32>() : 0;
            if (nAngle == 0)
            {
                nAngle = it->second.has<double>() ? it->second.get<double>() : 0;
            }

            sal_Int16 nDirection = 0;
            switch (nAngle)
            {
                case -90:
                    nDirection = text::WritingMode2::TB_RL90;
                    break;
                case -270:
                    nDirection = text::WritingMode2::BT_LR;
                    break;
                default:
                    SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled property value: "
                                        "CustomShapeGeometry:TextPreRotateAngle: "
                                            << nAngle);
                    break;
            }

            if (nDirection)
            {
                syncProperty(pShape, RES_FRAMEDIR, 0, uno::Any(nDirection), pObj);
            }
        }
    }
    else if (rPropertyName == UNO_NAME_TEXT_VERT_ADJUST)
        syncProperty(pShape, RES_TEXT_VERT_ADJUST, 0, rValue, pObj);
    else if (rPropertyName == UNO_NAME_TEXT_AUTOGROWHEIGHT)
        syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_IS_AUTO_HEIGHT, rValue, pObj);
    else if (rPropertyName == UNO_NAME_TEXT_LEFTDIST)
        syncProperty(pShape, RES_BOX, LEFT_BORDER_DISTANCE, rValue, pObj);
    else if (rPropertyName == UNO_NAME_TEXT_RIGHTDIST)
        syncProperty(pShape, RES_BOX, RIGHT_BORDER_DISTANCE, rValue, pObj);
    else if (rPropertyName == UNO_NAME_TEXT_UPPERDIST)
        syncProperty(pShape, RES_BOX, TOP_BORDER_DISTANCE, rValue, pObj);
    else if (rPropertyName == UNO_NAME_TEXT_LOWERDIST)
        syncProperty(pShape, RES_BOX, BOTTOM_BORDER_DISTANCE, rValue, pObj);
    else if (rPropertyName == UNO_NAME_TEXT_WRITINGMODE)
    {
        text::WritingMode eMode;
        sal_Int16 eMode2;
        if (rValue >>= eMode)
            syncProperty(pShape, RES_FRAMEDIR, 0, uno::Any(sal_Int16(eMode)), pObj);
        else if (rValue >>= eMode2)
            syncProperty(pShape, RES_FRAMEDIR, 0, uno::Any(eMode2), pObj);
    }
    else if (rPropertyName == u"WritingMode")
    {
        sal_Int16 eMode2;
        if (rValue >>= eMode2)
            syncProperty(pShape, RES_FRAMEDIR, 0, uno::Any(eMode2), pObj);
    }
    else if (rPropertyName == u"TextWordWrap")
    {
        // tdf#81567 shape word wrap to frame width type on shape update.
        bool bTextWordwrap{};
        if (rValue >>= bTextWordwrap)
        {
            syncProperty(pShape, RES_FRM_SIZE, MID_FRMSIZE_WIDTH_TYPE,
                         uno::Any(bTextWordwrap ? text::SizeType::FIX : text::SizeType::MIN), pObj);
        }
    }
    else
        SAL_INFO("sw.core", "SwTextBoxHelper::syncProperty: unhandled property: "
                                << static_cast<OUString>(rPropertyName));
}

void SwTextBoxHelper::getProperty(SwFrameFormat const* pShape, sal_uInt16 nWID, sal_uInt8 nMemberID,
                                  css::uno::Any& rValue)
{
    if (!pShape)
        return;

    nMemberID &= ~CONVERT_TWIPS;

    SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT);
    if (!pFormat)
        return;

    if (nWID != RES_CHAIN)
        return;

    switch (nMemberID)
    {
        case MID_CHAIN_PREVNAME:
        case MID_CHAIN_NEXTNAME:
        {
            const SwFormatChain& rChain = pFormat->GetChain();
            rChain.QueryValue(rValue, nMemberID);
        }
        break;
        case MID_CHAIN_NAME:
            rValue <<= pFormat->GetName().toString();
            break;
        default:
            SAL_WARN("sw.core", "SwTextBoxHelper::getProperty: unhandled member-id: "
                                    << o3tl::narrowing<sal_uInt16>(nMemberID));
            break;
    }
}

css::uno::Any SwTextBoxHelper::getProperty(SwFrameFormat const* pShape, const OUString& rPropName)
{
    if (!pShape)
        return {};

    SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT);
    if (!pFormat)
        return {};

    rtl::Reference<SwXTextFrame> xPropertySet
        = SwXTextFrame::CreateXTextFrame(pFormat->GetDoc(), pFormat);

    return xPropertySet->getPropertyValue(rPropName);
}

void SwTextBoxHelper::syncProperty(SwFrameFormat* pShape, sal_uInt16 nWID, sal_uInt8 nMemberID,
                                   const css::uno::Any& rValue, SdrObject* pObj)
{
    // No shape yet? Then nothing to do, initial properties are set by create().
    if (!pShape)
        return;

    uno::Any aValue(rValue);
    nMemberID &= ~CONVERT_TWIPS;

    SwFrameFormat* pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj);
    if (!pFormat)
        return;

    OUString aPropertyName;
    bool bAdjustX = false;
    bool bAdjustY = false;
    bool bAdjustSize = false;
    switch (nWID)
    {
        case RES_HORI_ORIENT:
            switch (nMemberID)
            {
                case MID_HORIORIENT_ORIENT:
                    aPropertyName = UNO_NAME_HORI_ORIENT;
                    break;
                case MID_HORIORIENT_RELATION:
                    if (pShape->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR)
                        aPropertyName = UNO_NAME_HORI_ORIENT_RELATION;
                    else
                        return;
                    break;
                case MID_HORIORIENT_POSITION:
                    aPropertyName = UNO_NAME_HORI_ORIENT_POSITION;
                    bAdjustX = true;
                    break;
                default:
                    SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled member-id: "
                                            << o3tl::narrowing<sal_uInt16>(nMemberID)
                                            << " (which-id: " << nWID << ")");
                    break;
            }
            break;
        case RES_LR_SPACE:
        {
            switch (nMemberID)
            {
                case MID_L_MARGIN:
                    aPropertyName = UNO_NAME_LEFT_MARGIN;
                    break;
                case MID_R_MARGIN:
                    aPropertyName = UNO_NAME_RIGHT_MARGIN;
                    break;
                default:
                    SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled member-id: "
                                            << o3tl::narrowing<sal_uInt16>(nMemberID)
                                            << " (which-id: " << nWID << ")");
                    break;
            }
            break;
        }
        case RES_VERT_ORIENT:
            switch (nMemberID)
            {
                case MID_VERTORIENT_ORIENT:
                    aPropertyName = UNO_NAME_VERT_ORIENT;
                    break;
                case MID_VERTORIENT_RELATION:
                    if (pShape->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR)
                        aPropertyName = UNO_NAME_VERT_ORIENT_RELATION;
                    else
                        return;
                    break;
                case MID_VERTORIENT_POSITION:
                    aPropertyName = UNO_NAME_VERT_ORIENT_POSITION;
                    bAdjustY = true;
                    break;
                default:
                    SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled member-id: "
                                            << o3tl::narrowing<sal_uInt16>(nMemberID)
                                            << " (which-id: " << nWID << ")");
                    break;
            }
            break;
        case RES_FRM_SIZE:
            switch (nMemberID)
            {
                case MID_FRMSIZE_WIDTH_TYPE:
                    aPropertyName = UNO_NAME_WIDTH_TYPE;
                    break;
                case MID_FRMSIZE_IS_AUTO_HEIGHT:
                    aPropertyName = UNO_NAME_FRAME_ISAUTOMATIC_HEIGHT;
                    break;
                case MID_FRMSIZE_REL_HEIGHT_RELATION:
                    aPropertyName = UNO_NAME_RELATIVE_HEIGHT_RELATION;
                    break;
                case MID_FRMSIZE_REL_WIDTH_RELATION:
                    aPropertyName = UNO_NAME_RELATIVE_WIDTH_RELATION;
                    break;
                default:
                    aPropertyName = UNO_NAME_SIZE;
                    bAdjustSize = true;
                    break;
            }
            break;
        case RES_ANCHOR:
            switch (nMemberID)
            {
                case MID_ANCHOR_ANCHORTYPE:
                {
                    changeAnchor(pShape, pObj);
                    return;
                }
                break;
                default:
                    SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled member-id: "
                                            << o3tl::narrowing<sal_uInt16>(nMemberID)
                                            << " (which-id: " << nWID << ")");
                    break;
            }
            break;
        case FN_TEXT_RANGE:
        {
            uno::Reference<text::XTextRange> xRange;
            rValue >>= xRange;
            SwUnoInternalPaM aInternalPaM(pFormat->GetDoc());
            if (sw::XTextRangeToSwPaM(aInternalPaM, xRange))
            {
                SwFormatAnchor aAnchor(pFormat->GetAnchor());
                aAnchor.SetAnchor(aInternalPaM.Start());
                pFormat->SetFormatAttr(aAnchor);
            }
        }
        break;
        case RES_CHAIN:
            switch (nMemberID)
            {
                case MID_CHAIN_PREVNAME:
                    aPropertyName = UNO_NAME_CHAIN_PREV_NAME;
                    break;
                case MID_CHAIN_NEXTNAME:
                    aPropertyName = UNO_NAME_CHAIN_NEXT_NAME;
                    break;
                default:
                    SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled member-id: "
                                            << o3tl::narrowing<sal_uInt16>(nMemberID)
                                            << " (which-id: " << nWID << ")");
                    break;
            }
            break;
        case RES_TEXT_VERT_ADJUST:
            aPropertyName = UNO_NAME_TEXT_VERT_ADJUST;
            break;
        case RES_BOX:
            switch (nMemberID)
            {
                case LEFT_BORDER_DISTANCE:
                    aPropertyName = UNO_NAME_LEFT_BORDER_DISTANCE;
                    break;
                case RIGHT_BORDER_DISTANCE:
                    aPropertyName = UNO_NAME_RIGHT_BORDER_DISTANCE;
                    break;
                case TOP_BORDER_DISTANCE:
                    aPropertyName = UNO_NAME_TOP_BORDER_DISTANCE;
                    break;
                case BOTTOM_BORDER_DISTANCE:
                    aPropertyName = UNO_NAME_BOTTOM_BORDER_DISTANCE;
                    break;
                default:
                    SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled member-id: "
                                            << o3tl::narrowing<sal_uInt16>(nMemberID)
                                            << " (which-id: " << nWID << ")");
                    break;
            }
            break;
        case RES_OPAQUE:
            aPropertyName = UNO_NAME_OPAQUE;
            break;
        case RES_FRAMEDIR:
            aPropertyName = UNO_NAME_WRITING_MODE;
            break;
        case RES_WRAP_INFLUENCE_ON_OBJPOS:
            switch (nMemberID)
            {
                case MID_ALLOW_OVERLAP:
                    aPropertyName = UNO_NAME_ALLOW_OVERLAP;
                    break;
                default:
                    SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled member-id: "
                                            << o3tl::narrowing<sal_uInt16>(nMemberID)
                                            << " (which-id: " << nWID << ")");
                    break;
            }
            break;
        default:
            SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: unhandled which-id: "
                                    << nWID << " (member-id: "
                                    << o3tl::narrowing<sal_uInt16>(nMemberID) << ")");
            break;
    }

    if (aPropertyName.isEmpty())
        return;

    // Position/size should be the text position/size, not the shape one as-is.
    if (bAdjustX || bAdjustY || bAdjustSize)
    {
        changeAnchor(pShape, pObj);
        tools::Rectangle aRect
            = getRelativeTextRectangle(pObj ? pObj : pShape->FindRealSdrObject());
        if (!aRect.IsEmpty())
        {
            if (bAdjustX || bAdjustY)
            {
                sal_Int32 nValue;
                if (aValue >>= nValue)
                {
                    nValue += convertTwipToMm100(bAdjustX ? aRect.Left() : aRect.Top());
                    aValue <<= nValue;
                }
            }
            else if (bAdjustSize)
            {
                awt::Size aSize(convertTwipToMm100(aRect.getOpenWidth()),
                                convertTwipToMm100(aRect.getOpenHeight()));
                aValue <<= aSize;
            }
        }
    }
    auto aGuard = SwTextBoxLockGuard(*pShape->GetOtherTextBoxFormats());
    rtl::Reference<SwXTextFrame> const xPropertySet
        = SwXTextFrame::CreateXTextFrame(pFormat->GetDoc(), pFormat);
    xPropertySet->setPropertyValue(aPropertyName, aValue);
}

void SwTextBoxHelper::saveLinks(const sw::FrameFormats<sw::SpzFrameFormat*>& rFormats,
                                std::map<const SwFrameFormat*, const SwFrameFormat*>& rLinks)
{
    for (const auto pFormat : rFormats)
    {
        if (SwFrameFormat* pTextBox = getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT))
            rLinks[pFormat] = pTextBox;
    }
}

void SwTextBoxHelper::restoreLinks(std::set<ZSortFly>& rOld, std::vector<SwFrameFormat*>& rNew,
                                   SavedLink& rSavedLinks)
{
    std::size_t i = 0;
    for (const auto& rIt : rOld)
    {
        auto aTextBoxIt = rSavedLinks.find(rIt.GetFormat());
        if (aTextBoxIt != rSavedLinks.end())
        {
            std::size_t j = 0;
            for (const auto& rJt : rOld)
            {
                if (rJt.GetFormat() == aTextBoxIt->second)
                    rNew[i]->SetFormatAttr(rNew[j]->GetContent());
                ++j;
            }
        }
        ++i;
    }
}

text::TextContentAnchorType SwTextBoxHelper::mapAnchorType(const RndStdIds& rAnchorID)
{
    text::TextContentAnchorType aAnchorType;
    switch (rAnchorID)
    {
        case RndStdIds::FLY_AS_CHAR:
            aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AS_CHARACTER;
            break;
        case RndStdIds::FLY_AT_CHAR:
            aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_CHARACTER;
            break;
        case RndStdIds::FLY_AT_PARA:
            aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_PARAGRAPH;
            break;
        case RndStdIds::FLY_AT_PAGE:
            aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_PAGE;
            break;
        case RndStdIds::FLY_AT_FLY:
            aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_FRAME;
            break;
        default:
            aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_PARAGRAPH;
            SAL_WARN("sw.core", "SwTextBoxHelper::mapAnchorType: Unknown AnchorType!");
            break;
    }
    return aAnchorType;
}

void SwTextBoxHelper::syncFlyFrameAttr(SwFrameFormat& rShape, SfxItemSet const& rSet,
                                       SdrObject* pObj)
{
    SwFrameFormat* pFormat = getOtherTextBoxFormat(&rShape, RES_DRAWFRMFMT, pObj);
    if (!pFormat)
        return;

    const bool bInlineAnchored = rShape.GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR;
    const bool bLayoutInCell = rShape.GetFollowTextFlow().GetValue()
                               && rShape.GetAnchor().GetAnchorNode()
                               && rShape.GetAnchor().GetAnchorNode()->FindTableNode();
    SfxItemSet aTextBoxSet(pFormat->GetDoc().GetAttrPool(), aFrameFormatSetRange);

    SfxItemIter aIter(rSet);
    const SfxPoolItem* pItem = aIter.GetCurItem();

    do
    {
        switch (pItem->Which())
        {
            case RES_VERT_ORIENT:
            {
                // The new position can be with anchor changing so sync it!
                const text::TextContentAnchorType aNewAnchorType
                    = mapAnchorType(rShape.GetAnchor().GetAnchorId());
                syncProperty(&rShape, RES_ANCHOR, MID_ANCHOR_ANCHORTYPE, uno::Any(aNewAnchorType),
                             pObj);
                if (bInlineAnchored || bLayoutInCell)
                    return;
                SwFormatVertOrient aOrient(pItem->StaticWhichCast(RES_VERT_ORIENT));

                tools::Rectangle aRect
                    = getRelativeTextRectangle(pObj ? pObj : rShape.FindRealSdrObject());
                if (!aRect.IsEmpty())
                    aOrient.SetPos(aOrient.GetPos() + aRect.Top());

                if (rShape.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE
                    && rShape.GetAnchor().GetPageNum() != 0)
                    aOrient.SetRelationOrient(rShape.GetVertOrient().GetRelationOrient());
                aTextBoxSet.Put(aOrient);

                // restore height (shrunk for extending beyond the page bottom - tdf#91260)
                SwFormatFrameSize aSize(pFormat->GetFrameSize());
                if (!aRect.IsEmpty())
                {
                    aSize.SetHeight(aRect.getOpenHeight());
                    aTextBoxSet.Put(aSize);
                }
            }
            break;
            case RES_HORI_ORIENT:
            {
                // The new position can be with anchor changing so sync it!
                const text::TextContentAnchorType aNewAnchorType
                    = mapAnchorType(rShape.GetAnchor().GetAnchorId());
                syncProperty(&rShape, RES_ANCHOR, MID_ANCHOR_ANCHORTYPE, uno::Any(aNewAnchorType),
                             pObj);
                if (bInlineAnchored || bLayoutInCell)
                    return;
                SwFormatHoriOrient aOrient(pItem->StaticWhichCast(RES_HORI_ORIENT));

                tools::Rectangle aRect
                    = getRelativeTextRectangle(pObj ? pObj : rShape.FindRealSdrObject());
                if (!aRect.IsEmpty())
                    aOrient.SetPos(aOrient.GetPos() + aRect.Left());

                if (rShape.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE
                    && rShape.GetAnchor().GetPageNum() != 0)
                    aOrient.SetRelationOrient(rShape.GetHoriOrient().GetRelationOrient());
                aTextBoxSet.Put(aOrient);
            }
            break;
            case RES_FRM_SIZE:
            {
                // In case the shape got resized, then we need to adjust both
                // the position and the size of the textbox (e.g. larger
                // rounded edges of a rectangle -> need to push right/down the
                // textbox).
                SwFormatVertOrient aVertOrient(rShape.GetVertOrient());
                SwFormatHoriOrient aHoriOrient(rShape.GetHoriOrient());
                SwFormatFrameSize aSize(pFormat->GetFrameSize());

                tools::Rectangle aRect
                    = getRelativeTextRectangle(pObj ? pObj : rShape.FindRealSdrObject());
                if (!aRect.IsEmpty())
                {
                    if (!bInlineAnchored)
                    {
                        aVertOrient.SetPos(
                            (pObj ? pObj->GetRelativePos().getX() : aVertOrient.GetPos())
                            + aRect.Top());
                        aHoriOrient.SetPos(
                            (pObj ? pObj->GetRelativePos().getY() : aHoriOrient.GetPos())
                            + aRect.Left());

                        aTextBoxSet.Put(aVertOrient);
                        aTextBoxSet.Put(aHoriOrient);
                    }

                    aSize.SetWidth(aRect.getOpenWidth());
                    aSize.SetHeight(aRect.getOpenHeight());
                    aTextBoxSet.Put(aSize);
                }
            }
            break;
            case RES_ANCHOR:
            {
                if (pItem->StaticWhichCast(RES_ANCHOR) == rShape.GetAnchor())
                // the anchor have to be synced
                {
                    const text::TextContentAnchorType aNewAnchorType
                        = mapAnchorType(rShape.GetAnchor().GetAnchorId());
                    syncProperty(&rShape, RES_ANCHOR, MID_ANCHOR_ANCHORTYPE,
                                 uno::Any(aNewAnchorType), pObj);
                }
                else
                {
                    SAL_WARN("sw.core", "SwTextBoxHelper::syncFlyFrameAttr: The anchor of the "
                                        "shape different from the textframe!");
                }
            }
            break;
            default:
                SAL_WARN("sw.core", "SwTextBoxHelper::syncFlyFrameAttr: unhandled which-id: "
                                        << pItem->Which());
                break;
        }

        pItem = aIter.NextItem();
    } while (pItem && (0 != pItem->Which()));

    if (aTextBoxSet.Count())
    {
        auto aGuard = SwTextBoxLockGuard(*rShape.GetOtherTextBoxFormats());
        pFormat->SetFormatAttr(aTextBoxSet);
    }
    DoTextBoxZOrderCorrection(&rShape, pObj);
}

void SwTextBoxHelper::updateTextBoxMargin(SdrObject* pObj)
{
    if (!pObj)
        return;
    uno::Reference<drawing::XShape> xShape(pObj->getUnoShape(), uno::UNO_QUERY);
    if (!xShape)
        return;
    uno::Reference<beans::XPropertySet> const xPropertySet(xShape, uno::UNO_QUERY);

    auto pParentFormat = getOtherTextBoxFormat(getOtherTextBoxFormat(xShape), RES_FLYFRMFMT);
    if (!pParentFormat)
        return;

    // Sync the padding
    syncProperty(pParentFormat, UNO_NAME_TEXT_LEFTDIST,
                 xPropertySet->getPropertyValue(UNO_NAME_TEXT_LEFTDIST), pObj);
    syncProperty(pParentFormat, UNO_NAME_TEXT_RIGHTDIST,
                 xPropertySet->getPropertyValue(UNO_NAME_TEXT_RIGHTDIST), pObj);
    syncProperty(pParentFormat, UNO_NAME_TEXT_UPPERDIST,
                 xPropertySet->getPropertyValue(UNO_NAME_TEXT_UPPERDIST), pObj);
    syncProperty(pParentFormat, UNO_NAME_TEXT_LOWERDIST,
                 xPropertySet->getPropertyValue(UNO_NAME_TEXT_LOWERDIST), pObj);

    // Sync the text aligning
    syncProperty(pParentFormat, UNO_NAME_TEXT_VERTADJUST,
                 xPropertySet->getPropertyValue(UNO_NAME_TEXT_VERTADJUST), pObj);
    syncProperty(pParentFormat, UNO_NAME_TEXT_HORZADJUST,
                 xPropertySet->getPropertyValue(UNO_NAME_TEXT_HORZADJUST), pObj);

    // tdf137803: Sync autogrow:
    const bool bIsAutoGrow
        = xPropertySet->getPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT).get<bool>();
    const bool bIsAutoWrap = xPropertySet->getPropertyValue(UNO_NAME_TEXT_WORDWRAP).get<bool>();

    syncProperty(pParentFormat, RES_FRM_SIZE, MID_FRMSIZE_IS_AUTO_HEIGHT, uno::Any(bIsAutoGrow),
                 pObj);

    syncProperty(pParentFormat, RES_FRM_SIZE, MID_FRMSIZE_WIDTH_TYPE,
                 uno::Any(bIsAutoWrap ? text::SizeType::FIX : text::SizeType::MIN), pObj);

    changeAnchor(pParentFormat, pObj);
    DoTextBoxZOrderCorrection(pParentFormat, pObj);
}

bool SwTextBoxHelper::changeAnchor(SwFrameFormat* pShape, SdrObject* pObj)
{
    if (auto pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj))
    {
        if (!isAnchorSyncNeeded(pShape, pFormat))
        {
            doTextBoxPositioning(pShape, pObj);
            DoTextBoxZOrderCorrection(pShape, pObj);
            if (pShape->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR
                && pFormat->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_CHAR
                && pFormat->GetVertOrient().GetRelationOrient() != text::RelOrientation::PRINT_AREA)
            {
                SwFormatVertOrient aTmp = pFormat->GetVertOrient();
                aTmp.SetRelationOrient(text::RelOrientation::PRINT_AREA);
                pFormat->SetFormatAttr(aTmp);
            }

            return false;
        }

        const SwFormatAnchor& rOldAnch = pFormat->GetAnchor();
        const SwFormatAnchor& rNewAnch = pShape->GetAnchor();

        const auto pOldCnt = rOldAnch.GetContentAnchor();
        const auto pNewCnt = rNewAnch.GetContentAnchor();

        const uno::Any aShapeHorRelOrient(pShape->GetHoriOrient().GetRelationOrient());

        try
        {
            auto aGuard = SwTextBoxLockGuard(*pShape->GetOtherTextBoxFormats());
            ::sw::UndoGuard const UndoGuard(pShape->GetDoc().GetIDocumentUndoRedo());
            rtl::Reference<SwXTextFrame> const xPropertySet
                = SwXTextFrame::CreateXTextFrame(pFormat->GetDoc(), pFormat);
            if (pOldCnt && rNewAnch.GetAnchorId() == RndStdIds::FLY_AT_PAGE
                && rNewAnch.GetPageNum())
            {
                uno::Any aValue(text::TextContentAnchorType_AT_PAGE);
                xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, aShapeHorRelOrient);
                xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, aValue);
                xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_PAGE_NO,
                                               uno::Any(rNewAnch.GetPageNum()));
            }
            else if (rOldAnch.GetAnchorId() == RndStdIds::FLY_AT_PAGE && pNewCnt)
            {
                if (rNewAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR)
                {
                    assert(pNewCnt);
                    uno::Any aValue(text::TextContentAnchorType_AT_CHARACTER);
                    xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, aValue);
                    xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION,
                                                   uno::Any(text::RelOrientation::CHAR));
                    xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT_RELATION,
                                                   uno::Any(text::RelOrientation::PRINT_AREA));
                    SwFormatAnchor aPos(pFormat->GetAnchor());
                    aPos.SetAnchor(pNewCnt);
                    pFormat->SetFormatAttr(aPos);
                }
                else
                {
                    uno::Any aValue(mapAnchorType(rNewAnch.GetAnchorId()));
                    xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION,
                                                   aShapeHorRelOrient);
                    xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, aValue);
                    pFormat->SetFormatAttr(rNewAnch);
                }
            }
            else
            {
                if (rNewAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR)
                {
                    assert(pNewCnt);
                    uno::Any aValue(text::TextContentAnchorType_AT_CHARACTER);
                    xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, aValue);
                    xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION,
                                                   uno::Any(text::RelOrientation::CHAR));
                    xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT_RELATION,
                                                   uno::Any(text::RelOrientation::PRINT_AREA));
                    SwFormatAnchor aPos(pFormat->GetAnchor());
                    aPos.SetAnchor(pNewCnt);
                    pFormat->SetFormatAttr(aPos);
                }
                else
                {
                    xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION,
                                                   aShapeHorRelOrient);
                    if (rNewAnch.GetAnchorId() == RndStdIds::FLY_AT_PAGE
                        && rNewAnch.GetPageNum() == 0)
                    {
                        pFormat->SetFormatAttr(SwFormatAnchor(RndStdIds::FLY_AT_PAGE, 1));
                    }
                    else
                        pFormat->SetFormatAttr(pShape->GetAnchor());
                }
            }
        }
        catch (uno::Exception& e)
        {
            SAL_WARN("sw.core", "SwTextBoxHelper::changeAnchor(): " << e.Message);
        }

        doTextBoxPositioning(pShape, pObj);
        DoTextBoxZOrderCorrection(pShape, pObj);
        return true;
    }

    return false;
}

bool SwTextBoxHelper::doTextBoxPositioning(SwFrameFormat* pShape, SdrObject* pObj)
{
    // Set the position of the textboxes according to the position of its shape-pair
    const bool bIsGroupObj = (pObj != pShape->FindRealSdrObject()) && pObj;
    if (auto pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj))
    {
        // Do not create undo entry for the positioning
        ::sw::UndoGuard const UndoGuard(pShape->GetDoc().GetIDocumentUndoRedo());
        auto aGuard = SwTextBoxLockGuard(*pShape->GetOtherTextBoxFormats());
        // Special treatment for AS_CHAR textboxes:
        if (pShape->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR)
        {
            // Get the text area of the shape
            tools::Rectangle aRect
                = getRelativeTextRectangle(pObj ? pObj : pShape->FindRealSdrObject());

            // Set the textbox position at the X-axis:
            SwFormatHoriOrient aNewHOri(pFormat->GetHoriOrient());
            if (bIsGroupObj && aNewHOri.GetHoriOrient() != text::HoriOrientation::NONE)
                aNewHOri.SetHoriOrient(text::HoriOrientation::NONE);

            // tdf#152142: For RTL, positioning is relative to the right
            if (pShape->GetLayoutDir() == SwFrameFormat::HORI_R2L)
            {
                auto nRightSpace = pShape->GetLRSpace().ResolveRight({});

                const bool bMSOLayout = pFormat->getIDocumentSettingAccess().get(
                    DocumentSettingId::DO_NOT_MIRROR_RTL_DRAW_OBJS);
                if (bMSOLayout)
                {
                    aNewHOri.SetPos(-aRect.Right() + nRightSpace
                                    + (bIsGroupObj ? pObj->GetRelativePos().getX() : 0));
                }
                else
                {
                    aNewHOri.SetPos(aRect.Right() + nRightSpace
                                    + (bIsGroupObj ? pObj->GetRelativePos().getX() : 0));
                }
            }
            else
            {
                auto nLeftSpace = pShape->GetLRSpace().ResolveLeft({});
                aNewHOri.SetPos(aRect.Left() + nLeftSpace
                                + (bIsGroupObj ? pObj->GetRelativePos().getX() : 0));
            }

            SwFormatVertOrient aNewVOri(pFormat->GetVertOrient());

            // Special handling of group textboxes
            if (bIsGroupObj)
            {
                // There are the following cases:
                // case 1: The textbox should be in that position where the shape is.
                // case 2: The shape has negative offset so that have to be subtracted
                // case 3: The shape and its parent shape also has negative offset, so subtract
                aNewVOri.SetPos(
                    ((pObj->GetRelativePos().getY()) > 0
                         ? (pShape->GetVertOrient().GetPos() > 0
                                ? pObj->GetRelativePos().getY()
                                : pObj->GetRelativePos().getY() - pShape->GetVertOrient().GetPos())
                         : (pShape->GetVertOrient().GetPos() > 0
                                ? 0 // Is this can be a variation?
                                : pObj->GetRelativePos().getY() - pShape->GetVertOrient().GetPos()))
                    + aRect.Top());
            }
            else
            {
                // Simple textboxes: vertical position equals to the vertical offset of the shape
                aNewVOri.SetPos(
                    ((pShape->GetVertOrient().GetPos()) > 0 ? pShape->GetVertOrient().GetPos() : 0)
                    + aRect.Top());
            }

            // Special cases when the shape is aligned to the line
            if (pShape->GetVertOrient().GetVertOrient() != text::VertOrientation::NONE)
            {
                aNewVOri.SetVertOrient(text::VertOrientation::NONE);
                switch (pShape->GetVertOrient().GetVertOrient())
                {
                    // Top aligned shape
                    case text::VertOrientation::TOP:
                    case text::VertOrientation::CHAR_TOP:
                    case text::VertOrientation::LINE_TOP:
                    {
                        aNewVOri.SetPos(aNewVOri.GetPos() - pShape->GetFrameSize().GetHeight());
                        break;
                    }
                    // Bottom aligned shape
                    case text::VertOrientation::BOTTOM:
                    case text::VertOrientation::CHAR_BOTTOM:
                    case text::VertOrientation::LINE_BOTTOM:
                    {
                        aNewVOri.SetPos(aNewVOri.GetPos() + pShape->GetFrameSize().GetHeight());
                        break;
                    }
                    // Center aligned shape
                    case text::VertOrientation::CENTER:
                    case text::VertOrientation::CHAR_CENTER:
                    case text::VertOrientation::LINE_CENTER:
                    {
                        aNewVOri.SetPos(aNewVOri.GetPos()
                                        + std::lroundf(pShape->GetFrameSize().GetHeight() / 2));
                        break;
                    }
                    default:
                        break;
                }
            }

            pFormat->SetFormatAttr(aNewHOri);
            pFormat->SetFormatAttr(aNewVOri);
        }
        // Other cases when the shape has different anchor from AS_CHAR
        else
        {
            // Text area of the shape
            tools::Rectangle aRect
                = getRelativeTextRectangle(pObj ? pObj : pShape->FindRealSdrObject());

            // Set the same position as the (child) shape has
            SwFormatHoriOrient aNewHOri(pShape->GetHoriOrient());
            if (bIsGroupObj && aNewHOri.GetHoriOrient() != text::HoriOrientation::NONE)
                aNewHOri.SetHoriOrient(text::HoriOrientation::NONE);

            aNewHOri.SetPos(
                (bIsGroupObj && pObj ? pObj->GetRelativePos().getX() : aNewHOri.GetPos())
                + aRect.Left());
            SwFormatVertOrient aNewVOri(pShape->GetVertOrient());
            aNewVOri.SetPos(
                (bIsGroupObj && pObj ? pObj->GetRelativePos().getY() : aNewVOri.GetPos())
                + aRect.Top());

            // Get the distance of the child shape inside its parent
            const auto nInshapePos
                = pObj ? pObj->GetRelativePos() - pShape->FindRealSdrObject()->GetRelativePos()
                       : Point();

            // Special case: the shape has relative position from the page
            if (pShape->GetHoriOrient().GetRelationOrient() == text::RelOrientation::PAGE_FRAME
                && pShape->GetAnchor().GetAnchorId() != RndStdIds::FLY_AT_PAGE)
            {
                aNewHOri.SetRelationOrient(text::RelOrientation::PAGE_FRAME);
                aNewHOri.SetPos(pShape->GetHoriOrient().GetPos() + nInshapePos.getX()
                                + aRect.Left());
            }

            if (pShape->GetVertOrient().GetRelationOrient() == text::RelOrientation::PAGE_FRAME
                && pShape->GetAnchor().GetAnchorId() != RndStdIds::FLY_AT_PAGE)
            {
                aNewVOri.SetRelationOrient(text::RelOrientation::PAGE_FRAME);
                aNewVOri.SetPos(pShape->GetVertOrient().GetPos() + nInshapePos.getY()
                                + aRect.Top());
            }

            // Other special case: shape is inside a table or floating table following the text flow
            if (pShape->GetFollowTextFlow().GetValue() && pShape->GetAnchor().GetAnchorNode()
                && pShape->GetAnchor().GetAnchorNode()->FindTableNode())
            {
                // WARNING: It is highly likely that everything here is simplistic and incomplete.

                // Microsoft allows WrapThrough shapes to be placed outside of the cell
                // despite having specified layoutInCell.
                // (Re-using existing, appropriately-named, compat flag to identify MSO formats.)
                const bool bMSOLayout = pFormat->getIDocumentSettingAccess().get(
                    DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION);

                // Table position
                Point nTableOffset;
                // Floating table
                if (auto pFly
                    = pShape->GetAnchor().GetAnchorNode()->FindTableNode()->FindFlyStartNode())
                {
                    if (auto pFlyFormat = pFly->GetFlyFormat())
                    {
                        nTableOffset.setX(pFlyFormat->GetHoriOrient().GetPos());
                        nTableOffset.setY(pFlyFormat->GetVertOrient().GetPos());
                    }
                }
                else
                // Normal table
                {
                    auto pTableNode = pShape->GetAnchor().GetAnchorNode()->FindTableNode();
                    if (auto pTableFormat = pTableNode->GetTable().GetFrameFormat())
                    {
                        nTableOffset.setX(pTableFormat->GetHoriOrient().GetPos());
                        nTableOffset.setY(pTableFormat->GetVertOrient().GetPos());
                    }
                }

                // stay within the cell limits (since following text flow)
                // unless this is based on a Microsoft layout which has a through-wrap exception.
                bool bWrapThrough = false;
                getShapeWrapThrough(pShape, bWrapThrough);
                sal_Int32 nPos = aNewHOri.GetPos();
                if (nPos < 0 && (!bMSOLayout || !bWrapThrough))
                    nPos = 0;
                // Add the table positions to the textbox
                aNewHOri.SetPos(nPos + nTableOffset.getX());

                if (pShape->GetVertOrient().GetRelationOrient() == text::RelOrientation::PAGE_FRAME
                    || pShape->GetVertOrient().GetRelationOrient()
                           == text::RelOrientation::PAGE_PRINT_AREA)
                {
                    nPos = aNewVOri.GetPos();
                    if (nPos < 0 && (!bMSOLayout || !bWrapThrough))
                        nPos = 0;
                    aNewVOri.SetPos(nPos + nTableOffset.getY());
                }
            }

            pFormat->SetFormatAttr(aNewHOri);
            pFormat->SetFormatAttr(aNewVOri);
        }
        return true;
    }

    return false;
}

bool SwTextBoxHelper::syncTextBoxSize(SwFrameFormat* pShape, SdrObject* pObj)
{
    if (!pShape || !pObj)
        return false;

    if (auto pTextBox = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj))
    {
        auto aGuard = SwTextBoxLockGuard(*pShape->GetOtherTextBoxFormats());
        const auto aSize = getRelativeTextRectangle(pObj).GetSize();
        if (!aSize.IsEmpty())
        {
            SwFormatFrameSize aFrameSize(pTextBox->GetFrameSize());
            aFrameSize.SetSize(aSize);
            return pTextBox->SetFormatAttr(aFrameSize);
        }
    }

    return false;
}

bool SwTextBoxHelper::DoTextBoxZOrderCorrection(SwFrameFormat* pShape, const SdrObject* pObj)
{
    // TODO: do this with group shape textboxes.
    SdrObject* pShpObj = nullptr;

    pShpObj = pShape->FindRealSdrObject();

    if (!pShpObj)
    {
        SAL_WARN("sw.core", "SwTextBoxHelper::DoTextBoxZOrderCorrection(): "
                            "No Valid SdrObject for the shape!");
        return false;
    }

    auto pTextBox = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT, pObj);
    if (!pTextBox)
        return false;
    SdrObject* pFrmObj = pTextBox->FindRealSdrObject();
    if (!pFrmObj)
    {
        if (SwFlyFrameFormat* pFlyFrameFormat = dynamic_cast<SwFlyFrameFormat*>(pTextBox))
        {
            // During loading there is no ready SdrObj for z-ordering, so create and cache it here
            pFrmObj = SwXTextFrame::GetOrCreateSdrObject(*pFlyFrameFormat);
        }
    }
    if (!pFrmObj)
    {
        SAL_WARN("sw.core", "SwTextBoxHelper::DoTextBoxZOrderCorrection(): "
                            "No Valid SdrObject for the frame!");
        return false;
    }
    // Get the draw model from the doc
    SwDrawModel* pDrawModel = pShape->GetDoc().getIDocumentDrawModelAccess().GetDrawModel();
    if (!pDrawModel)
    {
        SAL_WARN("sw.core", "SwTextBoxHelper::DoTextBoxZOrderCorrection(): "
                            "No Valid Draw model for SdrObject for the shape!");
        return false;
    }
    if (!pFrmObj->getParentSdrObjListFromSdrObject())
    {
        SAL_WARN("sw.core", "SwTextBoxHelper::DoTextBoxZOrderCorrection(): "
                            "Frame object is not inserted into any parent");
        return false;
    }
    // Not really sure this will work on all pages, but it seems it will.
    // If the shape is behind the frame, is good, but if there are some objects
    // between of them that is wrong so put the frame exactly one level higher
    // than the shape.
    pFrmObj->ensureSortedImmediatelyAfter(*pShpObj);
    return true; // Success
}

void SwTextBoxHelper::synchronizeGroupTextBoxProperty(bool pFunc(SwFrameFormat*, SdrObject*),
                                                      SwFrameFormat* pFormat, SdrObject* pObj)
{
    if (auto pChildren = pObj->getChildrenOfSdrObject())
    {
        for (const rtl::Reference<SdrObject>& pChildObj : *pChildren)
            synchronizeGroupTextBoxProperty(pFunc, pFormat, pChildObj.get());
    }
    else
    {
        (*pFunc)(pFormat, pObj);
    }
}

std::vector<SwFrameFormat*> SwTextBoxHelper::CollectTextBoxes(const SdrObject* pGroupObject,
                                                              SwFrameFormat* pFormat)
{
    std::vector<SwFrameFormat*> vRet;
    if (auto pChildren = pGroupObject->getChildrenOfSdrObject())
    {
        for (const rtl::Reference<SdrObject>& pObj : *pChildren)
        {
            auto pChildTextBoxes = CollectTextBoxes(pObj.get(), pFormat);
            for (auto& rChildTextBox : pChildTextBoxes)
                vRet.push_back(rChildTextBox);
        }
    }
    else
    {
        if (isTextBox(pFormat, RES_DRAWFRMFMT, pGroupObject))
            vRet.push_back(getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT, pGroupObject));
    }
    return vRet;
}

bool SwTextBoxHelper::isAnchorSyncNeeded(const SwFrameFormat* pFirst, const SwFrameFormat* pSecond)
{
    if (!pFirst)
        return false;

    if (!pSecond)
        return false;

    if (pFirst == pSecond)
        return false;

    if (!pFirst->GetOtherTextBoxFormats())
        return false;

    if (!pSecond->GetOtherTextBoxFormats())
        return false;

    if (pFirst->GetOtherTextBoxFormats() != pSecond->GetOtherTextBoxFormats())
        return false;

    if (pFirst->GetOtherTextBoxFormats()->GetOwnerShape() == pSecond
        || pFirst == pSecond->GetOtherTextBoxFormats()->GetOwnerShape())
    {
        const auto& rShapeAnchor
            = pFirst->Which() == RES_DRAWFRMFMT ? pFirst->GetAnchor() : pSecond->GetAnchor();
        const auto& rFrameAnchor
            = pFirst->Which() == RES_FLYFRMFMT ? pFirst->GetAnchor() : pSecond->GetAnchor();

        if (rShapeAnchor.GetAnchorId() == rFrameAnchor.GetAnchorId())
        {
            if (rShapeAnchor.GetAnchorNode() && rFrameAnchor.GetAnchorNode())
            {
                if (*rShapeAnchor.GetContentAnchor() != *rFrameAnchor.GetContentAnchor())
                    return true;

                return false;
            }

            if (rShapeAnchor.GetAnchorId() == RndStdIds::FLY_AT_PAGE
                && rFrameAnchor.GetAnchorId() == RndStdIds::FLY_AT_PAGE)
            {
                if (rShapeAnchor.GetPageNum() == rFrameAnchor.GetPageNum())
                    return false;
                else
                    return true;
            }

            return true;
        }

        if (rShapeAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR
            && rFrameAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR)
        {
            if (rShapeAnchor.GetAnchorNode() && rFrameAnchor.GetAnchorNode())
            {
                if (*rShapeAnchor.GetContentAnchor() != *rFrameAnchor.GetContentAnchor())
                    return true;

                return false;
            }
        }
        return true;
    }
    return false;
}

bool SwTextBoxHelper::TextBoxIsFramePr(const SwFrameFormat& rFrameFormat)
{
    SdrObject* pSdrObj = const_cast<SdrObject*>(rFrameFormat.FindRealSdrObject());
    if (!pSdrObj)
        return false;

    uno::Reference<beans::XPropertySet> xPropertySet(pSdrObj->getUnoShape(), uno::UNO_QUERY);
    if (!xPropertySet.is())
        return false;

    uno::Reference<beans::XPropertySetInfo> xPropSetInfo(xPropertySet->getPropertySetInfo());
    if (!xPropSetInfo.is() || !xPropSetInfo->hasPropertyByName(u"FrameInteropGrabBag"_ustr))
        return false;

    bool bRet = false;
    uno::Sequence<beans::PropertyValue> propList;
    xPropertySet->getPropertyValue(u"FrameInteropGrabBag"_ustr) >>= propList;
    auto pProp = std::find_if(
        std::cbegin(propList), std::cend(propList),
        [](const beans::PropertyValue& rProp) { return rProp.Name == "ParaFrameProperties"; });
    if (pProp != std::cend(propList))
        pProp->Value >>= bRet;

    return bRet;
}

SwTextBoxNode::SwTextBoxNode(SwFrameFormat* pOwnerShape)
{
    assert(pOwnerShape);
    assert(pOwnerShape->Which() == RES_DRAWFRMFMT);

    m_bIsCloningInProgress = false;
    m_bLock = false;

    m_pOwnerShapeFormat = pOwnerShape;
    if (!m_pTextBoxes.empty())
        m_pTextBoxes.clear();
}

SwTextBoxNode::~SwTextBoxNode()
{
    if (!m_pTextBoxes.empty())
    {
        SAL_WARN("sw.core", "SwTextBoxNode::~SwTextBoxNode(): Text-Box-Vector still not empty!");
        assert(false);
    }
}

void SwTextBoxNode::AddTextBox(SdrObject* pDrawObject, SwFrameFormat* pNewTextBox)
{
    assert(pNewTextBox);
    assert(pNewTextBox->Which() == RES_FLYFRMFMT);

    assert(pDrawObject);

    SwTextBoxElement aElem;
    aElem.m_pDrawObject = pDrawObject;
    aElem.m_pTextBoxFormat = pNewTextBox;

    for (const auto& rE : m_pTextBoxes)
    {
        if (rE.m_pDrawObject == pDrawObject || rE.m_pTextBoxFormat == pNewTextBox)
        {
            SAL_WARN("sw.core", "SwTextBoxNode::AddTextBox(): Already exist!");
            return;
        }
    }

    auto pSwFlyDraw = dynamic_cast<SwFlyDrawObj*>(pDrawObject);
    if (pSwFlyDraw)
    {
        pSwFlyDraw->SetTextBox(true);
    }
    m_pTextBoxes.push_back(aElem);
}

void SwTextBoxNode::DelTextBox(const SdrObject* pDrawObject, bool bDelFromDoc)
{
    assert(pDrawObject);
    if (m_pTextBoxes.empty())
        return;

    for (auto it = m_pTextBoxes.begin(); it != m_pTextBoxes.end();)
    {
        if (it->m_pDrawObject == pDrawObject)
        {
            if (bDelFromDoc)
            {
                it->m_pTextBoxFormat->GetDoc().getIDocumentLayoutAccess().DelLayoutFormat(
                    it->m_pTextBoxFormat);
                // What about m_pTextBoxes? So, when the DelLayoutFormat() removes the format
                // then the ~SwFrameFormat() will call this method again to remove the entry.
                return;
            }
            else
            {
                it = m_pTextBoxes.erase(it);
                return;
            }
        }
        ++it;
    }

    SAL_WARN("sw.core", "SwTextBoxNode::DelTextBox(): Not found!");
}

void SwTextBoxNode::DelTextBox(const SwFrameFormat* pTextBox, bool bDelFromDoc)
{
    if (m_pTextBoxes.empty())
        return;

    for (auto it = m_pTextBoxes.begin(); it != m_pTextBoxes.end();)
    {
        if (it->m_pTextBoxFormat == pTextBox)
        {
            if (bDelFromDoc)
            {
                it->m_pTextBoxFormat->GetDoc().getIDocumentLayoutAccess().DelLayoutFormat(
                    it->m_pTextBoxFormat);
                // What about m_pTextBoxes? So, when the DelLayoutFormat() removes the format
                // then the ~SwFrameFormat() will call this method again to remove the entry.
                return;
            }
            else
            {
                it = m_pTextBoxes.erase(it);
                return;
            }
        }
        ++it;
    }

    SAL_WARN("sw.core", "SwTextBoxNode::DelTextBox(): Not found!");
}

SwFrameFormat* SwTextBoxNode::GetTextBox(const SdrObject* pDrawObject) const
{
    assert(pDrawObject);
    assert(m_pOwnerShapeFormat);

    if (auto& pTextBoxes = m_pOwnerShapeFormat->GetOtherTextBoxFormats())
    {
        if (size_t(pTextBoxes.use_count()) != pTextBoxes->GetTextBoxCount() + size_t(1))
        {
            SAL_WARN("sw.core", "SwTextBoxNode::GetTextBox(): RefCount and TexBox count mismatch!");
        }
    }

    if (m_bLock)
        return nullptr;

    if (!m_pTextBoxes.empty())
    {
        for (auto it = m_pTextBoxes.begin(); it != m_pTextBoxes.end(); it++)
        {
            if (it->m_pDrawObject == pDrawObject)
            {
                return it->m_pTextBoxFormat;
            }
        }
        SAL_WARN("sw.core", "SwTextBoxNode::GetTextBox(): Not found!");
    }

    return nullptr;
}

void SwTextBoxNode::ClearAll()
{
    // If this called from ~SwDoc(), then only the address entries
    // have to be removed, the format will be deleted by the
    // the mpSpzFrameFormatTable->DeleteAndDestroyAll() in ~SwDoc()!
    if (m_pOwnerShapeFormat->GetDoc().IsInDtor())
    {
        m_pTextBoxes.clear();
        return;
    }

    // For loop control
    sal_uInt16 nLoopCount = 0;

    // Reference not enough, copy needed.
    const size_t nTextBoxCount = m_pTextBoxes.size();

    // For loop has problems: When one entry deleted, the iterator has
    // to be refreshed according to the new situation. So using While() instead.
    while (!m_pTextBoxes.empty())
    {
        // Delete the last textbox of the vector from the doc
        // (what will call deregister in ~SwFrameFormat()
        m_pOwnerShapeFormat->GetDoc().getIDocumentLayoutAccess().DelLayoutFormat(
            m_pTextBoxes.back().m_pTextBoxFormat);

        // Check if we are looping
        if (nLoopCount > (nTextBoxCount + 1))
        {
            SAL_WARN("sw.core", "SwTextBoxNode::ClearAll(): Maximum loop count reached!");
            break;
        }
        else
        {
            nLoopCount++;
        }
    }

    // Ensure the vector is empty.
    if (!m_pTextBoxes.empty())
    {
        SAL_WARN("sw.core", "SwTextBoxNode::ClearAll(): Text-Box-Vector still not empty!");
        assert(false);
    }
}

bool SwTextBoxNode::IsGroupTextBox() const { return m_pTextBoxes.size() > 1; }

std::map<SdrObject*, SwFrameFormat*> SwTextBoxNode::GetAllTextBoxes() const
{
    std::map<SdrObject*, SwFrameFormat*> aRet;
    for (auto& rElem : m_pTextBoxes)
    {
        aRet.emplace(rElem.m_pDrawObject, rElem.m_pTextBoxFormat);
    }
    return aRet;
}

void SwTextBoxNode::Clone(SwDoc* pDoc, const SwFormatAnchor& rNewAnc, SwFrameFormat* o_pTarget,
                          bool bSetAttr, bool bMakeFrame) const
{
    if (!o_pTarget || !pDoc)
        return;

    if (o_pTarget->Which() != RES_DRAWFRMFMT)
        return;

    if (m_bIsCloningInProgress)
        return;

    m_bIsCloningInProgress = true;

    Clone_Impl(pDoc, rNewAnc, o_pTarget, m_pOwnerShapeFormat->FindSdrObject(),
               o_pTarget->FindSdrObject(), bSetAttr, bMakeFrame);

    m_bIsCloningInProgress = false;

    for (auto& rElem : m_pTextBoxes)
    {
        SwTextBoxHelper::changeAnchor(m_pOwnerShapeFormat, rElem.m_pDrawObject);
        SwTextBoxHelper::doTextBoxPositioning(m_pOwnerShapeFormat, rElem.m_pDrawObject);
        SwTextBoxHelper::DoTextBoxZOrderCorrection(m_pOwnerShapeFormat, rElem.m_pDrawObject);
        SwTextBoxHelper::syncTextBoxSize(m_pOwnerShapeFormat, rElem.m_pDrawObject);
    }
}

void SwTextBoxNode::Clone_Impl(SwDoc* pDoc, const SwFormatAnchor& rNewAnc, SwFrameFormat* o_pTarget,
                               const SdrObject* pSrcObj, SdrObject* pDestObj, bool bSetAttr,
                               bool bMakeFrame) const
{
    if (!pSrcObj || !pDestObj)
        return;

    auto pSrcList = pSrcObj->getChildrenOfSdrObject();
    auto pDestList = pDestObj->getChildrenOfSdrObject();

    if (pSrcList && pDestList)
    {
        if (pSrcList->GetObjCount() != pDestList->GetObjCount())
        {
            SAL_WARN("sw.core", "SwTextBoxNode::Clone_Impl(): Difference between the shapes!");
            return;
        }

        for (auto itSrc = pSrcList->begin(), itDest = pDestList->begin(); itSrc != pSrcList->end();
             ++itSrc, ++itDest)
        {
            Clone_Impl(pDoc, rNewAnc, o_pTarget, itSrc->get(), itDest->get(), bSetAttr, bMakeFrame);
        }
        return;
    }

    if (!pSrcList && !pDestList)
    {
        if (auto pSrcFormat = GetTextBox(pSrcObj))
        {
            SwFormatAnchor aNewAnchor(rNewAnc);
            if (aNewAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR)
            {
                aNewAnchor.SetType(RndStdIds::FLY_AT_CHAR);

                if (!bMakeFrame)
                    bMakeFrame = true;
            }

            if (auto pTargetFormat = pDoc->getIDocumentLayoutAccess().CopyLayoutFormat(
                    *pSrcFormat, aNewAnchor, bSetAttr, bMakeFrame))
            {
                if (!o_pTarget->GetOtherTextBoxFormats())
                {
                    auto pNewTextBoxes = std::make_shared<SwTextBoxNode>(SwTextBoxNode(o_pTarget));
                    o_pTarget->SetOtherTextBoxFormats(pNewTextBoxes);
                    pNewTextBoxes->AddTextBox(pDestObj, pTargetFormat);
                    pTargetFormat->SetOtherTextBoxFormats(pNewTextBoxes);
                }
                else
                {
                    o_pTarget->GetOtherTextBoxFormats()->AddTextBox(pDestObj, pTargetFormat);
                    pTargetFormat->SetOtherTextBoxFormats(o_pTarget->GetOtherTextBoxFormats());
                }
                o_pTarget->SetFormatAttr(pTargetFormat->GetContent());
            }
        }
    }
}

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