/* -*- 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 <dcontact.hxx>
#include <dflyobj.hxx>
#include <pam.hxx>
#include "portab.hxx"
#include <flyfrm.hxx>
#include <rootfrm.hxx>
#include <frmfmt.hxx>
#include <viewsh.hxx>
#include <textboxhelper.hxx>
 
#include <vcl/outdev.hxx>
#include <editeng/lrspitem.hxx>
#include <editeng/ulspitem.hxx>
#include <sal/log.hxx>
#include <fmtanchr.hxx>
#include <fmtflcnt.hxx>
#include <fmtornt.hxx>
#include <frmatr.hxx>
#include <flyfrms.hxx>
#include <txatbase.hxx>
#include "porfly.hxx"
#include "porlay.hxx"
#include "inftxt.hxx"
 
#include <sortedobjs.hxx>
 
/**
 * class SwFlyPortion => we expect a frame-locale SwRect!
 */
 
void SwFlyPortion::Paint( const SwTextPaintInfo& ) const
{
}
 
bool SwFlyPortion::Format( SwTextFormatInfo &rInf )
{
    OSL_ENSURE( GetFix() >= rInf.X(), "SwFlyPortion::Format" );
 
    // tabs must be expanded
    if( rInf.GetLastTab() )
        rInf.GetLastTab()->FormatEOL( rInf );
 
    rInf.GetLast()->FormatEOL( rInf );
    PrtWidth( static_cast<sal_uInt16>(GetFix() - rInf.X() + PrtWidth()) );
    if( !Width() )
    {
        OSL_ENSURE( Width(), "+SwFlyPortion::Format: a fly is a fly is a fly" );
        Width(1);
    }
 
    // resetting
    rInf.SetFly( nullptr );
    rInf.Width( rInf.RealWidth() );
    rInf.GetParaPortion()->SetFly();
 
    // trailing blank:
    if( rInf.GetIdx() < TextFrameIndex(rInf.GetText().getLength())
        && TextFrameIndex(1) < rInf.GetIdx()
        && !rInf.GetRest()
        && ' ' == rInf.GetChar( rInf.GetIdx() )
        && ' ' != rInf.GetChar(rInf.GetIdx() - TextFrameIndex(1))
        && ( !rInf.GetLast() || !rInf.GetLast()->IsBreakPortion() ) )
    {
        SetBlankWidth( rInf.GetTextSize(OUString(' ')).Width() );
        SetLen(TextFrameIndex(1));
    }
 
    const sal_uInt16 nNewWidth = static_cast<sal_uInt16>(rInf.X() + PrtWidth());
    if( rInf.Width() <= nNewWidth )
    {
        Truncate();
        if( nNewWidth > rInf.Width() )
        {
            PrtWidth( nNewWidth - rInf.Width() );
            SetFixWidth( PrtWidth() );
        }
        return true;
    }
    return false;
}
 
bool SwFlyCntPortion::Format( SwTextFormatInfo &rInf )
{
    bool bFull = rInf.Width() < rInf.X() + PrtWidth();
 
    if( bFull )
    {
        // If the line is full, and the character-bound frame is at
        // the beginning of a line
        // If it is not possible to side step into a Fly
        // "Begin of line" criteria ( ! rInf.X() ) has to be extended.
        // KerningPortions at beginning of line, e.g., for grid layout
        // must be considered.
        const SwLinePortion* pLastPor = rInf.GetLast();
        const sal_uInt16 nLeft = ( pLastPor &&
                                    ( pLastPor->IsKernPortion() ||
                                      pLastPor->IsErgoSumPortion() ) ) ?
                               pLastPor->Width() :
                               0;
 
        if( nLeft == rInf.X() && ! rInf.GetFly() )
        {
            Width( rInf.Width() );
            bFull = false; // so that notes can still be placed in this line
        }
        else
        {
            if( !rInf.GetFly() )
                rInf.SetNewLine( true );
            Width(0);
            SetAscent(0);
            SetLen(TextFrameIndex(0));
            if( rInf.GetLast() )
                rInf.GetLast()->FormatEOL( rInf );
 
            return bFull;
        }
    }
 
    rInf.GetParaPortion()->SetFly();
    return bFull;
}
 
