/* -*- 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 <comphelper/string.hxx>
#include <svl/stritem.hxx>
#include <svx/svdotext.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdoutl.hxx>
#include <svx/svdmodel.hxx>
#include <svx/dialmgr.hxx>
#include <svx/strings.hrc>
#include <editeng/writingmodeitem.hxx>
#include <svx/sdtfchim.hxx>
#include <editeng/editdata.hxx>
#include <editeng/editstat.hxx>
#include <editeng/outlobj.hxx>
#include <editeng/editobj.hxx>
#include <editeng/outliner.hxx>
#include <textchain.hxx>
#include <textchainflow.hxx>
#include <tools/helpers.hxx>
#include <svx/sderitm.hxx>
#include <svx/sdooitm.hxx>
#include <svx/sdshitm.hxx>
#include <svx/sdtagitm.hxx>
#include <svx/sdtfsitm.hxx>
#include <svx/sdtmfitm.hxx>
#include <svx/xtextit0.hxx>
#include <sdr/properties/textproperties.hxx>
#include <sdr/contact/viewcontactoftextobj.hxx>
#include <basegfx/tuple/b2dtuple.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <vcl/gdimtf.hxx>
#include <vcl/virdev.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <sal/log.hxx>
#include <o3tl/unit_conversion.hxx>
#include <o3tl/temporary.hxx>
#include <comphelper/configuration.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/fhgtitem.hxx>
 
using namespace com::sun::star;
 
// BaseProperties section
std::unique_ptr<sdr::properties::BaseProperties> SdrTextObj::CreateObjectSpecificProperties()
{
    return std::make_unique<sdr::properties::TextProperties>(*this);
}
 
// DrawContact section
std::unique_ptr<sdr::contact::ViewContact> SdrTextObj::CreateObjectSpecificViewContact()
{
    return std::make_unique<sdr::contact::ViewContactOfTextObj>(*this);
}
 
SdrTextObj::SdrTextObj(SdrModel& rSdrModel)
    : SdrAttrObj(rSdrModel)
    , mpEditingOutliner(nullptr)
    , meTextKind(SdrObjKind::Text)
    , maTextEditOffset(Point(0, 0))
    , mbTextFrame(false)
    , mbNoShear(false)
    , mbTextSizeDirty(false)
    , mbInEditMode(false)
    , mbDisableAutoWidthOnDragging(false)
    , mbTextAnimationAllowed(true)
    , mbInDownScale(false)
{
    // #i25616#
    mbSupportTextIndentingOnLineWidthChange = true;
}
 
SdrTextObj::SdrTextObj(SdrModel& rSdrModel, SdrTextObj const & rSource)
    : SdrAttrObj(rSdrModel, rSource)
    , mpEditingOutliner(nullptr)
    , meTextKind(rSource.meTextKind)
    , maTextEditOffset(Point(0, 0))
    , mbTextFrame(rSource.mbTextFrame)
    , mbNoShear(rSource.mbNoShear)
    , mbTextSizeDirty(rSource.mbTextSizeDirty)
    , mbInEditMode(false)
    , mbDisableAutoWidthOnDragging(rSource.mbDisableAutoWidthOnDragging)
    , mbTextAnimationAllowed(true)
    , mbInDownScale(false)
{
    // #i25616#
    mbSupportTextIndentingOnLineWidthChange = true;
 
    maRectangle = rSource.maRectangle;
    maGeo = rSource.maGeo;
    maTextSize = rSource.maTextSize;
 
    // Not all of the necessary parameters were copied yet.
    SdrText* pText = getActiveText();
 
    if( pText && rSource.HasText() )
    {
        // before pNewOutlinerParaObject was created the same, but
        // set at mpText (outside this scope), but mpText might be
        // empty (this operator== seems not prepared for MultiText
        // objects). In the current form it makes only sense to
        // create locally and use locally on a known existing SdrText
        const Outliner* pEO = rSource.mpEditingOutliner;
        std::optional<OutlinerParaObject> pNewOutlinerParaObject;
 
        if (pEO!=nullptr)
        {
            pNewOutlinerParaObject = pEO->CreateParaObject();
        }
        else if (nullptr != rSource.getActiveText()->GetOutlinerParaObject())
        {
            pNewOutlinerParaObject = *rSource.getActiveText()->GetOutlinerParaObject();
        }
 
        pText->SetOutlinerParaObject( std::move(pNewOutlinerParaObject) );
    }
 
    ImpSetTextStyleSheetListeners();
}
 
SdrTextObj::SdrTextObj(SdrModel& rSdrModel, const tools::Rectangle& rNewRect)
    : SdrAttrObj(rSdrModel)
    , mpEditingOutliner(nullptr)
    , meTextKind(SdrObjKind::Text)
    , maTextEditOffset(Point(0, 0))
    , mbTextFrame(false)
    , mbNoShear(false)
    , mbTextSizeDirty(false)
    , mbInEditMode(false)
    , mbDisableAutoWidthOnDragging(false)
    , mbTextAnimationAllowed(true)
    , mbInDownScale(false)
{
    tools::Rectangle aRectangle(rNewRect);
    ImpJustifyRect(aRectangle);
    setRectangle(aRectangle);
 
    // #i25616#
    mbSupportTextIndentingOnLineWidthChange = true;
}
 
SdrTextObj::SdrTextObj(SdrModel& rSdrModel, SdrObjKind eNewTextKind)
    : SdrAttrObj(rSdrModel)
    , mpEditingOutliner(nullptr)
    , meTextKind(eNewTextKind)
    , maTextEditOffset(Point(0, 0))
    , mbTextFrame(true)
    , mbNoShear(true)
    , mbTextSizeDirty(false)
    , mbInEditMode(false)
    , mbDisableAutoWidthOnDragging(false)
    , mbTextAnimationAllowed(true)
    , mbInDownScale(false)
{
    // #i25616#
    mbSupportTextIndentingOnLineWidthChange = true;
}
 
SdrTextObj::SdrTextObj(SdrModel& rSdrModel, SdrObjKind eNewTextKind,
                       const tools::Rectangle& rNewRect)
    : SdrAttrObj(rSdrModel)
    , mpEditingOutliner(nullptr)
    , meTextKind(eNewTextKind)
    , maTextEditOffset(Point(0, 0))
    , mbTextFrame(true)
    , mbNoShear(true)
    , mbTextSizeDirty(false)
    , mbInEditMode(false)
    , mbDisableAutoWidthOnDragging(false)
    , mbTextAnimationAllowed(true)
    , mbInDownScale(false)
{
    tools::Rectangle aRectangle(rNewRect);
    ImpJustifyRect(aRectangle);
    setRectangle(aRectangle);
 
    // #i25616#
    mbSupportTextIndentingOnLineWidthChange = true;
}
 
SdrTextObj::~SdrTextObj()
{
    mxText.clear();
    ImpDeregisterLink();
}
 
void SdrTextObj::FitFrameToTextSize()
{
    ImpJustifyRect(maRectangle);
 
    SdrText* pText = getActiveText();
    if(pText==nullptr || !pText->GetOutlinerParaObject())
        return;
 
    SdrOutliner& rOutliner=ImpGetDrawOutliner();
    rOutliner.SetPaperSize(Size(getRectangle().Right() - getRectangle().Left(), getRectangle().Bottom() - getRectangle().Top()));
    rOutliner.SetUpdateLayout(true);
    rOutliner.SetText(*pText->GetOutlinerParaObject());
    Size aNewSize(rOutliner.CalcTextSize());
    rOutliner.Clear();
    aNewSize.AdjustWidth( 1 ); // because of possible rounding errors
    aNewSize.AdjustWidth(GetTextLeftDistance()+GetTextRightDistance() );
    aNewSize.AdjustHeight(GetTextUpperDistance()+GetTextLowerDistance() );
    tools::Rectangle aNewRect(getRectangle());
    aNewRect.SetSize(aNewSize);
    ImpJustifyRect(aNewRect);
 
    if (aNewRect != getRectangle())
        SetLogicRect(aNewRect);
}
 
void SdrTextObj::NbcSetText(const OUString& rStr)
{
    SdrOutliner& rOutliner=ImpGetDrawOutliner();
    rOutliner.SetStyleSheet( 0, GetStyleSheet());
    rOutliner.SetText(rStr,rOutliner.GetParagraph( 0 ));
    std::optional<OutlinerParaObject> pNewText=rOutliner.CreateParaObject();
    NbcSetOutlinerParaObject(std::move(pNewText));
    mbTextSizeDirty=true;
}
 
void SdrTextObj::SetText(const OUString& rStr)
{
    tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect();
    NbcSetText(rStr);
    SetChanged();
    BroadcastObjectChange();
    SendUserCall(SdrUserCallType::Resize,aBoundRect0);
}
 
void SdrTextObj::NbcSetText(SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat)
{
    SdrOutliner& rOutliner=ImpGetDrawOutliner();
    rOutliner.SetStyleSheet( 0, GetStyleSheet());
    rOutliner.Read(rInput,rBaseURL,eFormat);
    std::optional<OutlinerParaObject> pNewText=rOutliner.CreateParaObject();
    rOutliner.SetUpdateLayout(true);
    Size aSize(rOutliner.CalcTextSize());
    rOutliner.Clear();
    NbcSetOutlinerParaObject(std::move(pNewText));
    maTextSize=aSize;
    mbTextSizeDirty=false;
}
 
void SdrTextObj::SetText(SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat)
{
    tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect();
    NbcSetText(rInput,rBaseURL,eFormat);
    SetChanged();
    BroadcastObjectChange();
    SendUserCall(SdrUserCallType::Resize,aBoundRect0);
}
 
const Size& SdrTextObj::GetTextSize() const
{
    if (mbTextSizeDirty)
    {
        Size aSiz;
        SdrText* pText = getActiveText();
        if( pText && pText->GetOutlinerParaObject ())
        {
            SdrOutliner& rOutliner=ImpGetDrawOutliner();
            rOutliner.SetText(*pText->GetOutlinerParaObject());
            rOutliner.SetUpdateLayout(true);
            aSiz=rOutliner.CalcTextSize();
            rOutliner.Clear();
        }
        // casting to nonconst twice
        const_cast<SdrTextObj*>(this)->maTextSize = aSiz;
        const_cast<SdrTextObj*>(this)->mbTextSizeDirty = false;
    }
    return maTextSize;
}
 
bool SdrTextObj::IsAutoGrowHeight() const
{
    if(!mbTextFrame)
        return false; // AutoGrow only together with TextFrames
 
    const SfxItemSet& rSet = GetObjectItemSet();
    bool bRet = rSet.Get(SDRATTR_TEXT_AUTOGROWHEIGHT).GetValue();
 
    if(bRet)
    {
        SdrTextAniKind eAniKind = rSet.Get(SDRATTR_TEXT_ANIKIND).GetValue();
 
        if(eAniKind == SdrTextAniKind::Scroll || eAniKind == SdrTextAniKind::Alternate || eAniKind == SdrTextAniKind::Slide)
        {
            SdrTextAniDirection eDirection = rSet.Get(SDRATTR_TEXT_ANIDIRECTION).GetValue();
 
            if(eDirection == SdrTextAniDirection::Up || eDirection == SdrTextAniDirection::Down)
            {
                bRet = false;
            }
        }
    }
    return bRet;
}
 
bool SdrTextObj::IsAutoGrowWidth() const
{
    if (!mbTextFrame)
        return false; // AutoGrow only together with TextFrames
 
    const SfxItemSet& rSet = GetObjectItemSet();
    bool bRet = rSet.Get(SDRATTR_TEXT_AUTOGROWWIDTH).GetValue();
 
    bool bInEditMOde = IsInEditMode();
 
    if(!bInEditMOde && bRet)
    {
        SdrTextAniKind eAniKind = rSet.Get(SDRATTR_TEXT_ANIKIND).GetValue();
 
        if(eAniKind == SdrTextAniKind::Scroll || eAniKind == SdrTextAniKind::Alternate || eAniKind == SdrTextAniKind::Slide)
        {
            SdrTextAniDirection eDirection = rSet.Get(SDRATTR_TEXT_ANIDIRECTION).GetValue();
 
            if(eDirection == SdrTextAniDirection::Left || eDirection == SdrTextAniDirection::Right)
            {
                bRet = false;
            }
        }
    }
    return bRet;
}
 
SdrTextHorzAdjust SdrTextObj::GetTextHorizontalAdjust() const
{
    return GetTextHorizontalAdjust(GetObjectItemSet());
}
 
SdrTextHorzAdjust SdrTextObj::GetTextHorizontalAdjust(const SfxItemSet& rSet) const
{
    if(IsContourTextFrame())
        return SDRTEXTHORZADJUST_BLOCK;
 
    SdrTextHorzAdjust eRet = rSet.Get(SDRATTR_TEXT_HORZADJUST).GetValue();
 
    bool bInEditMode = IsInEditMode();
 
    if(!bInEditMode && eRet == SDRTEXTHORZADJUST_BLOCK)
    {
        SdrTextAniKind eAniKind = rSet.Get(SDRATTR_TEXT_ANIKIND).GetValue();
 
        if(eAniKind == SdrTextAniKind::Scroll || eAniKind == SdrTextAniKind::Alternate || eAniKind == SdrTextAniKind::Slide)
        {
            SdrTextAniDirection eDirection = rSet.Get(SDRATTR_TEXT_ANIDIRECTION).GetValue();
 
            if(eDirection == SdrTextAniDirection::Left || eDirection == SdrTextAniDirection::Right)
            {
                eRet = SDRTEXTHORZADJUST_LEFT;
            }
        }
    }
 
    return eRet;
} // defaults: BLOCK (justify) for text frame, CENTER for captions of drawing objects
 
SdrTextVertAdjust SdrTextObj::GetTextVerticalAdjust() const
{
    return GetTextVerticalAdjust(GetObjectItemSet());
}
 
SdrTextVertAdjust SdrTextObj::GetTextVerticalAdjust(const SfxItemSet& rSet) const
{
    if(IsContourTextFrame())
        return SDRTEXTVERTADJUST_TOP;
 
    // Take care for vertical text animation here
    SdrTextVertAdjust eRet = rSet.Get(SDRATTR_TEXT_VERTADJUST).GetValue();
    bool bInEditMode = IsInEditMode();
 
    // Take care for vertical text animation here
    if(!bInEditMode && eRet == SDRTEXTVERTADJUST_BLOCK)
    {
        SdrTextAniKind eAniKind = rSet.Get(SDRATTR_TEXT_ANIKIND).GetValue();
 
        if(eAniKind == SdrTextAniKind::Scroll || eAniKind == SdrTextAniKind::Alternate || eAniKind == SdrTextAniKind::Slide)
        {
            SdrTextAniDirection eDirection = rSet.Get(SDRATTR_TEXT_ANIDIRECTION).GetValue();
 
            if(eDirection == SdrTextAniDirection::Left || eDirection == SdrTextAniDirection::Right)
            {
                eRet = SDRTEXTVERTADJUST_TOP;
            }
        }
    }
 
    return eRet;
} // defaults: TOP for text frame, CENTER for captions of drawing objects
 
void SdrTextObj::ImpJustifyRect(tools::Rectangle& rRect)
{
    if (!rRect.IsEmpty()) {
        rRect.Normalize();
        if (rRect.Left()==rRect.Right()) rRect.AdjustRight( 1 );
        if (rRect.Top()==rRect.Bottom()) rRect.AdjustBottom( 1 );
    }
}
 
void SdrTextObj::ImpCheckShear()
{
    if (mbNoShear && maGeo.m_nShearAngle)
    {
        maGeo.m_nShearAngle = 0_deg100;
        maGeo.mfTanShearAngle = 0;
    }
}
 
void SdrTextObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const
{
    bool bNoTextFrame=!IsTextFrame();
    rInfo.bResizeFreeAllowed=bNoTextFrame || ((maGeo.m_nRotationAngle.get() % 9000) == 0);
    rInfo.bResizePropAllowed=true;
    rInfo.bRotateFreeAllowed=true;
    rInfo.bRotate90Allowed  =true;
    rInfo.bMirrorFreeAllowed=bNoTextFrame;
    rInfo.bMirror45Allowed  =bNoTextFrame;
    rInfo.bMirror90Allowed  =bNoTextFrame;
 
    // allow transparency
    rInfo.bTransparenceAllowed = true;
 
    rInfo.bShearAllowed     =bNoTextFrame;
    rInfo.bEdgeRadiusAllowed=true;
    bool bCanConv=ImpCanConvTextToCurve();
    rInfo.bCanConvToPath    =bCanConv;
    rInfo.bCanConvToPoly    =bCanConv;
    rInfo.bCanConvToPathLineToArea=bCanConv;
    rInfo.bCanConvToPolyLineToArea=bCanConv;
    rInfo.bCanConvToContour = (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary());
}
 
SdrObjKind SdrTextObj::GetObjIdentifier() const
{
    return meTextKind;
}
 
bool SdrTextObj::HasTextImpl( SdrOutliner const * pOutliner )
{
    bool bRet=false;
    if(pOutliner)
    {
        Paragraph* p1stPara=pOutliner->GetParagraph( 0 );
        sal_Int32 nParaCount=pOutliner->GetParagraphCount();
        if(p1stPara==nullptr)
            nParaCount=0;
 
        if(nParaCount==1)
        {
            // if it is only one paragraph, check if that paragraph is empty
            if( pOutliner->GetText(p1stPara).isEmpty() )
                nParaCount = 0;
        }
 
        bRet= nParaCount!=0;
    }
    return bRet;
}
 
void SdrTextObj::handlePageChange(SdrPage* pOldPage, SdrPage* pNewPage)
{
    const bool bRemove(pNewPage == nullptr && pOldPage != nullptr);
    const bool bInsert(pNewPage != nullptr && pOldPage == nullptr);
    const bool bLinked(IsLinkedText());
 
    if (bLinked && bRemove)
    {
        ImpDeregisterLink();
    }
 
    // call parent
    SdrAttrObj::handlePageChange(pOldPage, pNewPage);
 
    if (bLinked && bInsert)
    {
        ImpRegisterLink();
    }
}
 
void SdrTextObj::NbcSetEckenradius(tools::Long nRad)
{
    SetObjectItem(makeSdrEckenradiusItem(nRad));
}
 
// #115391# This implementation is based on the object size (aRect) and the
// states of IsAutoGrowWidth/Height to correctly set TextMinFrameWidth/Height
void SdrTextObj::AdaptTextMinSize()
{
    if (!mbTextFrame)
        // Only do this for text frame.
        return;
 
    if (getSdrModelFromSdrObject().IsPasteResize())
        // Don't do this during paste resize.
        return;
 
    const bool bW = IsAutoGrowWidth();
    const bool bH = IsAutoGrowHeight();
 
    if (!bW && !bH)
        // No auto grow requested.  Bail out.
        return;
 
    SfxItemSetFixed<SDRATTR_TEXT_MINFRAMEHEIGHT, SDRATTR_TEXT_AUTOGROWHEIGHT,
        SDRATTR_TEXT_MINFRAMEWIDTH, SDRATTR_TEXT_AUTOGROWWIDTH> // contains SDRATTR_TEXT_MAXFRAMEWIDTH
        aSet(*GetObjectItemSet().GetPool());
 
    if(bW)
    {
        // Set minimum width.
        const tools::Long nDist = GetTextLeftDistance() + GetTextRightDistance();
        const tools::Long nW = std::max<tools::Long>(0, getRectangle().GetWidth() - 1 - nDist); // text width without margins
 
        aSet.Put(makeSdrTextMinFrameWidthItem(nW));
 
        if(!IsVerticalWriting() && mbDisableAutoWidthOnDragging)
        {
            mbDisableAutoWidthOnDragging = true;
            aSet.Put(makeSdrTextAutoGrowWidthItem(false));
        }
    }
 
    if(bH)
    {
        // Set Minimum height.
        const tools::Long nDist = GetTextUpperDistance() + GetTextLowerDistance();
        const tools::Long nH = std::max<tools::Long>(0, getRectangle().GetHeight() - 1 - nDist); // text height without margins
 
        aSet.Put(makeSdrTextMinFrameHeightItem(nH));
 
        if(IsVerticalWriting() && mbDisableAutoWidthOnDragging)
        {
            mbDisableAutoWidthOnDragging = false;
            aSet.Put(makeSdrTextAutoGrowHeightItem(false));
        }
    }
 
    SetObjectItemSet(aSet);
}
 
void SdrTextObj::ImpSetContourPolygon( SdrOutliner& rOutliner, tools::Rectangle const & rAnchorRect, bool bLineWidth ) const
{
    basegfx::B2DPolyPolygon aXorPolyPolygon(TakeXorPoly());
    std::optional<basegfx::B2DPolyPolygon> pContourPolyPolygon;
    basegfx::B2DHomMatrix aMatrix(basegfx::utils::createTranslateB2DHomMatrix(
        -rAnchorRect.Left(), -rAnchorRect.Top()));
 
    if(maGeo.m_nRotationAngle)
    {
        // Unrotate!
        aMatrix.rotate(-toRadians(maGeo.m_nRotationAngle));
    }
 
    aXorPolyPolygon.transform(aMatrix);
 
    if( bLineWidth )
    {
        // Take line width into account.
        // When doing the hit test, avoid this. (Performance!)
        pContourPolyPolygon.emplace();
 
        // test if shadow needs to be avoided for TakeContour()
        const SfxItemSet& rSet = GetObjectItemSet();
        bool bShadowOn = rSet.Get(SDRATTR_SHADOW).GetValue();
 
        // #i33696#
        // Remember TextObject currently set at the DrawOutliner, it WILL be
        // replaced during calculating the outline since it uses an own paint
        // and that one uses the DrawOutliner, too.
        const SdrTextObj* pLastTextObject = rOutliner.GetTextObj();
 
        if(bShadowOn)
        {
            // force shadow off
            rtl::Reference<SdrTextObj> pCopy = SdrObject::Clone(*this, getSdrModelFromSdrObject());
            pCopy->SetMergedItem(makeSdrShadowItem(false));
            *pContourPolyPolygon = pCopy->TakeContour();
        }
        else
        {
            *pContourPolyPolygon = TakeContour();
        }
 
        // #i33696#
        // restore remembered text object
        if(pLastTextObject != rOutliner.GetTextObj())
        {
            rOutliner.SetTextObj(pLastTextObject);
        }
 
        pContourPolyPolygon->transform(aMatrix);
    }
 
    rOutliner.SetPolygon(aXorPolyPolygon, pContourPolyPolygon ? &*pContourPolyPolygon : nullptr);
}
 
void SdrTextObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const
{
    rRect = getRectangle();
}
 
// See also: <unnamed>::getTextAnchorRange in svx/source/sdr/primitive2d/sdrdecompositiontools.cxx
void SdrTextObj::AdjustRectToTextDistance(tools::Rectangle& rAnchorRect) const
{
    const tools::Long nLeftDist = GetTextLeftDistance();
    const tools::Long nRightDist = GetTextRightDistance();
    const tools::Long nUpperDist = GetTextUpperDistance();
    const tools::Long nLowerDist = GetTextLowerDistance();
    if (!IsVerticalWriting())
    {
        rAnchorRect.AdjustLeft(nLeftDist);
        rAnchorRect.AdjustTop(nUpperDist);
        rAnchorRect.AdjustRight(-nRightDist);
        rAnchorRect.AdjustBottom(-nLowerDist);
    }
    else if (IsTopToBottom())
    {
        rAnchorRect.AdjustLeft(nLowerDist);
        rAnchorRect.AdjustTop(nLeftDist);
        rAnchorRect.AdjustRight(-nUpperDist);
        rAnchorRect.AdjustBottom(-nRightDist);
    }
    else
    {
        rAnchorRect.AdjustLeft(nUpperDist);
        rAnchorRect.AdjustTop(nRightDist);
        rAnchorRect.AdjustRight(-nLowerDist);
        rAnchorRect.AdjustBottom(-nLeftDist);
    }
 
    // Since sizes may be bigger than the object bounds it is necessary to
    // justify the rect now.
    ImpJustifyRect(rAnchorRect);
}
 
void SdrTextObj::TakeTextAnchorRect(tools::Rectangle& rAnchorRect) const
{
    tools::Rectangle aAnkRect(getRectangle()); // the rectangle in which we anchor
    bool bFrame=IsTextFrame();
    if (!bFrame) {
        TakeUnrotatedSnapRect(aAnkRect);
    }
    Point aRotateRef(aAnkRect.TopLeft());
    AdjustRectToTextDistance(aAnkRect);
 
    if (bFrame) {
        // TODO: Optimize this.
        if (aAnkRect.GetWidth()<2) aAnkRect.SetRight(aAnkRect.Left()+1 ); // minimum size h and v: 2 px
        if (aAnkRect.GetHeight()<2) aAnkRect.SetBottom(aAnkRect.Top()+1 );
    }
    if (maGeo.m_nRotationAngle) {
        Point aTmpPt(aAnkRect.TopLeft());
        RotatePoint(aTmpPt,aRotateRef,maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle);
        aTmpPt-=aAnkRect.TopLeft();
        aAnkRect.Move(aTmpPt.X(),aTmpPt.Y());
    }
    rAnchorRect=aAnkRect;
}
 
void SdrTextObj::TakeTextRect( SdrOutliner& rOutliner, tools::Rectangle& rTextRect, bool bNoEditText,
                               tools::Rectangle* pAnchorRect, bool bLineWidth ) const
{
    tools::Rectangle aAnkRect; // the rectangle in which we anchor
    TakeTextAnchorRect(aAnkRect);
    SdrTextVertAdjust eVAdj=GetTextVerticalAdjust();
    SdrTextHorzAdjust eHAdj=GetTextHorizontalAdjust();
    SdrTextAniKind      eAniKind=GetTextAniKind();
    SdrTextAniDirection eAniDirection=GetTextAniDirection();
 
    bool bFitToSize(IsFitToSize());
    bool bContourFrame=IsContourTextFrame();
 
    bool bFrame=IsTextFrame();
    EEControlBits nStat0=rOutliner.GetControlWord();
    Size aNullSize;
    if (!bContourFrame)
    {
        rOutliner.SetControlWord(nStat0|EEControlBits::AUTOPAGESIZE);
        rOutliner.SetMinAutoPaperSize(aNullSize);
        rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
    }
 
    if (!bFitToSize && !bContourFrame)
    {
        tools::Long nAnkWdt=aAnkRect.GetWidth();
        tools::Long nAnkHgt=aAnkRect.GetHeight();
        if (bFrame)
        {
            tools::Long nWdt=nAnkWdt;
            tools::Long nHgt=nAnkHgt;
 
            bool bInEditMode = IsInEditMode();
 
            if (!bInEditMode && (eAniKind==SdrTextAniKind::Scroll || eAniKind==SdrTextAniKind::Alternate || eAniKind==SdrTextAniKind::Slide))
            {
                // unlimited paper size for ticker text
                if (eAniDirection==SdrTextAniDirection::Left || eAniDirection==SdrTextAniDirection::Right) nWdt=1000000;
                if (eAniDirection==SdrTextAniDirection::Up || eAniDirection==SdrTextAniDirection::Down) nHgt=1000000;
            }
 
            bool bChainedFrame = IsChainable();
            // Might be required for overflow check working: do limit height to frame if box is chainable.
            if (!bChainedFrame) {
                // #i119885# Do not limit/force height to geometrical frame (vice versa for vertical writing)
 
                if(IsVerticalWriting())
                {
                    nWdt = 1000000;
                }
                else
                {
                    nHgt = 1000000;
                }
            }
 
            rOutliner.SetMaxAutoPaperSize(Size(nWdt,nHgt));
        }
 
        // New try with _BLOCK for hor and ver after completely
        // supporting full width for vertical text.
        if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !IsVerticalWriting())
        {
            rOutliner.SetMinAutoPaperSize(Size(nAnkWdt, 0));
            rOutliner.SetMinColumnWrapHeight(nAnkHgt);
        }
 
        if(SDRTEXTVERTADJUST_BLOCK == eVAdj && IsVerticalWriting())
        {
            rOutliner.SetMinAutoPaperSize(Size(0, nAnkHgt));
            rOutliner.SetMinColumnWrapHeight(nAnkWdt);
        }
    }
 
    rOutliner.SetPaperSize(aNullSize);
    if (bContourFrame)
        ImpSetContourPolygon( rOutliner, aAnkRect, bLineWidth );
 
    // put text into the outliner, if available from the edit outliner
    SdrText* pText = getActiveText();
    OutlinerParaObject* pOutlinerParaObject = pText ? pText->GetOutlinerParaObject() : nullptr;
    std::optional<OutlinerParaObject> pPara;
    if (mpEditingOutliner && !bNoEditText)
        pPara = mpEditingOutliner->CreateParaObject();
    else if (pOutlinerParaObject)
        pPara = *pOutlinerParaObject;
 
    if (pPara)
    {
        const bool bHitTest(&getSdrModelFromSdrObject().GetHitTestOutliner() == &rOutliner);
        const SdrTextObj* pTestObj = rOutliner.GetTextObj();
 
        if( !pTestObj || !bHitTest || pTestObj != this ||
            pTestObj->GetOutlinerParaObject() != pOutlinerParaObject )
        {
            if( bHitTest ) // #i33696# take back fix #i27510#
            {
                rOutliner.SetTextObj( this );
                rOutliner.SetFixedCellHeight(GetMergedItem(SDRATTR_TEXT_USEFIXEDCELLHEIGHT).GetValue());
            }
 
            rOutliner.SetUpdateLayout(true);
            rOutliner.SetText(*pPara);
        }
    }
    else
    {
        rOutliner.SetTextObj( nullptr );
    }
 
    rOutliner.SetUpdateLayout(true);
    rOutliner.SetControlWord(nStat0);
 
    if( pText )
        pText->CheckPortionInfo(rOutliner);
 
    Point aTextPos(aAnkRect.TopLeft());
    Size aTextSiz(rOutliner.GetPaperSize()); // GetPaperSize() adds a little tolerance, right?
 
    // For draw objects containing text correct hor/ver alignment if text is bigger
    // than the object itself. Without that correction, the text would always be
    // formatted to the left edge (or top edge when vertical) of the draw object.
    if(!IsTextFrame())
    {
        if(aAnkRect.GetWidth() < aTextSiz.Width() && !IsVerticalWriting())
        {
            // Horizontal case here. Correct only if eHAdj == SDRTEXTHORZADJUST_BLOCK,
            // else the alignment is wanted.
            if(SDRTEXTHORZADJUST_BLOCK == eHAdj)
            {
                eHAdj = SDRTEXTHORZADJUST_CENTER;
            }
        }
 
        if(aAnkRect.GetHeight() < aTextSiz.Height() && IsVerticalWriting())
        {
            // Vertical case here. Correct only if eHAdj == SDRTEXTVERTADJUST_BLOCK,
            // else the alignment is wanted.
            if(SDRTEXTVERTADJUST_BLOCK == eVAdj)
            {
                eVAdj = SDRTEXTVERTADJUST_CENTER;
            }
        }
    }
 
    if (eHAdj==SDRTEXTHORZADJUST_CENTER || eHAdj==SDRTEXTHORZADJUST_RIGHT)
    {
        tools::Long nFreeWdt=aAnkRect.GetWidth()-aTextSiz.Width();
        if (eHAdj==SDRTEXTHORZADJUST_CENTER)
            aTextPos.AdjustX(nFreeWdt/2 );
        if (eHAdj==SDRTEXTHORZADJUST_RIGHT)
            aTextPos.AdjustX(nFreeWdt );
    }
    if (eVAdj==SDRTEXTVERTADJUST_CENTER || eVAdj==SDRTEXTVERTADJUST_BOTTOM)
    {
        tools::Long nFreeHgt=aAnkRect.GetHeight()-aTextSiz.Height();
        if (eVAdj==SDRTEXTVERTADJUST_CENTER)
            aTextPos.AdjustY(nFreeHgt/2 );
        if (eVAdj==SDRTEXTVERTADJUST_BOTTOM)
            aTextPos.AdjustY(nFreeHgt );
    }
    if (maGeo.m_nRotationAngle)
        RotatePoint(aTextPos,aAnkRect.TopLeft(),maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle);
 
    if (pAnchorRect)
        *pAnchorRect=aAnkRect;
 
    // rTextRect might not be correct in some cases at ContourFrame
    rTextRect=tools::Rectangle(aTextPos,aTextSiz);
    if (bContourFrame)
        rTextRect=aAnkRect;
}
 
bool SdrTextObj::CanCreateEditOutlinerParaObject() const
{
    if( HasTextImpl( mpEditingOutliner ) )
    {
        return mpEditingOutliner->GetParagraphCount() > 0;
    }
    return false;
}
 
std::optional<OutlinerParaObject> SdrTextObj::CreateEditOutlinerParaObject() const
{
    std::optional<OutlinerParaObject> pPara;
    if( HasTextImpl( mpEditingOutliner ) )
    {
        sal_Int32 nParaCount = mpEditingOutliner->GetParagraphCount();
        pPara = mpEditingOutliner->CreateParaObject(0, nParaCount);
    }
    return pPara;
}
 
void SdrTextObj::ImpSetCharStretching(SdrOutliner& rOutliner, const Size& rTextSize, const Size& rShapeSize, Fraction& rFitXCorrection)
{
    OutputDevice* pOut = rOutliner.GetRefDevice();
    bool bNoStretching(false);
 
    if(pOut && pOut->GetOutDevType() == OUTDEV_PRINTER)
    {
        // check whether CharStretching is possible at all
        GDIMetaFile* pMtf = pOut->GetConnectMetaFile();
        OUString aTestString(u'J');
 
        if(pMtf && (!pMtf->IsRecord() || pMtf->IsPause()))
            pMtf = nullptr;
 
        if(pMtf)
            pMtf->Pause(true);
 
        vcl::Font aOriginalFont(pOut->GetFont());
        vcl::Font aTmpFont( OutputDevice::GetDefaultFont( DefaultFontType::SERIF, LANGUAGE_SYSTEM, GetDefaultFontFlags::OnlyOne ) );
 
        aTmpFont.SetFontSize(Size(0,100));
        pOut->SetFont(aTmpFont);
        Size aSize1(pOut->GetTextWidth(aTestString), pOut->GetTextHeight());
        aTmpFont.SetFontSize(Size(800,100));
        pOut->SetFont(aTmpFont);
        Size aSize2(pOut->GetTextWidth(aTestString), pOut->GetTextHeight());
        pOut->SetFont(aOriginalFont);
 
        if(pMtf)
            pMtf->Pause(false);
 
        bNoStretching = (aSize1 == aSize2);
 
#ifdef _WIN32
        // Windows zooms the font proportionally when using Size(100,500),
        // we don't like that.
        if(aSize2.Height() >= aSize1.Height() * 2)
        {
            bNoStretching = true;
        }
#endif
    }
 
    rOutliner.setRoundFontSizeToPt(false);
 
    unsigned nLoopCount=0;
    bool bNoMoreLoop = false;
    tools::Long nXDiff0=0x7FFFFFFF;
    tools::Long nWantWdt=rShapeSize.Width();
    tools::Long nIsWdt=rTextSize.Width();
    if (nIsWdt==0) nIsWdt=1;
 
    tools::Long nWantHgt=rShapeSize.Height();
    tools::Long nIsHgt=rTextSize.Height();
    if (nIsHgt==0) nIsHgt=1;
 
    tools::Long nXTolPl=nWantWdt/100; // tolerance: +1%
    tools::Long nXTolMi=nWantWdt/25;  // tolerance: -4%
    tools::Long nXCorr =nWantWdt/20;  // correction scale: 5%
 
    double nX = nWantWdt / double(nIsWdt); // calculate X stretching
    double nY = nWantHgt / double(nIsHgt); // calculate Y stretching
    bool bChkX = true;
    if (bNoStretching)
    { // might only be possible proportionally
        if (nX > nY)
        {
            nX = nY;
            bChkX = false;
        }
        else
        {
            nY = nX;
        }
    }
 
    while (nLoopCount<5 && !bNoMoreLoop)
    {
        if (nX < 0.0)
            nX = -nX;
        if (nX < 0.01)
        {
            nX = 0.01;
            bNoMoreLoop = true;
        }
        if (nX > 655.35)
        {
            nX = 655.35;
            bNoMoreLoop = true;
        }
 
        if (nY < 0.0)
        {
            nY = -nY;
        }
        if (nY < 0.01)
        {
            nY = 0.01;
            bNoMoreLoop = true;
        }
        if (nY > 655.35)
        {
            nY = 655.35;
            bNoMoreLoop = true;
        }
 
        // exception, there is no text yet (horizontal case)
        if (nIsWdt <= 1)
        {
            nX = nY;
            bNoMoreLoop = true;
        }
 
        // exception, there is no text yet (vertical case)
        if (nIsHgt <= 1)
        {
            nY = nX;
            bNoMoreLoop = true;
        }
        rOutliner.setScalingParameters({nX, nY});
        nLoopCount++;
        Size aSiz(rOutliner.CalcTextSize());
        tools::Long nXDiff = aSiz.Width() - nWantWdt;
        rFitXCorrection=Fraction(nWantWdt,aSiz.Width());
        if (((nXDiff>=nXTolMi || !bChkX) && nXDiff<=nXTolPl) || nXDiff==nXDiff0) {
            bNoMoreLoop = true;
        } else {
            // correct stretching factors
            tools::Long nMul = nWantWdt;
            tools::Long nDiv = aSiz.Width();
            if (std::abs(nXDiff) <= 2 * nXCorr)
            {
                if (nMul > nDiv)
                    nDiv += (nMul - nDiv) / 2.0; // but only add half of what we calculated,
                else
                    nMul += (nDiv - nMul) / 2.0;// because the EditEngine calculates wrongly later on
            }
            nX = nX * double(nMul) / double(nDiv);
            if (bNoStretching)
                nY = nX;
        }
        nXDiff0 = nXDiff;
    }
}
 
OUString SdrTextObj::TakeObjNameSingul() const
{
    OUString aStr;
 
    switch(meTextKind)
    {
        case SdrObjKind::OutlineText:
        {
            aStr = SvxResId(STR_ObjNameSingulOUTLINETEXT);
            break;
        }
 
        case SdrObjKind::TitleText  :
        {
            aStr = SvxResId(STR_ObjNameSingulTITLETEXT);
            break;
        }
 
        default:
        {
            if(IsLinkedText())
                aStr = SvxResId(STR_ObjNameSingulTEXTLNK);
            else
                aStr = SvxResId(STR_ObjNameSingulTEXT);
            break;
        }
    }
 
    OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject();
    if(pOutlinerParaObject && meTextKind != SdrObjKind::OutlineText)
    {
        // shouldn't currently cause any problems at OUTLINETEXT
        OUString aStr2(comphelper::string::stripStart(pOutlinerParaObject->GetTextObject().GetText(0), ' '));
 
        // avoid non expanded text portions in object name
        // (second condition is new)
        if(!aStr2.isEmpty() && aStr2.indexOf(u'\x00FF') == -1)
        {
            // space between ResStr and content text
            aStr += " \'";
 
            if(aStr2.getLength() > 10)
            {
                aStr2 = OUString::Concat(aStr2.subView(0, 8)) + "...";
            }
 
            aStr += aStr2 + "\'";
        }
    }
 
    OUString sName(aStr);
 
    OUString aName(GetName());
    if (!aName.isEmpty())
        sName += " '" + aName + "'";
 
    return sName;
}
 
OUString SdrTextObj::TakeObjNamePlural() const
{
    OUString sName;
    switch (meTextKind)
    {
        case SdrObjKind::OutlineText: sName=SvxResId(STR_ObjNamePluralOUTLINETEXT); break;
        case SdrObjKind::TitleText  : sName=SvxResId(STR_ObjNamePluralTITLETEXT);   break;
        default: {
            if (IsLinkedText()) {
                sName=SvxResId(STR_ObjNamePluralTEXTLNK);
            } else {
                sName=SvxResId(STR_ObjNamePluralTEXT);
            }
        } break;
    } // switch
    return sName;
}
 
rtl::Reference<SdrObject> SdrTextObj::CloneSdrObject(SdrModel& rTargetModel) const
{
    return new SdrTextObj(rTargetModel, *this);
}
 
basegfx::B2DPolyPolygon SdrTextObj::TakeXorPoly() const
{
    tools::Polygon aPol(getRectangle());
    if (maGeo.m_nShearAngle)
        ShearPoly(aPol, getRectangle().TopLeft(), maGeo.mfTanShearAngle);
    if (maGeo.m_nRotationAngle)
        RotatePoly(aPol, getRectangle().TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
 
    basegfx::B2DPolyPolygon aRetval;
    aRetval.append(aPol.getB2DPolygon());
    return aRetval;
}
 
basegfx::B2DPolyPolygon SdrTextObj::TakeContour() const
{
    basegfx::B2DPolyPolygon aRetval(SdrAttrObj::TakeContour());
 
    // and now add the BoundRect of the text, if necessary
    if ( GetOutlinerParaObject() && !IsFontwork() && !IsContourTextFrame() )
    {
        // using Clone()-Paint() strategy inside TakeContour() leaves a destroyed
        // SdrObject as pointer in DrawOutliner. Set *this again in fetching the outliner
        // in every case
        SdrOutliner& rOutliner=ImpGetDrawOutliner();
 
        tools::Rectangle aAnchor2;
        tools::Rectangle aR;
        TakeTextRect(rOutliner,aR,false,&aAnchor2);
        rOutliner.Clear();
        bool bFitToSize(IsFitToSize());
        if (bFitToSize) aR=aAnchor2;
        tools::Polygon aPol(aR);
        if (maGeo.m_nRotationAngle) RotatePoly(aPol,aR.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
 
        aRetval.append(aPol.getB2DPolygon());
    }
 
    return aRetval;
}
 
void SdrTextObj::RecalcSnapRect()
{
    if (maGeo.m_nRotationAngle || maGeo.m_nShearAngle)
    {
        maSnapRect = Rect2Poly(getRectangle(), maGeo).GetBoundRect();
    } else {
        maSnapRect = getRectangle();
    }
}
 
sal_uInt32 SdrTextObj::GetSnapPointCount() const
{
    return 4;
}
 
Point SdrTextObj::GetSnapPoint(sal_uInt32 i) const
{
    Point aP;
    const auto& rRectangle = getRectangle();
    switch (i) {
        case 0: aP = rRectangle.TopLeft(); break;
        case 1: aP = rRectangle.TopRight(); break;
        case 2: aP = rRectangle.BottomLeft(); break;
        case 3: aP = rRectangle.BottomRight(); break;
        default: aP = rRectangle.Center(); break;
    }
    if (maGeo.m_nShearAngle)
        ShearPoint(aP, rRectangle.TopLeft(), maGeo.mfTanShearAngle);
    if (maGeo.m_nRotationAngle)
        RotatePoint(aP, rRectangle.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
    return aP;
}
 
// Extracted from ImpGetDrawOutliner()
void SdrTextObj::ImpInitDrawOutliner( SdrOutliner& rOutl ) const
{
    rOutl.SetUpdateLayout(false);
    OutlinerMode nOutlinerMode = OutlinerMode::OutlineObject;
    if ( !IsOutlText() )
        nOutlinerMode = OutlinerMode::TextObject;
    rOutl.Init( nOutlinerMode );
 
    rOutl.resetScalingParameters();
 
    EEControlBits nStat=rOutl.GetControlWord();
    nStat &= ~EEControlBits(EEControlBits::STRETCHING|EEControlBits::AUTOPAGESIZE);
    rOutl.SetControlWord(nStat);
    Size aMaxSize(100000,100000);
    rOutl.SetMinAutoPaperSize(Size());
    rOutl.SetMaxAutoPaperSize(aMaxSize);
    rOutl.SetPaperSize(aMaxSize);
    rOutl.ClearPolygon();
}
 
SdrOutliner& SdrTextObj::ImpGetDrawOutliner() const
{
    SdrOutliner& rOutl(getSdrModelFromSdrObject().GetDrawOutliner(this));
 
    // Code extracted to ImpInitDrawOutliner()
    ImpInitDrawOutliner( rOutl );
 
    return rOutl;
}
 
// Extracted from Paint()
void SdrTextObj::ImpSetupDrawOutlinerForPaint( bool             bContourFrame,
                                               SdrOutliner&     rOutliner,
                                               tools::Rectangle&       rTextRect,
                                               tools::Rectangle&       rAnchorRect,
                                               tools::Rectangle&       rPaintRect,
                                               Fraction&        rFitXCorrection ) const
{
    if (!bContourFrame)
    {
        // FitToSize can't be used together with ContourFrame for now
        if (IsFitToSize() || IsAutoFit())
        {
            EEControlBits nStat=rOutliner.GetControlWord();
            nStat|=EEControlBits::STRETCHING|EEControlBits::AUTOPAGESIZE;
            rOutliner.SetControlWord(nStat);
        }
    }
    rOutliner.SetFixedCellHeight(GetMergedItem(SDRATTR_TEXT_USEFIXEDCELLHEIGHT).GetValue());
    TakeTextRect(rOutliner, rTextRect, false, &rAnchorRect);
    rPaintRect = rTextRect;
 
    if (bContourFrame)
        return;
 
    // FitToSize can't be used together with ContourFrame for now
    if (IsFitToSize())
    {
        ImpSetCharStretching(rOutliner,rTextRect.GetSize(),rAnchorRect.GetSize(),rFitXCorrection);
        rPaintRect=rAnchorRect;
    }
    else if (IsAutoFit())
    {
        setupAutoFitText(rOutliner);
    }
}
 
double SdrTextObj::GetFontScale() const
{
    SdrOutliner& rOutliner = ImpGetDrawOutliner();
    // This eventually calls setupAutoFitText
    UpdateOutlinerFormatting(rOutliner, o3tl::temporary(tools::Rectangle()));
 
    return rOutliner.getScalingParameters().fFontY;
}
 
double SdrTextObj::GetSpacingScale() const
{
    SdrOutliner& rOutliner = ImpGetDrawOutliner();
    // This eventually calls setupAutoFitText
    UpdateOutlinerFormatting(rOutliner, o3tl::temporary(tools::Rectangle()));
 
    return rOutliner.getScalingParameters().fSpacingY;
}
 
void SdrTextObj::setupAutoFitText(SdrOutliner& rOutliner) const
{
    const Size aShapeSize = GetSnapRect().GetSize();
    Size aSize(aShapeSize.Width() - GetTextLeftDistance() - GetTextRightDistance(),
               aShapeSize.Height() - GetTextUpperDistance() - GetTextLowerDistance());
 
    setupAutoFitText(rOutliner, aSize);
}
void SdrTextObj::setupAutoFitText(SdrOutliner& rOutliner, const Size& rTextBoxSize) const
{
    rOutliner.setRoundFontSizeToPt(true); // We need to round the font size nearest integer pt size
    rOutliner.SetMaxAutoPaperSize(rTextBoxSize);
    rOutliner.SetPaperSize(rTextBoxSize);
 
    const SdrTextFitToSizeTypeItem& rItem = GetObjectItem(SDRATTR_TEXT_FITTOSIZE);
 
    double fFontScale = rItem.getFontScale();
    double fSpacingScale = rItem.getSpacingScale();
 
    if (fFontScale > 0.0 && fSpacingScale > 0.0 && !mbInEditMode)
    {
        rOutliner.setScalingParameters({ fFontScale, fFontScale, 1.0, fSpacingScale });
    }
    else
    {
        rOutliner.resetScalingParameters();
    }
 
    rOutliner.QuickFormatDoc();
}
 
void SdrTextObj::SetupOutlinerFormatting( SdrOutliner& rOutl, tools::Rectangle& rPaintRect ) const
{
    ImpInitDrawOutliner( rOutl );
    UpdateOutlinerFormatting( rOutl, rPaintRect );
}
 
void SdrTextObj::UpdateOutlinerFormatting( SdrOutliner& rOutl, tools::Rectangle& rPaintRect ) const
{
    tools::Rectangle aTextRect;
    tools::Rectangle aAnchorRect;
    Fraction aFitXCorrection(1,1);
 
    const bool bContourFrame(IsContourTextFrame());
    const MapMode aMapMode(getSdrModelFromSdrObject().GetScaleUnit());
 
    rOutl.SetRefMapMode(aMapMode);
    ImpSetupDrawOutlinerForPaint(
        bContourFrame,
        rOutl,
        aTextRect,
        aAnchorRect,
        rPaintRect,
        aFitXCorrection);
}
 
 
OutlinerParaObject* SdrTextObj::GetOutlinerParaObject() const
{
    SdrText* pText = getActiveText();
    if( pText )
        return pText->GetOutlinerParaObject();
    else
        return nullptr;
}
 
void SdrTextObj::NbcSetOutlinerParaObject(std::optional<OutlinerParaObject> pTextObject, bool bAdjustTextFrameWidthAndHeight)
{
    NbcSetOutlinerParaObjectForText( std::move(pTextObject), getActiveText(), bAdjustTextFrameWidthAndHeight );
}
 
namespace
{
    bool IsAutoGrow(const SdrTextObj& rObj)
    {
        bool bAutoGrow = rObj.IsAutoGrowHeight() || rObj.IsAutoGrowWidth();
        return bAutoGrow && !comphelper::IsFuzzing();
    }
}
 
void SdrTextObj::NbcSetOutlinerParaObjectForText( std::optional<OutlinerParaObject> pTextObject, SdrText* pText, bool bAdjustTextFrameWidthAndHeight )
{
    if( pText )
        pText->SetOutlinerParaObject( std::move(pTextObject) );
 
    if (pText && pText->GetOutlinerParaObject())
    {
        SvxWritingModeItem aWritingMode(pText->GetOutlinerParaObject()->IsEffectivelyVertical() && pText->GetOutlinerParaObject()->IsTopToBottom()
            ? css::text::WritingMode_TB_RL
            : css::text::WritingMode_LR_TB,
            SDRATTR_TEXTDIRECTION);
        GetProperties().SetObjectItemDirect(aWritingMode);
    }
 
    SetTextSizeDirty();
    if (IsTextFrame() && IsAutoGrow(*this) && bAdjustTextFrameWidthAndHeight)
    { // adapt text frame!
        NbcAdjustTextFrameWidthAndHeight();
    }
    if (!IsTextFrame())
    {
        // the SnapRect keeps its size
        SetBoundAndSnapRectsDirty(true);
    }
 
    // always invalidate BoundRect on change
    SetBoundRectDirty();
    ActionChanged();
 
    ImpSetTextStyleSheetListeners();
}
 
void SdrTextObj::NbcReformatText()
{
    SdrText* pText = getActiveText();
    if( !(pText && pText->GetOutlinerParaObject()) )
        return;
 
    pText->ReformatText();
    if (mbTextFrame)
    {
        NbcAdjustTextFrameWidthAndHeight();
    }
    else
    {
        // the SnapRect keeps its size
        SetBoundRectDirty();
        SetBoundAndSnapRectsDirty(/*bNotMyself*/true);
    }
    SetTextSizeDirty();
    ActionChanged();
    // i22396
    // Necessary here since we have no compare operator at the outliner
    // para object which may detect changes regarding the combination
    // of outliner para data and configuration (e.g., change of
    // formatting of text numerals)
    GetViewContact().flushViewObjectContacts(false);
}
 
std::unique_ptr<SdrObjGeoData> SdrTextObj::NewGeoData() const
{
    return std::make_unique<SdrTextObjGeoData>();
}
 
void SdrTextObj::SaveGeoData(SdrObjGeoData& rGeo) const
{
    SdrAttrObj::SaveGeoData(rGeo);
    SdrTextObjGeoData& rTGeo=static_cast<SdrTextObjGeoData&>(rGeo);
    rTGeo.maRect = getRectangle();
    rTGeo.maGeo = maGeo;
}
 
void SdrTextObj::RestoreGeoData(const SdrObjGeoData& rGeo)
{ // RectsDirty is called by SdrObject
    SdrAttrObj::RestoreGeoData(rGeo);
    const SdrTextObjGeoData& rTGeo=static_cast<const SdrTextObjGeoData&>(rGeo);
    NbcSetLogicRect(rTGeo.maRect);
    maGeo = rTGeo.maGeo;
    SetTextSizeDirty();
}
 
drawing::TextFitToSizeType SdrTextObj::GetFitToSize() const
{
    drawing::TextFitToSizeType eType = drawing::TextFitToSizeType_NONE;
 
    if(!IsAutoGrowWidth())
        eType = GetObjectItem(SDRATTR_TEXT_FITTOSIZE).GetValue();
 
    return eType;
}
 