//TODO: improve documentation
/** move character-bound objects inside the given area
 *
 * This allows moving those objects from Master to Follow, or vice versa.
 *
 * @param pNew
 * @param nStart
 * @param nEnd
 */
void SwTextFrame::MoveFlyInCnt(SwTextFrame *pNew,
        TextFrameIndex const nStart, TextFrameIndex const nEnd)
{
    SwSortedObjs *pObjs = nullptr;
    if ( nullptr != (pObjs = GetDrawObjs()) )
    {
        for ( size_t i = 0; GetDrawObjs() && i < pObjs->size(); ++i )
        {
            // Consider changed type of <SwSortedList> entries
            SwAnchoredObject* pAnchoredObj = (*pObjs)[i];
            const SwFormatAnchor& rAnch = pAnchoredObj->GetFrameFormat().GetAnchor();
            if (rAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR)
            {
                const SwPosition* pPos = rAnch.GetContentAnchor();
                TextFrameIndex const nIndex(MapModelToViewPos(*pPos));
                if (nStart <= nIndex && nIndex < nEnd)
                {
                    if ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) !=  nullptr )
                    {
                        RemoveFly( static_cast<SwFlyFrame*>(pAnchoredObj) );
                        pNew->AppendFly( static_cast<SwFlyFrame*>(pAnchoredObj) );
                    }
                    else if ( dynamic_cast< const SwAnchoredDrawObject *>( pAnchoredObj ) !=  nullptr )
                    {
                        RemoveDrawObj( *pAnchoredObj );
                        pNew->AppendDrawObj( *pAnchoredObj );
                    }
                    --i;
                }
            }
        }
    }
}
 
TextFrameIndex SwTextFrame::CalcFlyPos( SwFrameFormat const * pSearch )
{
    sw::MergedAttrIter iter(*this);
    for (SwTextAttr const* pHt = iter.NextAttr(); pHt; pHt = iter.NextAttr())
    {
        if( RES_TXTATR_FLYCNT == pHt->Which() )
        {
            SwFrameFormat* pFrameFormat = pHt->GetFlyCnt().GetFrameFormat();
            if( pFrameFormat == pSearch )
            {
                return TextFrameIndex(pHt->GetStart());
            }
        }
    }
    OSL_ENSURE(false, "CalcFlyPos: Not Found!");
    return TextFrameIndex(COMPLETE_STRING);
}
 
void sw::FlyContentPortion::Paint(const SwTextPaintInfo& rInf) const
{
    // Baseline output
    // Re-paint everything at a CompletePaint call
    SwRect aRepaintRect(rInf.GetPaintRect());
 
    if(rInf.GetTextFrame()->IsRightToLeft())
        rInf.GetTextFrame()->SwitchLTRtoRTL(aRepaintRect);
 
    if(rInf.GetTextFrame()->IsVertical())
        rInf.GetTextFrame()->SwitchHorizontalToVertical(aRepaintRect);
 
    if((m_pFly->IsCompletePaint() ||
            m_pFly->getFrameArea().IsOver(aRepaintRect)) &&
            SwFlyFrame::IsPaint(m_pFly->GetVirtDrawObj(), m_pFly->getRootFrame()->GetCurrShell()))
    {
        SwRect aRect(m_pFly->getFrameArea());
        if(!m_pFly->IsCompletePaint())
            aRect.Intersection_(aRepaintRect);
 
        // GetFlyFrame() may change the layout mode at the output device.
        {
            SwLayoutModeModifier aLayoutModeModifier(*rInf.GetOut());
            m_pFly->PaintSwFrame(const_cast<vcl::RenderContext&>(*rInf.GetOut()), aRect);
        }
        const_cast<SwTextPaintInfo&>(rInf).GetRefDev()->SetLayoutMode(rInf.GetOut()->GetLayoutMode());
 
        // As the OutputDevice might be anything, the font must be re-selected.
        // Being in const method should not be a problem.
        const_cast<SwTextPaintInfo&>(rInf).SelectFont();
 
        assert(rInf.GetVsh());
        SAL_WARN_IF(rInf.GetVsh()->GetOut() != rInf.GetOut(), "sw.core", "SwFlyCntPortion::Paint: Outdev has changed");
        if(rInf.GetVsh())
            const_cast<SwTextPaintInfo&>(rInf).SetOut(rInf.GetVsh()->GetOut());
    }
}
 