const tools::Rectangle& SdrTextObj::GetGeoRect() const
{
    return getRectangle();
}
 
void SdrTextObj::ForceOutlinerParaObject()
{
    SdrText* pText = getActiveText();
    if( pText && (pText->GetOutlinerParaObject() == nullptr) )
    {
        OutlinerMode nOutlMode = OutlinerMode::TextObject;
        if( IsTextFrame() && meTextKind == SdrObjKind::OutlineText )
            nOutlMode = OutlinerMode::OutlineObject;
 
        pText->ForceOutlinerParaObject( nOutlMode );
    }
}
 
TextChain *SdrTextObj::GetTextChain() const
{
    //if (!IsChainable())
    //    return NULL;
 
    return getSdrModelFromSdrObject().GetTextChain();
}
 
bool SdrTextObj::IsVerticalWriting() const
{
    if(mpEditingOutliner)
    {
        return mpEditingOutliner->IsVertical();
    }
 
    OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject();
    if(pOutlinerParaObject)
    {
        return pOutlinerParaObject->IsEffectivelyVertical();
    }
 
    return false;
}
 
void SdrTextObj::SetVerticalWriting(bool bVertical)
{
    OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject();
 
    if( !pOutlinerParaObject && bVertical )
    {
        // we only need to force an outliner para object if the default of
        // horizontal text is changed
        ForceOutlinerParaObject();
        pOutlinerParaObject = GetOutlinerParaObject();
    }
 
    if (!pOutlinerParaObject ||
        (pOutlinerParaObject->IsEffectivelyVertical() == bVertical))
        return;
 
    // get item settings
    const SfxItemSet& rSet = GetObjectItemSet();
    bool bAutoGrowWidth = rSet.Get(SDRATTR_TEXT_AUTOGROWWIDTH).GetValue();
    bool bAutoGrowHeight = rSet.Get(SDRATTR_TEXT_AUTOGROWHEIGHT).GetValue();
 
    // Also exchange hor/ver adjust items
    SdrTextHorzAdjust eHorz = rSet.Get(SDRATTR_TEXT_HORZADJUST).GetValue();
    SdrTextVertAdjust eVert = rSet.Get(SDRATTR_TEXT_VERTADJUST).GetValue();
 
    // rescue object size
    tools::Rectangle aObjectRect = GetSnapRect();
 
    // prepare ItemSet to set exchanged width and height items
    SfxItemSetFixed<SDRATTR_TEXT_AUTOGROWHEIGHT, SDRATTR_TEXT_AUTOGROWHEIGHT,
        // Expanded item ranges to also support hor and ver adjust.
        SDRATTR_TEXT_VERTADJUST, SDRATTR_TEXT_VERTADJUST,
        SDRATTR_TEXT_AUTOGROWWIDTH, SDRATTR_TEXT_HORZADJUST> aNewSet(*rSet.GetPool());
 
    aNewSet.Put(rSet);
    aNewSet.Put(makeSdrTextAutoGrowWidthItem(bAutoGrowHeight));
    aNewSet.Put(makeSdrTextAutoGrowHeightItem(bAutoGrowWidth));
 
    // Exchange horz and vert adjusts
    switch (eVert)
    {
        case SDRTEXTVERTADJUST_TOP: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_RIGHT)); break;
        case SDRTEXTVERTADJUST_CENTER: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_CENTER)); break;
        case SDRTEXTVERTADJUST_BOTTOM: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_LEFT)); break;
        case SDRTEXTVERTADJUST_BLOCK: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_BLOCK)); break;
    }
    switch (eHorz)
    {
        case SDRTEXTHORZADJUST_LEFT: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_BOTTOM)); break;
        case SDRTEXTHORZADJUST_CENTER: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_CENTER)); break;
        case SDRTEXTHORZADJUST_RIGHT: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_TOP)); break;
        case SDRTEXTHORZADJUST_BLOCK: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_BLOCK)); break;
    }
 
    SetObjectItemSet(aNewSet);
 
    pOutlinerParaObject = GetOutlinerParaObject();
    if (pOutlinerParaObject)
    {
        // set ParaObject orientation accordingly
        pOutlinerParaObject->SetVertical(bVertical);
    }
 
    // restore object size
    SetSnapRect(aObjectRect);
}
 
bool SdrTextObj::IsTopToBottom() const
{
    if (mpEditingOutliner)
        return mpEditingOutliner->IsTopToBottom();
 
    if (OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject())
        return pOutlinerParaObject->IsTopToBottom();
 
    return false;
}
 
// transformation interface for StarOfficeAPI. This implements support for
// homogeneous 3x3 matrices containing the transformation of the SdrObject. At the
// moment it contains a shearX, rotation and translation, but for setting all linear
// transforms like Scale, ShearX, ShearY, Rotate and Translate are supported.
 
 
// gets base transformation and rectangle of object. If it's an SdrPathObj it fills the PolyPolygon
// with the base geometry and returns TRUE. Otherwise it returns FALSE.
bool SdrTextObj::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& /*rPolyPolygon*/) const
{
    // get turn and shear
    double fRotate = toRadians(maGeo.m_nRotationAngle);
    double fShearX = toRadians(maGeo.m_nShearAngle);
 
    // get aRect, this is the unrotated snaprect
    tools::Rectangle aRectangle(getRectangle());
 
    // fill other values
    basegfx::B2DTuple aScale(aRectangle.GetWidth(), aRectangle.GetHeight());
    basegfx::B2DTuple aTranslate(aRectangle.Left(), aRectangle.Top());
 
    // position maybe relative to anchorpos, convert
    if( getSdrModelFromSdrObject().IsWriter() )
    {
        if(GetAnchorPos().X() || GetAnchorPos().Y())
        {
            aTranslate -= basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
        }
    }
 
    // build matrix
    rMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
        aScale,
        basegfx::fTools::equalZero(fShearX) ? 0.0 : tan(fShearX),
        basegfx::fTools::equalZero(fRotate) ? 0.0 : -fRotate,
        aTranslate);
 
    return false;
}
 