void sw::DrawFlyCntPortion::Paint(const SwTextPaintInfo&) const
{
    if(!m_pContact->GetAnchorFrame())
    {
        // No direct positioning of the drawing object is needed
        m_pContact->ConnectToLayout();
    }
}
 
/**
 * Use the dimensions of pFly->OutRect()
 */
SwFlyCntPortion::SwFlyCntPortion()
    : m_bMax(false)
    , m_eAlign(sw::LineAlign::NONE)
{
    nLineLength = TextFrameIndex(1);
    SetWhichPor(POR_FLYCNT);
}
 
sw::FlyContentPortion::FlyContentPortion(SwFlyInContentFrame* pFly)
    : m_pFly(pFly)
{
    SAL_WARN_IF(!pFly, "sw.core", "SwFlyCntPortion::SwFlyCntPortion: no SwFlyInContentFrame!");
}
 
sw::DrawFlyCntPortion::DrawFlyCntPortion(SwFrameFormat const & rFormat)
    : m_pContact(nullptr)
{
    rFormat.CallSwClientNotify(sw::CreatePortionHint(&m_pContact));
    assert(m_pContact);
}
 
sw::FlyContentPortion* sw::FlyContentPortion::Create(const SwTextFrame& rFrame, SwFlyInContentFrame* pFly, const Point& rBase, long nLnAscent, long nLnDescent, long nFlyAsc, long nFlyDesc, AsCharFlags nFlags)
{
    auto pNew(new sw::FlyContentPortion(pFly));
    pNew->SetBase(rFrame, rBase, nLnAscent, nLnDescent, nFlyAsc, nFlyDesc, nFlags | AsCharFlags::UlSpace | AsCharFlags::Init);
    return pNew;
}
 
sw::DrawFlyCntPortion* sw::DrawFlyCntPortion::Create(const SwTextFrame& rFrame, SwFrameFormat const & rFormat, const Point& rBase, long nLnAscent, long nLnDescent, long nFlyAsc, long nFlyDesc, AsCharFlags nFlags)
{
    auto pNew(new DrawFlyCntPortion(rFormat));
    pNew->SetBase(rFrame, rBase, nLnAscent, nLnDescent, nFlyAsc, nFlyDesc, nFlags | AsCharFlags::UlSpace | AsCharFlags::Init);
    return pNew;
}
 
sw::DrawFlyCntPortion::~DrawFlyCntPortion() {};
sw::FlyContentPortion::~FlyContentPortion() {};
 
SdrObject* sw::FlyContentPortion::GetSdrObj(const SwTextFrame&)
{
    return m_pFly->GetVirtDrawObj();
}
 
SdrObject* sw::DrawFlyCntPortion::GetSdrObj(const SwTextFrame& rFrame)
{
    SdrObject* pSdrObj;
    // Determine drawing object ('master' or 'virtual') by frame
    pSdrObj = m_pContact->GetDrawObjectByAnchorFrame(rFrame);
    if(!pSdrObj)
    {
        SAL_WARN("sw.core", "SwFlyCntPortion::SetBase(..) - No drawing object found by <GetDrawContact()->GetDrawObjectByAnchorFrame( rFrame )>");
        pSdrObj = m_pContact->GetMaster();
    }
 
    // Call <SwAnchoredDrawObject::MakeObjPos()> to assure that flag at
    // the <DrawFrameFormat> and at the <SwAnchoredDrawObject> instance are
    // correctly set
    if(pSdrObj)
        m_pContact->GetAnchoredObj(pSdrObj)->MakeObjPos();
    return pSdrObj;
}
 