// sets the base geometry of the object using infos contained in the homogeneous 3x3 matrix.
// If it's an SdrPathObj it will use the provided geometry information. The Polygon has
// to use (0,0) as upper left and will be scaled to the given size in the matrix.
void SdrTextObj::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& /*rPolyPolygon*/)
{
    // break up matrix
    basegfx::B2DTuple aScale;
    basegfx::B2DTuple aTranslate;
    double fRotate(0.0);
    double fShearX(0.0);
    rMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
 
    // flip?
    bool bFlipX = aScale.getX() < 0.0,
         bFlipY = aScale.getY() < 0.0;
    if (bFlipX)
    {
        aScale.setX(fabs(aScale.getX()));
    }
    if (bFlipY)
    {
        aScale.setY(fabs(aScale.getY()));
    }
 
    // reset object shear and rotations
    maGeo.m_nRotationAngle = 0_deg100;
    maGeo.RecalcSinCos();
    maGeo.m_nShearAngle = 0_deg100;
    maGeo.RecalcTan();
 
    // if anchor is used, make position relative to it
    if( getSdrModelFromSdrObject().IsWriter() )
    {
        if(GetAnchorPos().X() || GetAnchorPos().Y())
        {
            aTranslate += basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
        }
    }
 
    // build and set BaseRect (use scale)
    Size aSize(basegfx::fround<tools::Long>(aScale.getX()),
               basegfx::fround<tools::Long>(aScale.getY()));
    tools::Rectangle aBaseRect(Point(), aSize);
    SetSnapRect(aBaseRect);
 
    // flip?
    if (bFlipX)
    {
        Mirror(Point(), Point(0, 1));
    }
    if (bFlipY)
    {
        Mirror(Point(), Point(1, 0));
    }
 
    // shear?
    if(!basegfx::fTools::equalZero(fShearX))
    {
        GeoStat aGeoStat;
        aGeoStat.m_nShearAngle = Degree100(basegfx::fround(basegfx::rad2deg<100>(atan(fShearX))));
        aGeoStat.RecalcTan();
        Shear(Point(), aGeoStat.m_nShearAngle, aGeoStat.mfTanShearAngle, false);
    }
 
    // rotation?
    if(!basegfx::fTools::equalZero(fRotate))
    {
        GeoStat aGeoStat;
 
        // #i78696#
        // fRotate is matematically correct, but aGeoStat.nRotationAngle is
        // mirrored -> mirror value here
        aGeoStat.m_nRotationAngle = NormAngle36000(Degree100(basegfx::fround(-basegfx::rad2deg<100>(fRotate))));
        aGeoStat.RecalcSinCos();
        Rotate(Point(), aGeoStat.m_nRotationAngle, aGeoStat.mfSinRotationAngle, aGeoStat.mfCosRotationAngle);
    }
 
    // translate?
    if(!aTranslate.equalZero())
    {
        Move(Size(basegfx::fround<tools::Long>(aTranslate.getX()),
                  basegfx::fround<tools::Long>(aTranslate.getY())));
    }
}
 
bool SdrTextObj::IsReallyEdited() const
{
    return mpEditingOutliner && mpEditingOutliner->IsModified();
}
 
// moved inlines here form hxx
 
tools::Long SdrTextObj::GetEckenradius() const
{
    return GetObjectItemSet().Get(SDRATTR_CORNER_RADIUS).GetValue();
}
 
tools::Long SdrTextObj::GetMinTextFrameHeight() const
{
    return GetObjectItemSet().Get(SDRATTR_TEXT_MINFRAMEHEIGHT).GetValue();
}
 
tools::Long SdrTextObj::GetMaxTextFrameHeight() const
{
    return GetObjectItemSet().Get(SDRATTR_TEXT_MAXFRAMEHEIGHT).GetValue();
}
 
tools::Long SdrTextObj::GetMinTextFrameWidth() const
{
    return GetObjectItemSet().Get(SDRATTR_TEXT_MINFRAMEWIDTH).GetValue();
}
 
tools::Long SdrTextObj::GetMaxTextFrameWidth() const
{
    return GetObjectItemSet().Get(SDRATTR_TEXT_MAXFRAMEWIDTH).GetValue();
}
 
bool SdrTextObj::IsFontwork() const
{
    return !mbTextFrame // Default is FALSE
        && GetObjectItemSet().Get(XATTR_FORMTXTSTYLE).GetValue() != XFormTextStyle::NONE;
}
 