/**
 * After setting the RefPoints, the ascent needs to be recalculated
 * because it is dependent on RelPos
 *
 * @param rBase CAUTION: needs to be an absolute value!
 */
void SwFlyCntPortion::SetBase( const SwTextFrame& rFrame, const Point &rBase,
                               long nLnAscent, long nLnDescent,
                               long nFlyAsc, long nFlyDesc,
                               AsCharFlags nFlags )
{
    // Use new class to position object
    // Determine drawing object
    SdrObject* pSdrObj = GetSdrObj(rFrame);
    if (!pSdrObj)
        return;
 
    // position object
    objectpositioning::SwAsCharAnchoredObjectPosition aObjPositioning(
                                    *pSdrObj,
                                    rBase, nFlags,
                                    nLnAscent, nLnDescent, nFlyAsc, nFlyDesc );
 
    // Scope of local variable <aObjPosInProgress>
    {
        SwObjPositioningInProgress aObjPosInProgress( *pSdrObj );
        aObjPositioning.CalcPosition();
    }
 
    SwFrameFormat* pShape = FindFrameFormat(pSdrObj);
    const SwFormatAnchor& rAnchor(pShape->GetAnchor());
    if (rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR)
    {
        // This is an inline draw shape, see if it has a textbox.
        SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT);
        if (pTextBox)
        {
            // It has, so look up its text rectangle, and adjust the position
            // of the textbox accordingly.
            // Both rectangles are absolute, SwFormatHori/VertOrient's position
            // is relative to the print area of the anchor text frame.
            tools::Rectangle aTextRectangle = SwTextBoxHelper::getTextRectangle(pShape);
 
            SwFormatHoriOrient aHori(pTextBox->GetHoriOrient());
            aHori.SetHoriOrient(css::text::HoriOrientation::NONE);
            sal_Int32 nLeft = aTextRectangle.getX() - rFrame.getFrameArea().Left()
                              - rFrame.getFramePrintArea().Left();
            aHori.SetPos(nLeft);
 
            SwFormatVertOrient aVert(pTextBox->GetVertOrient());
            aVert.SetVertOrient(css::text::VertOrientation::NONE);
            sal_Int32 const nTop = aTextRectangle.getY() - rFrame.getFrameArea().Top()
                                   - rFrame.getFramePrintArea().Top();
            aVert.SetPos(nTop);
 
            pTextBox->LockModify();
            pTextBox->SetFormatAttr(aHori);
            pTextBox->SetFormatAttr(aVert);
            pTextBox->UnlockModify();
        }
    }
 
    SetAlign( aObjPositioning.GetLineAlignment() );
 
    m_aRef = aObjPositioning.GetAnchorPos();
    if( nFlags & AsCharFlags::Rotate )
        SvXSize( aObjPositioning.GetObjBoundRectInclSpacing().SSize() );
    else
        SvLSize( aObjPositioning.GetObjBoundRectInclSpacing().SSize() );
    if( Height() )
    {
        // GetRelPosY returns the relative position to baseline (if 0, the
        // upper border of the FlyCnt if on the baseline of a line)
        SwTwips nRelPos = aObjPositioning.GetRelPosY();
        if ( nRelPos < 0 )
        {
            nAscent = static_cast<sal_uInt16>(-nRelPos);
            if( nAscent > Height() )
                Height( nAscent );
        }
        else
        {
            nAscent = 0;
            Height( Height() + static_cast<sal_uInt16>(nRelPos) );
        }
    }
    else
    {
        Height( 1 );
        nAscent = 0;
    }
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V595 The 'rInf.GetVsh()' pointer was utilized before it was verified against nullptr. Check lines: 236, 237.