bool SdrTextObj::IsHideContour() const
{
    return !mbTextFrame // Default is: no, don't HideContour; HideContour not together with TextFrames
        && GetObjectItemSet().Get(XATTR_FORMTXTHIDEFORM).GetValue();
}
 
bool SdrTextObj::IsContourTextFrame() const
{
    return !mbTextFrame // ContourFrame not together with normal TextFrames
        && GetObjectItemSet().Get(SDRATTR_TEXT_CONTOURFRAME).GetValue();
}
 
tools::Long SdrTextObj::GetTextLeftDistance() const
{
    return GetObjectItemSet().Get(SDRATTR_TEXT_LEFTDIST).GetValue();
}
 
tools::Long SdrTextObj::GetTextRightDistance() const
{
    return GetObjectItemSet().Get(SDRATTR_TEXT_RIGHTDIST).GetValue();
}
 
tools::Long SdrTextObj::GetTextUpperDistance() const
{
    return GetObjectItemSet().Get(SDRATTR_TEXT_UPPERDIST).GetValue();
}
 
tools::Long SdrTextObj::GetTextLowerDistance() const
{
    return GetObjectItemSet().Get(SDRATTR_TEXT_LOWERDIST).GetValue();
}
 
SdrTextAniKind SdrTextObj::GetTextAniKind() const
{
    return GetObjectItemSet().Get(SDRATTR_TEXT_ANIKIND).GetValue();
}
 
SdrTextAniDirection SdrTextObj::GetTextAniDirection() const
{
    return GetObjectItemSet().Get(SDRATTR_TEXT_ANIDIRECTION).GetValue();
}
 
bool SdrTextObj::HasTextColumnsNumber() const
{
    return GetObjectItemSet().HasItem(SDRATTR_TEXTCOLUMNS_NUMBER);
}
 
sal_Int16 SdrTextObj::GetTextColumnsNumber() const
{
    return GetObjectItemSet().Get(SDRATTR_TEXTCOLUMNS_NUMBER).GetValue();
}
 
void SdrTextObj::SetTextColumnsNumber(sal_Int16 nColumns)
{
    SetObjectItem(SfxInt16Item(SDRATTR_TEXTCOLUMNS_NUMBER, nColumns));
}
 
bool SdrTextObj::HasTextColumnsSpacing() const
{
    return GetObjectItemSet().HasItem(SDRATTR_TEXTCOLUMNS_SPACING);
}
 
sal_Int32 SdrTextObj::GetTextColumnsSpacing() const
{
    return GetObjectItemSet().Get(SDRATTR_TEXTCOLUMNS_SPACING).GetValue();
}
 
void SdrTextObj::SetTextColumnsSpacing(sal_Int32 nSpacing)
{
    SetObjectItem(SdrMetricItem(SDRATTR_TEXTCOLUMNS_SPACING, nSpacing));
}
 
// Get necessary data for text scroll animation. ATM base it on a Text-Metafile and a
// painting rectangle. Rotation is excluded from the returned values.
GDIMetaFile* SdrTextObj::GetTextScrollMetaFileAndRectangle(
    tools::Rectangle& rScrollRectangle, tools::Rectangle& rPaintRectangle)
{
    GDIMetaFile* pRetval = nullptr;
    SdrOutliner& rOutliner = ImpGetDrawOutliner();
    tools::Rectangle aTextRect;
    tools::Rectangle aAnchorRect;
    tools::Rectangle aPaintRect;
    Fraction aFitXCorrection(1,1);
    bool bContourFrame(IsContourTextFrame());
 
    // get outliner set up. To avoid getting a somehow rotated MetaFile,
    // temporarily disable object rotation.
    Degree100 nAngle(maGeo.m_nRotationAngle);
    maGeo.m_nRotationAngle = 0_deg100;
    ImpSetupDrawOutlinerForPaint( bContourFrame, rOutliner, aTextRect, aAnchorRect, aPaintRect, aFitXCorrection );
    maGeo.m_nRotationAngle = nAngle;
 
    tools::Rectangle aScrollFrameRect(aPaintRect);
    const SfxItemSet& rSet = GetObjectItemSet();
    SdrTextAniDirection eDirection = rSet.Get(SDRATTR_TEXT_ANIDIRECTION).GetValue();
 
    if(SdrTextAniDirection::Left == eDirection || SdrTextAniDirection::Right == eDirection)
    {
        aScrollFrameRect.SetLeft( aAnchorRect.Left() );
        aScrollFrameRect.SetRight( aAnchorRect.Right() );
    }
 
    if(SdrTextAniDirection::Up == eDirection || SdrTextAniDirection::Down == eDirection)
    {
        aScrollFrameRect.SetTop( aAnchorRect.Top() );
        aScrollFrameRect.SetBottom( aAnchorRect.Bottom() );
    }
 
    // create the MetaFile
    pRetval = new GDIMetaFile;
    ScopedVclPtrInstance< VirtualDevice > pBlackHole;
    pBlackHole->EnableOutput(false);
    pRetval->Record(pBlackHole);
    Point aPaintPos = aPaintRect.TopLeft();
 
    rOutliner.Draw(*pBlackHole, aPaintPos);
 
    pRetval->Stop();
    pRetval->WindStart();
 
    // return PaintRectanglePixel and pRetval;
    rScrollRectangle = aScrollFrameRect;
    rPaintRectangle = aPaintRect;
 
    return pRetval;
}
 
// Access to TextAnimationAllowed flag
bool SdrTextObj::IsAutoFit() const
{
    return GetFitToSize() == drawing::TextFitToSizeType_AUTOFIT;
}
 
bool SdrTextObj::IsFitToSize() const
{
    const drawing::TextFitToSizeType eFit = GetFitToSize();
    return (eFit == drawing::TextFitToSizeType_PROPORTIONAL
         || eFit == drawing::TextFitToSizeType_ALLLINES);
}
 
void SdrTextObj::SetTextAnimationAllowed(bool bNew)
{
    if(mbTextAnimationAllowed != bNew)
    {
        mbTextAnimationAllowed = bNew;
        ActionChanged();
    }
}
 
/** called from the SdrObjEditView during text edit when the status of the edit outliner changes */
void SdrTextObj::onEditOutlinerStatusEvent( EditStatus* pEditStatus )
{
    const EditStatusFlags nStat = pEditStatus->GetStatusWord();
    const bool bGrowX = bool(nStat & EditStatusFlags::TEXTWIDTHCHANGED);
    const bool bGrowY = bool(nStat & EditStatusFlags::TextHeightChanged);
    if(!(mbTextFrame && (bGrowX || bGrowY)))
        return;
 
    if ((bGrowX && IsAutoGrowWidth()) || (bGrowY && IsAutoGrowHeight()))
    {
        AdjustTextFrameWidthAndHeight();
    }
    else if ( (IsAutoFit() || IsFitToSize()) && !mbInDownScale)
    {
        assert(mpEditingOutliner);
        mbInDownScale = true;
 
        // Need to reset scaling so it searches for the fitting size again
        mpEditingOutliner->resetScalingParameters();
 
        // sucks that we cannot disable paints via
        // mpEditingOutliner->SetUpdateMode(FALSE) - but EditEngine skips
        // formatting as well, then.
        setupAutoFitText(*mpEditingOutliner);
        mbInDownScale = false;
    }
}
 
/* Begin chaining code */
 
// XXX: Make it a method somewhere?
static SdrObject *ImpGetObjByName(SdrObjList const *pObjList, std::u16string_view aObjName)
{
    // scan the whole list
    for (const rtl::Reference<SdrObject>& pCurObj : *pObjList)
        if (pCurObj->GetName() == aObjName)
            return pCurObj.get();
    // not found
    return nullptr;
}
 
// XXX: Make it a (private) method of SdrTextObj
static void ImpUpdateChainLinks(SdrTextObj *pTextObj, std::u16string_view aNextLinkName)
{
    // XXX: Current implementation constraints text boxes to be on the same page
 
    // No next link
    if (aNextLinkName.empty()) {
        pTextObj->SetNextLinkInChain(nullptr);
        return;
    }
 
    SdrPage *pPage(pTextObj->getSdrPageFromSdrObject());
    assert(pPage);
    SdrTextObj *pNextTextObj = DynCastSdrTextObj
                                (ImpGetObjByName(pPage, aNextLinkName));
    if (!pNextTextObj) {
        SAL_INFO("svx.chaining", "[CHAINING] Can't find object as next link.");
        return;
    }
 
    pTextObj->SetNextLinkInChain(pNextTextObj);
}
 
bool SdrTextObj::IsChainable() const
{
    // Read it as item
    const SfxItemSet& rSet = GetObjectItemSet();
    OUString aNextLinkName = rSet.Get(SDRATTR_TEXT_CHAINNEXTNAME).GetValue();
 
    // Update links if any inconsistency is found
    bool bNextLinkUnsetYet = !aNextLinkName.isEmpty() && !mpNextInChain;
    bool bInconsistentNextLink = mpNextInChain && mpNextInChain->GetName() != aNextLinkName;
    // if the link is not set despite there should be one OR if it has changed
    if (bNextLinkUnsetYet || bInconsistentNextLink) {
        ImpUpdateChainLinks(const_cast<SdrTextObj *>(this), aNextLinkName);
    }
 
    return !aNextLinkName.isEmpty(); // XXX: Should we also check for GetNilChainingEvent? (see old code below)
 
/*
    // Check that no overflow is going on
    if (!GetTextChain() || GetTextChain()->GetNilChainingEvent(this))
        return false;
*/
}
 
void SdrTextObj::onChainingEvent()
{
    if (!mpEditingOutliner)
        return;
 
    // Outliner for text transfer
    SdrOutliner &aDrawOutliner = ImpGetDrawOutliner();
 
    EditingTextChainFlow aTxtChainFlow(this);
    aTxtChainFlow.CheckForFlowEvents(mpEditingOutliner);
 
    if (aTxtChainFlow.IsOverflow()) {
        SAL_INFO("svx.chaining", "[CHAINING] Overflow going on");
        // One outliner is for non-overflowing text, the other for overflowing text
        // We remove text directly from the editing outliner
        aTxtChainFlow.ExecuteOverflow(mpEditingOutliner, &aDrawOutliner);
    } else if (aTxtChainFlow.IsUnderflow()) {
        SAL_INFO("svx.chaining", "[CHAINING] Underflow going on");
        // underflow-induced overflow
        aTxtChainFlow.ExecuteUnderflow(&aDrawOutliner);
        bool bIsOverflowFromUnderflow = aTxtChainFlow.IsOverflow();
        // handle overflow
        if (bIsOverflowFromUnderflow) {
            SAL_INFO("svx.chaining", "[CHAINING] Overflow going on (underflow induced)");
            // prevents infinite loops when setting text for editing outliner
            aTxtChainFlow.ExecuteOverflow(&aDrawOutliner, &aDrawOutliner);
        }
    }
}
 
SdrTextObj* SdrTextObj::GetNextLinkInChain() const
{
    /*
    if (GetTextChain())
        return GetTextChain()->GetNextLink(this);
 
    return NULL;
    */
 
    return mpNextInChain;
}
 
void SdrTextObj::SetNextLinkInChain(SdrTextObj *pNextObj)
{
    // Basically a doubly linked list implementation
 
    SdrTextObj *pOldNextObj = mpNextInChain;
 
    // Replace next link
    mpNextInChain = pNextObj;
    // Deal with old next link's prev link
    if (pOldNextObj) {
        pOldNextObj->mpPrevInChain = nullptr;
    }
 
    // Deal with new next link's prev link
    if (mpNextInChain) {
        // If there is a prev already at all and this is not already the current object
        if (mpNextInChain->mpPrevInChain &&
            mpNextInChain->mpPrevInChain != this)
            mpNextInChain->mpPrevInChain->mpNextInChain = nullptr;
        mpNextInChain->mpPrevInChain = this;
    }
 
    // TODO: Introduce check for circular chains
 
}
 
SdrTextObj* SdrTextObj::GetPrevLinkInChain() const
{
    /*
    if (GetTextChain())
        return GetTextChain()->GetPrevLink(this);
 
    return NULL;
    */
 
    return mpPrevInChain;
}
 
bool SdrTextObj::GetPreventChainable() const
{
    // Prevent chaining it 1) during dragging && 2) when we are editing next link
    return mbIsUnchainableClone || (GetNextLinkInChain() && GetNextLinkInChain()->IsInEditMode());
}
 
rtl::Reference<SdrObject> SdrTextObj::getFullDragClone() const
{
    rtl::Reference<SdrObject> pClone = SdrAttrObj::getFullDragClone();
    SdrTextObj *pTextObjClone = DynCastSdrTextObj(pClone.get());
    if (pTextObjClone != nullptr) {
        // Avoid transferring of text for chainable object during dragging
        pTextObjClone->mbIsUnchainableClone = true;
    }
 
    return pClone;
 }
 
/* End chaining code */
 
/** returns the currently active text. */
SdrText* SdrTextObj::getActiveText() const
{
    if( !mxText )
        return getText( 0 );
    else
        return mxText.get();
}
 
/** returns the nth available text. */
SdrText* SdrTextObj::getText( sal_Int32 nIndex ) const
{
    if( nIndex == 0 )
    {
        if( !mxText )
            const_cast< SdrTextObj* >(this)->mxText = new SdrText( *const_cast< SdrTextObj* >(this) );
        return mxText.get();
    }
    else
    {
        return nullptr;
    }
}
 
/** returns the number of texts available for this object. */
sal_Int32 SdrTextObj::getTextCount() const
{
    return 1;
}
 
/** changes the current active text */
void SdrTextObj::setActiveText( sal_Int32 /*nIndex*/ )
{
}
 
/** returns the index of the text that contains the given point or -1 */
sal_Int32 SdrTextObj::CheckTextHit(const Point& /*rPnt*/) const
{
    return 0;
}
 
void SdrTextObj::SetObjectItemNoBroadcast(const SfxPoolItem& rItem)
{
    static_cast< sdr::properties::TextProperties& >(GetProperties()).SetObjectItemNoBroadcast(rItem);
}
 
 
// The concept of the text object:
// ~~~~~~~~~~~~~~~~~~~~~~~~
// Attributes/Variations:
// - bool text frame / graphics object with caption
// - bool FontWork                 (if it is not a text frame and not a ContourTextFrame)
// - bool ContourTextFrame         (if it is not a text frame and not Fontwork)
// - long rotation angle               (if it is not FontWork)
// - long text frame margins           (if it is not FontWork)
// - bool FitToSize                (if it is not FontWork)
// - bool AutoGrowingWidth/Height  (if it is not FitToSize and not FontWork)
// - long Min/MaxFrameWidth/Height     (if AutoGrowingWidth/Height)
// - enum horizontal text anchoring left,center,right,justify/block,Stretch(ni)
// - enum vertical text anchoring top, middle, bottom, block, stretch(ni)
// - enum ticker text                  (if it is not FontWork)
 
// Every derived object is either a text frame (mbTextFrame=true)
// or a drawing object with a caption (mbTextFrame=false).
 
// Default anchoring for text frames:
//   SDRTEXTHORZADJUST_BLOCK, SDRTEXTVERTADJUST_TOP
//   = static Pool defaults
// Default anchoring for drawing objects with a caption:
//   SDRTEXTHORZADJUST_CENTER, SDRTEXTVERTADJUST_CENTER
//   via "hard" attribution of SdrAttrObj
 
// Every object derived from SdrTextObj must return an "UnrotatedSnapRect"
// (->TakeUnrotatedSnapRect()) (the reference point for the rotation is the top
// left of the rectangle (maGeo.nRotationAngle)) which is the basis for anchoring
// text. We then subtract the text frame margins from this rectangle, as a re-
// sult we get the anchoring area (->TakeTextAnchorRect()). Within this area, we
// calculate the anchoring point and the painting area, depending on the hori-
// zontal and vertical adjustment of the text (SdrTextVertAdjust,
// SdrTextHorzAdjust).
// In the case of drawing objects with a caption the painting area might well
// be larger than the anchoring area, for text frames on the other hand, it is
// always of the same or a smaller size (except when there are negative text
// frame margins).
 
// FitToSize takes priority over text anchoring and AutoGrowHeight/Width. When
// FitToSize is turned on, the painting area is always equal to the anchoring
// area. Additionally, FitToSize doesn't allow automatic line breaks.
 
// ContourTextFrame:
// - long rotation angle
// - long text frame margins (maybe later)
// - bool FitToSize (maybe later)
// - bool AutoGrowingWidth/Height (maybe much later)
// - long Min/MaxFrameWidth/Height (maybe much later)
// - enum horizontal text anchoring (maybe later, for now: left, centered)
// - enum vertical text anchoring (maybe later, for now: top)
// - enum ticker text (maybe later, maybe even with correct clipping)
 
// When making changes, check these:
// - Paint
// - HitTest
// - ConvertToPoly
// - Edit
// - Printing, Saving, Painting in neighboring View while editing
// - ModelChanged (e. g. through a neighboring View or rulers) while editing
// - FillColorChanged while editing
// - and many more...
 
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'Read' is required to be utilized.

V1053 Calling the 'getActiveText' virtual function in the constructor may lead to unexpected result at runtime.

V1048 The 'mbDisableAutoWidthOnDragging' variable was assigned the same value.