diff options
Diffstat (limited to 'sw/source/core/layout')
45 files changed, 64522 insertions, 0 deletions
diff --git a/sw/source/core/layout/anchoreddrawobject.cxx b/sw/source/core/layout/anchoreddrawobject.cxx new file mode 100644 index 000000000..491e86001 --- /dev/null +++ b/sw/source/core/layout/anchoreddrawobject.cxx @@ -0,0 +1,939 @@ +/* -*- 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 <config_wasm_strip.h> + +#include <dcontact.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <tocntntanchoredobjectposition.hxx> +#include <tolayoutanchoredobjectposition.hxx> +#include <frmtool.hxx> +#include <fmtornt.hxx> +#include <txtfrm.hxx> +#include <vector> +#include <svx/svdogrp.hxx> +#include <tools/fract.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentState.hxx> +#include <txtfly.hxx> +#include <viewimp.hxx> +#include <textboxhelper.hxx> +#include <unomid.h> +#include <svx/svdoashp.hxx> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; + +namespace { + +/// helper class for correct notification due to the positioning of +/// the anchored drawing object +class SwPosNotify +{ + private: + SwAnchoredDrawObject* mpAnchoredDrawObj; + SwRect maOldObjRect; + SwPageFrame* mpOldPageFrame; + + public: + explicit SwPosNotify( SwAnchoredDrawObject* _pAnchoredDrawObj ); + ~SwPosNotify() COVERITY_NOEXCEPT_FALSE; + // #i32795# + Point const & LastObjPos() const; +}; + +} + +SwPosNotify::SwPosNotify( SwAnchoredDrawObject* _pAnchoredDrawObj ) : + mpAnchoredDrawObj( _pAnchoredDrawObj ) +{ + maOldObjRect = mpAnchoredDrawObj->GetObjRect(); + // --> #i35640# - determine correct page frame + mpOldPageFrame = mpAnchoredDrawObj->GetPageFrame(); +} + +SwPosNotify::~SwPosNotify() COVERITY_NOEXCEPT_FALSE +{ + if ( maOldObjRect != mpAnchoredDrawObj->GetObjRect() ) + { + if( maOldObjRect.HasArea() && mpOldPageFrame ) + { + mpAnchoredDrawObj->NotifyBackground( mpOldPageFrame, maOldObjRect, + PrepareHint::FlyFrameLeave ); + } + SwRect aNewObjRect( mpAnchoredDrawObj->GetObjRect() ); + if( aNewObjRect.HasArea() ) + { + // --> #i35640# - determine correct page frame + SwPageFrame* pNewPageFrame = mpAnchoredDrawObj->GetPageFrame(); + if( pNewPageFrame ) + mpAnchoredDrawObj->NotifyBackground( pNewPageFrame, aNewObjRect, + PrepareHint::FlyFrameArrive ); + } + + ::ClrContourCache( mpAnchoredDrawObj->GetDrawObj() ); + + // --> #i35640# - additional notify anchor text frame + // Needed for negative positioned drawing objects + // --> #i43255# - refine condition to avoid unneeded + // invalidations: anchored object had to be on the page of its anchor + // text frame. + if ( mpAnchoredDrawObj->GetAnchorFrame()->IsTextFrame() && + mpOldPageFrame == mpAnchoredDrawObj->GetAnchorFrame()->FindPageFrame() ) + { + mpAnchoredDrawObj->AnchorFrame()->Prepare( PrepareHint::FlyFrameLeave ); + } + + // indicate a restart of the layout process + mpAnchoredDrawObj->SetRestartLayoutProcess( true ); + } + else + { + // lock position + mpAnchoredDrawObj->LockPosition(); + + if ( !mpAnchoredDrawObj->ConsiderForTextWrap() ) + { + // indicate that object has to be considered for text wrap + mpAnchoredDrawObj->SetConsiderForTextWrap( true ); + // invalidate 'background' in order to allow its 'background' + // to wrap around it. + mpAnchoredDrawObj->NotifyBackground( mpAnchoredDrawObj->GetPageFrame(), + mpAnchoredDrawObj->GetObjRectWithSpaces(), + PrepareHint::FlyFrameArrive ); + // invalidate position of anchor frame in order to force + // a re-format of the anchor frame, which also causes a + // re-format of the invalid previous frames of the anchor frame. + mpAnchoredDrawObj->AnchorFrame()->InvalidatePos(); + } + } + // tdf#101464 notify SwAccessibleMap about new drawing object position +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if (mpOldPageFrame && mpOldPageFrame->getRootFrame()->IsAnyShellAccessible()) + { + mpOldPageFrame->getRootFrame()->GetCurrShell()->Imp()->MoveAccessible( + nullptr, mpAnchoredDrawObj->GetDrawObj(), maOldObjRect); + } +#endif +} + +// --> #i32795# +Point const & SwPosNotify::LastObjPos() const +{ + return maOldObjRect.Pos(); +} + +namespace { + +// #i32795# +/// helper class for oscillation control on object positioning +class SwObjPosOscillationControl +{ + private: + const SwAnchoredDrawObject* mpAnchoredDrawObj; + + std::vector<Point> maObjPositions; + + public: + explicit SwObjPosOscillationControl( const SwAnchoredDrawObject& _rAnchoredDrawObj ); + + bool OscillationDetected(); +}; + +} + +SwObjPosOscillationControl::SwObjPosOscillationControl( + const SwAnchoredDrawObject& _rAnchoredDrawObj ) + : mpAnchoredDrawObj( &_rAnchoredDrawObj ) +{ +} + +bool SwObjPosOscillationControl::OscillationDetected() +{ + bool bOscillationDetected = false; + + if ( maObjPositions.size() == 20 ) + { + // position stack is full -> oscillation + bOscillationDetected = true; + } + else + { + Point aNewObjPos = mpAnchoredDrawObj->GetObjRect().Pos(); + for ( auto const & pt : maObjPositions ) + { + if ( aNewObjPos == pt ) + { + // position already occurred -> oscillation + bOscillationDetected = true; + break; + } + } + if ( !bOscillationDetected ) + { + maObjPositions.push_back( aNewObjPos ); + } + } + + return bOscillationDetected; +} + + +SwAnchoredDrawObject::SwAnchoredDrawObject() : + mbValidPos( false ), + mbNotYetAttachedToAnchorFrame( true ), + // --> #i28749# + mbNotYetPositioned( true ), + // --> #i62875# + mbCaptureAfterLayoutDirChange( false ) +{ +} + +SwAnchoredDrawObject::~SwAnchoredDrawObject() +{ +} + +// --> #i62875# +void SwAnchoredDrawObject::UpdateLayoutDir() +{ + SwFrameFormat::tLayoutDir nOldLayoutDir( GetFrameFormat().GetLayoutDir() ); + + SwAnchoredObject::UpdateLayoutDir(); + + if ( !NotYetPositioned() && + GetFrameFormat().GetLayoutDir() != nOldLayoutDir && + GetFrameFormat().GetDoc()->GetDocumentSettingManager().get(DocumentSettingId::DO_NOT_CAPTURE_DRAW_OBJS_ON_PAGE) && + !IsOutsidePage() ) + { + mbCaptureAfterLayoutDirChange = true; + } +} + +// --> #i62875# +bool SwAnchoredDrawObject::IsOutsidePage() const +{ + bool bOutsidePage( false ); + + if ( !NotYetPositioned() && GetPageFrame() ) + { + SwRect aTmpRect( GetObjRect() ); + bOutsidePage = + ( aTmpRect.Intersection( GetPageFrame()->getFrameArea() ) != GetObjRect() ); + } + + return bOutsidePage; +} + +void SwAnchoredDrawObject::MakeObjPos() +{ + if ( IsPositioningInProgress() ) + { + // nothing to do - positioning already in progress + return; + } + + if ( mbValidPos ) + { + // nothing to do - position is valid + return; + } + + // --> #i28749# - anchored drawing object has to be attached + // to anchor frame + if ( mbNotYetAttachedToAnchorFrame ) + { + OSL_FAIL( "<SwAnchoredDrawObject::MakeObjPos() - drawing object not yet attached to anchor frame -> no positioning" ); + return; + } + + SwDrawContact* pDrawContact = + static_cast<SwDrawContact*>(::GetUserCall( GetDrawObj() )); + + // --> #i28749# - if anchored drawing object hasn't been yet + // positioned, convert its positioning attributes, if its positioning + // attributes are given in horizontal left-to-right layout. + // --> #i36010# - Note: horizontal left-to-right layout is made + // the default layout direction for <SwDrawFrameFormat> instances. Thus, it has + // to be adjusted manually, if no adjustment of the positioning attributes + // have to be performed here. + // --> #i35635# - additionally move drawing object to the visible layer. + if ( mbNotYetPositioned ) + { + // --> #i35635# + pDrawContact->MoveObjToVisibleLayer( DrawObj() ); + // --> perform conversion of positioning + // attributes only for 'master' drawing objects + // #i44334#, #i44681# - check, if positioning + // attributes already have been set. + if ( dynamic_cast< const SwDrawVirtObj* >(GetDrawObj()) == nullptr && + !static_cast<SwDrawFrameFormat&>(GetFrameFormat()).IsPosAttrSet() ) + { + SetPositioningAttr(); + } + // --> + // - reset internal flag after all needed actions are performed to + // avoid callbacks from drawing layer + mbNotYetPositioned = false; + } + + // indicate that positioning is in progress + { + SwObjPositioningInProgress aObjPosInProgress( *this ); + + // determine relative position of drawing object and set it + switch ( pDrawContact->GetAnchorId() ) + { + case RndStdIds::FLY_AS_CHAR: + { + // indicate that position will be valid after positioning is performed + mbValidPos = true; + // nothing to do, because as-character anchored objects are positioned + // during the format of its anchor frame - see <SwFlyCntPortion::SetBase(..)> + } + break; + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: + { + // --> #i32795# - move intrinsic positioning to + // helper method <MakeObjPosAnchoredAtPara()> + MakeObjPosAnchoredAtPara(); + } + break; + case RndStdIds::FLY_AT_PAGE: + case RndStdIds::FLY_AT_FLY: + { + // --> #i32795# - move intrinsic positioning to + // helper method <MakeObjPosAnchoredAtLayout()> + MakeObjPosAnchoredAtLayout(); + } + break; + default: + { + assert(!"<SwAnchoredDrawObject::MakeObjPos()> - unknown anchor type."); + } + } + + // keep, current object rectangle + // --> #i34748# - use new method <SetLastObjRect(..)> + SetLastObjRect( GetObjRect().SVRect() ); + + // Assure for 'master' drawing object, that it's registered at the correct page. + // Perform check not for as-character anchored drawing objects and only if + // the anchor frame is valid. + if ( dynamic_cast< const SwDrawVirtObj* >(GetDrawObj()) == nullptr && + !pDrawContact->ObjAnchoredAsChar() && + GetAnchorFrame()->isFrameAreaDefinitionValid() ) + { + pDrawContact->ChkPage(); + } + } + + // --> #i62875# + if ( !(mbCaptureAfterLayoutDirChange && + GetPageFrame()) ) + return; + + SwRect aPageRect( GetPageFrame()->getFrameArea() ); + SwRect aObjRect( GetObjRect() ); + if ( aObjRect.Right() >= aPageRect.Right() + 10 ) + { + Size aSize( aPageRect.Right() - aObjRect.Right(), 0 ); + DrawObj()->Move( aSize ); + aObjRect = GetObjRect(); + } + + if ( aObjRect.Left() + 10 <= aPageRect.Left() ) + { + Size aSize( aPageRect.Left() - aObjRect.Left(), 0 ); + DrawObj()->Move( aSize ); + } + + mbCaptureAfterLayoutDirChange = false; +} + +/** method for the intrinsic positioning of an at-paragraph|at-character + anchored drawing object + + #i32795# - helper method for method <MakeObjPos> +*/ +void SwAnchoredDrawObject::MakeObjPosAnchoredAtPara() +{ + // --> #i32795# - adopt positioning algorithm from Writer + // fly frames, which are anchored at paragraph|at character + + // Determine, if anchor frame can/has to be formatted. + // If yes, after each object positioning the anchor frame is formatted. + // If after the anchor frame format the object position isn't valid, the + // object is positioned again. + // --> #i43255# - refine condition: anchor frame format not + // allowed, if another anchored object, has to be consider its wrap influence + // --> #i50356# - format anchor frame containing the anchor + // position. E.g., for at-character anchored object this can be the follow + // frame of the anchor frame, which contains the anchor character. + bool bJoinLocked + = static_cast<const SwTextFrame*>(GetAnchorFrameContainingAnchPos())->IsAnyJoinLocked(); + const bool bFormatAnchor = !bJoinLocked && !ConsiderObjWrapInfluenceOnObjPos() + && !ConsiderObjWrapInfluenceOfOtherObjs(); + + // Format of anchor is needed for (vertical) fly offsets, otherwise the + // lack of fly portions will result in an incorrect 0 offset. + bool bAddVerticalFlyOffsets = GetFrameFormat().getIDocumentSettingAccess().get( + DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS); + bool bFormatAnchorOnce = !bJoinLocked && bAddVerticalFlyOffsets; + + if (bFormatAnchor || bFormatAnchorOnce) + { + // --> #i50356# + GetAnchorFrameContainingAnchPos()->Calc(GetAnchorFrameContainingAnchPos()->getRootFrame()->GetCurrShell()->GetOut()); + } + + bool bOscillationDetected = false; + SwObjPosOscillationControl aObjPosOscCtrl( *this ); + // --> #i3317# - boolean, to apply temporarily the + // 'straightforward positioning process' for the frame due to its + // overlapping with a previous column. + bool bConsiderWrapInfluenceDueToOverlapPrevCol( false ); + do { + // indicate that position will be valid after positioning is performed + mbValidPos = true; + + // --> #i35640# - correct scope for <SwPosNotify> instance + { + // create instance of <SwPosNotify> for correct notification + SwPosNotify aPosNotify( this ); + + // determine and set position + objectpositioning::SwToContentAnchoredObjectPosition + aObjPositioning( *DrawObj() ); + aObjPositioning.CalcPosition(); + + // get further needed results of the positioning algorithm + SetVertPosOrientFrame ( aObjPositioning.GetVertPosOrientFrame() ); + SetDrawObjAnchor(); + + // check for object position oscillation, if position has changed. + if ( GetObjRect().Pos() != aPosNotify.LastObjPos() ) + { + bOscillationDetected = aObjPosOscCtrl.OscillationDetected(); + } + } + // format anchor frame, if requested. + // Note: the format of the anchor frame can cause the object position + // to be invalid. + if ( bFormatAnchor ) + { + // --> #i50356# + GetAnchorFrameContainingAnchPos()->Calc(GetAnchorFrameContainingAnchPos()->getRootFrame()->GetCurrShell()->GetOut()); + } + + // --> #i3317# + if ( !ConsiderObjWrapInfluenceOnObjPos() && + OverlapsPrevColumn() ) + { + bConsiderWrapInfluenceDueToOverlapPrevCol = true; + } + } while ( !mbValidPos && !bOscillationDetected && + !bConsiderWrapInfluenceDueToOverlapPrevCol ); + + // --> #i3317# - consider a detected oscillation and overlapping + // with previous column. + // temporarily consider the anchored objects wrapping style influence + if ( bOscillationDetected || bConsiderWrapInfluenceDueToOverlapPrevCol ) + { + SetTmpConsiderWrapInfluence( true ); + SetRestartLayoutProcess( true ); + } +} + +/** method for the intrinsic positioning of an at-page|at-frame anchored + drawing object + + #i32795# - helper method for method <MakeObjPos> +*/ +void SwAnchoredDrawObject::MakeObjPosAnchoredAtLayout() +{ + // indicate that position will be valid after positioning is performed + mbValidPos = true; + + // create instance of <SwPosNotify> for correct notification + SwPosNotify aPosNotify( this ); + + // determine position + objectpositioning::SwToLayoutAnchoredObjectPosition + aObjPositioning( *DrawObj() ); + aObjPositioning.CalcPosition(); + + // set position + + // --> #i31698# + // --> #i34995# - setting anchor position needed for filters, + // especially for the xml-filter to the OpenOffice.org file format + { + const Point aNewAnchorPos = + GetAnchorFrame()->GetFrameAnchorPos( ::HasWrap( GetDrawObj() ) ); + DrawObj()->SetAnchorPos( aNewAnchorPos ); + // --> #i70122# - missing invalidation + InvalidateObjRectWithSpaces(); + } + SetCurrRelPos( aObjPositioning.GetRelPos() ); + const SwFrame* pAnchorFrame = GetAnchorFrame(); + SwRectFnSet aRectFnSet(pAnchorFrame); + const Point aAnchPos( aRectFnSet.GetPos(pAnchorFrame->getFrameArea()) ); + SetObjLeft( aAnchPos.X() + GetCurrRelPos().X() ); + SetObjTop( aAnchPos.Y() + GetCurrRelPos().Y() ); +} + +void SwAnchoredDrawObject::SetDrawObjAnchor() +{ + // new anchor position + // --> #i31698# - + Point aNewAnchorPos = + GetAnchorFrame()->GetFrameAnchorPos( ::HasWrap( GetDrawObj() ) ); + Point aCurrAnchorPos = GetDrawObj()->GetAnchorPos(); + if ( aNewAnchorPos != aCurrAnchorPos ) + { + // determine movement to be applied after setting the new anchor position + Size aMove( aCurrAnchorPos.getX() - aNewAnchorPos.getX(), + aCurrAnchorPos.getY() - aNewAnchorPos.getY() ); + // set new anchor position + DrawObj()->SetAnchorPos( aNewAnchorPos ); + // correct object position, caused by setting new anchor position + DrawObj()->Move( aMove ); + // --> #i70122# - missing invalidation + InvalidateObjRectWithSpaces(); + } +} + +/** method to invalidate the given page frame + + #i28701# +*/ +void SwAnchoredDrawObject::InvalidatePage_( SwPageFrame* _pPageFrame ) +{ + if ( !_pPageFrame || _pPageFrame->GetFormat()->GetDoc()->IsInDtor() ) + return; + + if ( !_pPageFrame->GetUpper() ) + return; + + // --> #i35007# - correct invalidation for as-character + // anchored objects. + if ( GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR ) + { + _pPageFrame->InvalidateFlyInCnt(); + } + else + { + _pPageFrame->InvalidateFlyLayout(); + } + + SwRootFrame* pRootFrame = static_cast<SwRootFrame*>(_pPageFrame->GetUpper()); + pRootFrame->DisallowTurbo(); + if ( pRootFrame->GetTurbo() ) + { + const SwContentFrame* pTmpFrame = pRootFrame->GetTurbo(); + pRootFrame->ResetTurbo(); + pTmpFrame->InvalidatePage(); + } + pRootFrame->SetIdleFlags(); +} + +void SwAnchoredDrawObject::InvalidateObjPos() +{ + // --> #i28701# - check, if invalidation is allowed + if ( !(mbValidPos && + InvalidationOfPosAllowed()) ) + return; + + mbValidPos = false; + // --> #i68520# + InvalidateObjRectWithSpaces(); + + // --> #i44339# - check, if anchor frame exists. + if ( !GetAnchorFrame() ) + return; + + // --> #118547# - notify anchor frame of as-character + // anchored object, because its positioned by the format of its anchor frame. + // --> #i44559# - assure, that text hint is already + // existing in the text frame + if ( GetAnchorFrame()->DynCastTextFrame() != nullptr && + (GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR) ) + { + SwTextFrame* pAnchorTextFrame( static_cast<SwTextFrame*>(AnchorFrame()) ); + if (pAnchorTextFrame->CalcFlyPos(&GetFrameFormat()) != TextFrameIndex(COMPLETE_STRING)) + { + AnchorFrame()->Prepare( PrepareHint::FlyFrameAttributesChanged, &GetFrameFormat() ); + } + } + + SwPageFrame* pPageFrame = AnchorFrame()->FindPageFrame(); + InvalidatePage_( pPageFrame ); + + // --> #i32270# - also invalidate page frame, at which the + // drawing object is registered at. + SwPageFrame* pPageFrameRegisteredAt = GetPageFrame(); + if ( pPageFrameRegisteredAt && + pPageFrameRegisteredAt != pPageFrame ) + { + InvalidatePage_( pPageFrameRegisteredAt ); + } + // #i33751#, #i34060# - method <GetPageFrameOfAnchor()> + // is replaced by method <FindPageFrameOfAnchor()>. It's return value + // have to be checked. + SwPageFrame* pPageFrameOfAnchor = FindPageFrameOfAnchor(); + if ( pPageFrameOfAnchor && + pPageFrameOfAnchor != pPageFrame && + pPageFrameOfAnchor != pPageFrameRegisteredAt ) + { + InvalidatePage_( pPageFrameOfAnchor ); + } +} + +SwFrameFormat& SwAnchoredDrawObject::GetFrameFormat() +{ + assert(static_cast<SwDrawContact*>(GetUserCall(GetDrawObj()))->GetFormat()); + return *(static_cast<SwDrawContact*>(GetUserCall(GetDrawObj()))->GetFormat()); +} +const SwFrameFormat& SwAnchoredDrawObject::GetFrameFormat() const +{ + assert(static_cast<SwDrawContact*>(GetUserCall(GetDrawObj()))->GetFormat()); + return *(static_cast<SwDrawContact*>(GetUserCall(GetDrawObj()))->GetFormat()); +} + +SwRect SwAnchoredDrawObject::GetObjRect() const +{ + // use geometry of drawing object + //return GetDrawObj()->GetCurrentBoundRect(); + return SwRect(GetDrawObj()->GetSnapRect()); +} + +namespace +{ + // Imagine an open book, inside margin is the one that is at the inner side of the pages, at the center of the book, + // outside margin is at the two opposite edges of the book. + // outside --text-- inside | inside --text-- outside + // With mirrored margins, when relating the size of an object from the inside margin for example, on the + // first page we calculate the new size of the object using the size of the right margin, + // on second page the left margin, third page right margin, etc. + tools::Long getInsideOutsideRelativeWidth(bool isOutside, const SwPageFrame* const pPageFrame) + { + // Alternating between the only two possible cases: inside and outside. + // Inside = false, Outside = true. + auto nPageNum = pPageFrame->GetPhyPageNum(); + if (nPageNum % 2 == (isOutside ? 0 : 1)) + return pPageFrame->GetRightMargin(); + else + return pPageFrame->GetLeftMargin(); + } +} + +// --> #i70122# +SwRect SwAnchoredDrawObject::GetObjBoundRect() const +{ + bool bGroupShape = dynamic_cast<const SdrObjGroup*>( GetDrawObj() ); + // Resize objects with relative width or height + if ( !bGroupShape && GetPageFrame( ) && ( GetDrawObj( )->GetRelativeWidth( ) || GetDrawObj()->GetRelativeHeight( ) ) ) + { + tools::Rectangle aCurrObjRect = GetDrawObj()->GetCurrentBoundRect(); + + tools::Long nTargetWidth = aCurrObjRect.GetWidth( ); + if ( GetDrawObj( )->GetRelativeWidth( ) ) + { + tools::Long nWidth = 0; + if (GetDrawObj()->GetRelativeWidthRelation() == text::RelOrientation::FRAME) + // Exclude margins. + nWidth = GetPageFrame()->getFramePrintArea().SVRect().GetWidth(); + // Here we handle the relative size of the width of some shape. + // The size of the shape's width is going to be relative to the size of the left margin. + // E.g.: (left margin = 8 && relative size = 150%) -> width of some shape = 12. + else if (GetDrawObj()->GetRelativeWidthRelation() == text::RelOrientation::PAGE_LEFT) + { + if (GetPageFrame()->GetPageDesc()->GetUseOn() == UseOnPage::Mirror) + // We want to get the width of whatever is going through here using the size of the + // outside margin. + nWidth = getInsideOutsideRelativeWidth(true, GetPageFrame()); + else + nWidth = GetPageFrame()->GetLeftMargin(); + } + // Same as the left margin above. + else if (GetDrawObj()->GetRelativeWidthRelation() == text::RelOrientation::PAGE_RIGHT) + if (GetPageFrame()->GetPageDesc()->GetUseOn() == UseOnPage::Mirror) + // We want to get the width of whatever is going through here using the size of the + // inside margin. + nWidth = getInsideOutsideRelativeWidth(false, GetPageFrame()); + else + nWidth = GetPageFrame()->GetRightMargin(); + else + nWidth = GetPageFrame( )->GetBoundRect( GetPageFrame()->getRootFrame()->GetCurrShell()->GetOut() ).SVRect().GetWidth(); + nTargetWidth = nWidth * (*GetDrawObj( )->GetRelativeWidth()); + } + + bool bCheck = GetDrawObj()->GetRelativeHeight(); + if (bCheck) + { + auto pObjCustomShape = dynamic_cast<const SdrObjCustomShape*>(GetDrawObj()); + bCheck = !pObjCustomShape || !pObjCustomShape->IsAutoGrowHeight(); + } + + tools::Long nTargetHeight = aCurrObjRect.GetHeight(); + if (bCheck) + { + tools::Long nHeight = 0; + if (GetDrawObj()->GetRelativeHeightRelation() == text::RelOrientation::FRAME) + // Exclude margins. + nHeight = GetPageFrame()->getFramePrintArea().SVRect().GetHeight(); + else if (GetDrawObj()->GetRelativeHeightRelation() == text::RelOrientation::PAGE_PRINT_AREA) + { + // count required height: print area top = top margin + header + SwRect aHeaderRect; + const SwHeaderFrame* pHeaderFrame = GetPageFrame()->GetHeaderFrame(); + if (pHeaderFrame) + aHeaderRect = pHeaderFrame->GetPaintArea(); + nHeight = GetPageFrame()->GetTopMargin() + aHeaderRect.Height(); + } + else if (GetDrawObj()->GetRelativeHeightRelation() == text::RelOrientation::PAGE_PRINT_AREA_BOTTOM) + { + // count required height: print area bottom = bottom margin + footer + SwRect aFooterRect; + auto pFooterFrame = GetPageFrame()->GetFooterFrame(); + if (pFooterFrame) + aFooterRect = pFooterFrame->GetPaintArea(); + nHeight = GetPageFrame()->GetBottomMargin() + aFooterRect.Height(); + } + else + nHeight = GetPageFrame( )->GetBoundRect( GetPageFrame()->getRootFrame()->GetCurrShell()->GetOut() ).SVRect().GetHeight(); + nTargetHeight = nHeight * (*GetDrawObj()->GetRelativeHeight()); + } + + if ( nTargetWidth != aCurrObjRect.GetWidth( ) || nTargetHeight != aCurrObjRect.GetHeight( ) ) + { + SwDoc* pDoc = const_cast<SwDoc*>(GetPageFrame()->GetFormat()->GetDoc()); + + bool bEnableSetModified = pDoc->getIDocumentState().IsEnableSetModified(); + pDoc->getIDocumentState().SetEnableSetModified(false); + auto pObject = const_cast<SdrObject*>(GetDrawObj()); + pObject->Resize( aCurrObjRect.TopLeft(), + Fraction( nTargetWidth, aCurrObjRect.GetWidth() ), + Fraction( nTargetHeight, aCurrObjRect.GetHeight() ), false ); + + if (SwFrameFormat* pFrameFormat = FindFrameFormat(pObject)) + { + if (SwTextBoxHelper::isTextBox(pFrameFormat, RES_DRAWFRMFMT)) + { + // Shape has relative size and also a textbox, update its text area as well. + uno::Reference<drawing::XShape> xShape(pObject->getUnoShape(), uno::UNO_QUERY); + SwTextBoxHelper::syncProperty(pFrameFormat, RES_FRM_SIZE, MID_FRMSIZE_SIZE, + uno::Any(xShape->getSize())); + } + } + + pDoc->getIDocumentState().SetEnableSetModified(bEnableSetModified); + } + } + return SwRect(GetDrawObj()->GetCurrentBoundRect()); +} + +// --> #i68520# +bool SwAnchoredDrawObject::SetObjTop_( const SwTwips _nTop ) +{ + SwTwips nDiff = _nTop - GetObjRect().Top(); + DrawObj()->Move( Size( 0, nDiff ) ); + + return nDiff != 0; +} +bool SwAnchoredDrawObject::SetObjLeft_( const SwTwips _nLeft ) +{ + SwTwips nDiff = _nLeft - GetObjRect().Left(); + DrawObj()->Move( Size( nDiff, 0 ) ); + + return nDiff != 0; +} + +/** adjust positioning and alignment attributes for new anchor frame + + #i33313# - add second optional parameter <_pNewObjRect> +*/ +void SwAnchoredDrawObject::AdjustPositioningAttr( const SwFrame* _pNewAnchorFrame, + const SwRect* _pNewObjRect ) +{ + SwTwips nHoriRelPos = 0; + SwTwips nVertRelPos = 0; + const Point aAnchorPos = _pNewAnchorFrame->GetFrameAnchorPos( ::HasWrap( GetDrawObj() ) ); + // --> #i33313# + const SwRect aObjRect( _pNewObjRect ? *_pNewObjRect : GetObjRect() ); + const bool bVert = _pNewAnchorFrame->IsVertical(); + const bool bR2L = _pNewAnchorFrame->IsRightToLeft(); + if ( bVert ) + { + nHoriRelPos = aObjRect.Top() - aAnchorPos.Y(); + nVertRelPos = aAnchorPos.X() - aObjRect.Right(); + } + else if ( bR2L ) + { + nHoriRelPos = aAnchorPos.X() - aObjRect.Right(); + nVertRelPos = aObjRect.Top() - aAnchorPos.Y(); + } + else + { + nHoriRelPos = aObjRect.Left() - aAnchorPos.X(); + nVertRelPos = aObjRect.Top() - aAnchorPos.Y(); + } + + SwFormatHoriOrient hori(nHoriRelPos, text::HoriOrientation::NONE, text::RelOrientation::FRAME); + SwFormatVertOrient vert(nVertRelPos, text::VertOrientation::NONE, text::RelOrientation::FRAME); + SfxItemSetFixed<RES_VERT_ORIENT, RES_HORI_ORIENT> items(GetFrameFormat().GetDoc()->GetAttrPool()); + items.Put(hori); + items.Put(vert); + GetFrameFormat().GetDoc()->SetAttr(items, GetFrameFormat()); +} + +// --> #i34748# - change return type. +// If member <mpLastObjRect> is NULL, create one. +void SwAnchoredDrawObject::SetLastObjRect( const tools::Rectangle& _rNewLastRect ) +{ + maLastObjRect = _rNewLastRect; +} + +void SwAnchoredDrawObject::ObjectAttachedToAnchorFrame() +{ + // --> #i31698# + SwAnchoredObject::ObjectAttachedToAnchorFrame(); + + if ( mbNotYetAttachedToAnchorFrame ) + { + mbNotYetAttachedToAnchorFrame = false; + } +} + +/** method to set positioning attributes + + #i35798# + During load the positioning attributes aren't set. + Thus, the positioning attributes are set by the current object geometry. + This method is also used for the conversion for drawing objects + (not anchored as-character) imported from OpenOffice.org file format + once and directly before the first positioning. +*/ +void SwAnchoredDrawObject::SetPositioningAttr() +{ + SwDrawContact* pDrawContact = + static_cast<SwDrawContact*>(GetUserCall( GetDrawObj() )); + + if ( !pDrawContact->ObjAnchoredAsChar() ) + { + SwRect aObjRect( GetObjRect() ); + + SwTwips nHoriPos = aObjRect.Left(); + SwTwips nVertPos = aObjRect.Top(); + // #i44334#, #i44681# + // perform conversion only if position is in horizontal-left-to-right-layout. + if ( GetFrameFormat().GetPositionLayoutDir() == + text::PositionLayoutDir::PositionInHoriL2R ) + { + SwFrameFormat::tLayoutDir eLayoutDir = GetFrameFormat().GetLayoutDir(); + switch ( eLayoutDir ) + { + case SwFrameFormat::HORI_L2R: + { + // nothing to do + } + break; + case SwFrameFormat::HORI_R2L: + { + nHoriPos = -aObjRect.Left() - aObjRect.Width(); + } + break; + case SwFrameFormat::VERT_R2L: + { + nHoriPos = aObjRect.Top(); + nVertPos = -aObjRect.Left() - aObjRect.Width(); + } + break; + default: + { + assert(!"<SwAnchoredDrawObject::SetPositioningAttr()> - unsupported layout direction"); + } + } + } + + // --> #i71182# + // only change position - do not lose other attributes + + SwFormatHoriOrient aHori( GetFrameFormat().GetHoriOrient() ); + if (nHoriPos != aHori.GetPos()) { + aHori.SetPos( nHoriPos ); + InvalidateObjRectWithSpaces(); + GetFrameFormat().SetFormatAttr( aHori ); + } + + SwFormatVertOrient aVert( GetFrameFormat().GetVertOrient() ); + if (nVertPos != aVert.GetPos()) { + aVert.SetPos( nVertPos ); + InvalidateObjRectWithSpaces(); + GetFrameFormat().SetFormatAttr( aVert ); + } + + // --> #i36010# - set layout direction of the position + GetFrameFormat().SetPositionLayoutDir( + text::PositionLayoutDir::PositionInLayoutDirOfAnchor ); + } + // --> #i65798# - also for as-character anchored objects + // --> #i45952# - indicate that position + // attributes are set now. + static_cast<SwDrawFrameFormat&>(GetFrameFormat()).PosAttrSet(); +} + +void SwAnchoredDrawObject::NotifyBackground( SwPageFrame* _pPageFrame, + const SwRect& _rRect, + PrepareHint _eHint ) +{ + ::Notify_Background( GetDrawObj(), _pPageFrame, _rRect, _eHint, true ); +} + +/** method to assure that anchored object is registered at the correct + page frame + + #i28701# +*/ +void SwAnchoredDrawObject::RegisterAtCorrectPage() +{ + SwPageFrame* pPageFrame( nullptr ); + if ( GetVertPosOrientFrame() ) + { + pPageFrame = const_cast<SwPageFrame*>(GetVertPosOrientFrame()->FindPageFrame()); + } + if ( pPageFrame && GetPageFrame() != pPageFrame ) + { + RegisterAtPage(*pPageFrame); + } +} + +void SwAnchoredDrawObject::RegisterAtPage(SwPageFrame & rPageFrame) +{ + assert(GetPageFrame() != &rPageFrame); + if (GetPageFrame()) + { + GetPageFrame()->RemoveDrawObjFromPage( *this ); + } + rPageFrame.AppendDrawObjToPage( *this ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/anchoredobject.cxx b/sw/source/core/layout/anchoredobject.cxx new file mode 100644 index 000000000..c38cfcb03 --- /dev/null +++ b/sw/source/core/layout/anchoredobject.cxx @@ -0,0 +1,916 @@ +/* -*- 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 <txtfrm.hxx> +#include <frmatr.hxx> +#include <fmtornt.hxx> +#include <doc.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <fmtsrnd.hxx> +#include <dcontact.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lrspitem.hxx> +#include <sortedobjs.hxx> +#include <pagefrm.hxx> +#include <layouter.hxx> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; + +// --> #i28701# - +// implementation of helper class <SwObjPositioningInProgress> + +SwObjPositioningInProgress::SwObjPositioningInProgress( SdrObject& _rSdrObj ) : + mpAnchoredObj( nullptr ), + // --> #i52904# + mbOldObjPositioningInProgress( false ) +{ + mpAnchoredObj = ::GetUserCall( &_rSdrObj )->GetAnchoredObj( &_rSdrObj ); + // --> #i52904# + mbOldObjPositioningInProgress = mpAnchoredObj->IsPositioningInProgress(); + mpAnchoredObj->SetPositioningInProgress( true ); +} +SwObjPositioningInProgress::SwObjPositioningInProgress( SwAnchoredObject& _rAnchoredObj ) : + mpAnchoredObj( &_rAnchoredObj ), + // --> #i52904# + mbOldObjPositioningInProgress( false ) +{ + // --> #i52904# + mbOldObjPositioningInProgress = mpAnchoredObj->IsPositioningInProgress(); + mpAnchoredObj->SetPositioningInProgress( true ); +} + +SwObjPositioningInProgress::~SwObjPositioningInProgress() +{ + if ( mpAnchoredObj ) + { + // --> #i52904# + mpAnchoredObj->SetPositioningInProgress( mbOldObjPositioningInProgress ); + } +} + + +SwAnchoredObject::SwAnchoredObject() : + mpDrawObj( nullptr ), + mpAnchorFrame( nullptr ), + // --> #i28701# + mpPageFrame( nullptr ), + mnLastTopOfLine( 0 ), + mpVertPosOrientFrame( nullptr ), + // --> #i28701# + mbPositioningInProgress( false ), + mbConsiderForTextWrap( false ), + mbPositionLocked( false ), + // --> #i40147# + mbKeepPositionLockedForSection( false ), + mbRestartLayoutProcess( false ), + // --> #i35911# + mbClearedEnvironment( false ), + // --> #i3317# + mbTmpConsiderWrapInfluence( false ), + mbObjRectWithSpacesValid( false ) +{ +} + +void SwAnchoredObject::ClearVertPosOrientFrame() +{ + if (mpVertPosOrientFrame) + { + const_cast<SwLayoutFrame*>(mpVertPosOrientFrame)->ClearVertPosOrientFrameFor(this); + mpVertPosOrientFrame = nullptr; + } +} + +SwAnchoredObject::~SwAnchoredObject() +{ + ClearVertPosOrientFrame(); +} + +void SwAnchoredObject::SetDrawObj( SdrObject& _rDrawObj ) +{ + mpDrawObj = &_rDrawObj; +} + + +void SwAnchoredObject::ChgAnchorFrame( SwFrame* _pNewAnchorFrame ) +{ + mpAnchorFrame = _pNewAnchorFrame; + + if ( mpAnchorFrame ) + { + ObjectAttachedToAnchorFrame(); + } +} + +/** determine anchor frame containing the anchor position + + #i26945# + the anchor frame, which is determined, is <mpAnchorFrame> + for an at-page, at-frame or at-paragraph anchored object + and the anchor character frame for an at-character and as-character + anchored object. +*/ +SwFrame* SwAnchoredObject::GetAnchorFrameContainingAnchPos() +{ + SwFrame* pAnchorFrameContainingAnchPos = FindAnchorCharFrame(); + if ( !pAnchorFrameContainingAnchPos ) + { + pAnchorFrameContainingAnchPos = AnchorFrame(); + } + + return pAnchorFrameContainingAnchPos; +} + + +void SwAnchoredObject::SetPageFrame( SwPageFrame* _pNewPageFrame ) +{ + if ( mpPageFrame == _pNewPageFrame ) + return; + + // clear member, which denotes the layout frame at which the vertical + // position is oriented at, if it doesn't fit to the new page frame. + if ( GetVertPosOrientFrame() && + ( !_pNewPageFrame || + _pNewPageFrame != GetVertPosOrientFrame()->FindPageFrame() ) ) + { + ClearVertPosOrientFrame(); + } + + // assign new page frame + mpPageFrame = _pNewPageFrame; +} + + +SwTwips SwAnchoredObject::GetRelCharX( const SwFrame* pFrame ) const +{ + return maLastCharRect.Left() - pFrame->getFrameArea().Left(); +} + +SwTwips SwAnchoredObject::GetRelCharY( const SwFrame* pFrame ) const +{ + return maLastCharRect.Bottom() - pFrame->getFrameArea().Top(); +} + +void SwAnchoredObject::AddLastCharY( tools::Long nDiff ) +{ + maLastCharRect.Pos().AdjustY(nDiff ); +} + +void SwAnchoredObject::ResetLastCharRectHeight() +{ + maLastCharRect.Height( 0 ); +} + +void SwAnchoredObject::SetVertPosOrientFrame( const SwLayoutFrame& _rVertPosOrientFrame ) +{ + ClearVertPosOrientFrame(); + + mpVertPosOrientFrame = &_rVertPosOrientFrame; + const_cast<SwLayoutFrame*>(mpVertPosOrientFrame)->SetVertPosOrientFrameFor(this); + + // #i28701# - take over functionality of deleted method + // <SwFlyAtContentFrame::AssertPage()>: assure for at-paragraph and at-character + // an anchored object, that it is registered at the correct page frame + RegisterAtCorrectPage(); +} + + +// #i28701# - follow-up of #i22341# +void SwAnchoredObject::AddLastTopOfLineY( SwTwips _nDiff ) +{ + mnLastTopOfLine += _nDiff; +} + +/** check anchor character rectangle and top of line + + #i26791 + For to-character anchored Writer fly frames the members <maLastCharRect> + and <maLastTopOfLine> are updated. These are checked for change and + depending on the applied positioning, it's decided, if the Writer fly + frame has to be invalidated. + + add parameter <_bCheckForParaPorInf>, default value <true> +*/ +void SwAnchoredObject::CheckCharRectAndTopOfLine( + const bool _bCheckForParaPorInf ) +{ + if ( !(GetAnchorFrame() && + GetAnchorFrame()->IsTextFrame()) ) + return; + + const SwFormatAnchor& rAnch = GetFrameFormat().GetAnchor(); + if ( !((rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR) && + rAnch.GetContentAnchor()) ) + return; + + // --> if requested, assure that anchor frame, + // which contains the anchor character, has a paragraph portion information. + // The paragraph portion information is needed to determine the + // anchor character rectangle respectively the top of the line. + // Thus, a format of this frame is avoided to determine the + // paragraph portion information. + // --> #i26945# - use new method <FindAnchorCharFrame()> + const SwTextFrame& aAnchorCharFrame = *(FindAnchorCharFrame()); + if ( !_bCheckForParaPorInf || aAnchorCharFrame.HasPara() ) + { + CheckCharRect( rAnch, aAnchorCharFrame ); + CheckTopOfLine( rAnch, aAnchorCharFrame ); + } +} + +/** check anchor character rectangle + + #i22341# + helper method for method <CheckCharRectAndTopOfLine()> + For to-character anchored Writer fly frames the member <maLastCharRect> + is updated. This is checked for change and depending on the applied + positioning, it's decided, if the Writer fly frame has to be invalidated. + + improvement - add second parameter <_rAnchorCharFrame> +*/ +void SwAnchoredObject::CheckCharRect( const SwFormatAnchor& _rAnch, + const SwTextFrame& _rAnchorCharFrame ) +{ + // determine rectangle of anchor character. If not exist, abort operation + SwRect aCharRect; + if ( !_rAnchorCharFrame.GetAutoPos( aCharRect, *_rAnch.GetContentAnchor() ) ) + { + return; + } + // check, if anchor character rectangle has changed + if ( aCharRect == maLastCharRect ) + return; + + // check positioning and alignment for invalidation of position + { + SwRectFnSet aRectFnSet(&_rAnchorCharFrame); + // determine positioning and alignment + SwFormatVertOrient aVert( GetFrameFormat().GetVertOrient() ); + SwFormatHoriOrient aHori( GetFrameFormat().GetHoriOrient() ); + // check for anchor character rectangle changes for certain + // positionings and alignments + // add condition to invalidate position, + // if vertical aligned at frame/page area and vertical position + // of anchor character has changed. + const sal_Int16 eVertRelOrient = aVert.GetRelationOrient(); + if ( ( aHori.GetRelationOrient() == text::RelOrientation::CHAR && + aRectFnSet.GetLeft(aCharRect) != aRectFnSet.GetLeft(maLastCharRect) ) || + ( eVertRelOrient == text::RelOrientation::CHAR && + ( aRectFnSet.GetTop(aCharRect) != aRectFnSet.GetTop(maLastCharRect) || + aRectFnSet.GetHeight(aCharRect) != aRectFnSet.GetHeight(maLastCharRect) ) ) || + ( ( ( eVertRelOrient == text::RelOrientation::FRAME ) || + ( eVertRelOrient == text::RelOrientation::PRINT_AREA ) || + ( eVertRelOrient == text::RelOrientation::PAGE_FRAME ) || + ( eVertRelOrient == text::RelOrientation::PAGE_PRINT_AREA ) ) && + ( aRectFnSet.GetTop(aCharRect) != aRectFnSet.GetTop(maLastCharRect) ) ) ) + { + // #i26945#, #i35911# - unlock position of + // anchored object, if it isn't registered at the page, + // where its anchor character frame is on. + if ( GetPageFrame() != _rAnchorCharFrame.FindPageFrame() ) + { + UnlockPosition(); + } + InvalidateObjPos(); + } + } + // keep new anchor character rectangle + maLastCharRect = aCharRect; +} + +/** check top of line + + #i22341# + helper method for method <CheckCharRectAndTopOfLine()> + For to-character anchored Writer fly frames the member <mnLastTopOfLine> + is updated. This is checked for change and depending on the applied + positioning, it's decided, if the Writer fly frame has to be invalidated. + + improvement - add second parameter <_rAnchorCharFrame> +*/ +void SwAnchoredObject::CheckTopOfLine( const SwFormatAnchor& _rAnch, + const SwTextFrame& _rAnchorCharFrame ) +{ + SwTwips nTopOfLine = 0; + if ( !_rAnchorCharFrame.GetTopOfLine( nTopOfLine, *_rAnch.GetContentAnchor() ) ) + return; + + if ( nTopOfLine == mnLastTopOfLine ) + return; + + // check alignment for invalidation of position + if ( GetFrameFormat().GetVertOrient().GetRelationOrient() == text::RelOrientation::TEXT_LINE ) + { + // #i26945#, #i35911# - unlock position of + // anchored object, if it isn't registered at the page, + // where its anchor character frame is on. + if ( GetPageFrame() != _rAnchorCharFrame.FindPageFrame() ) + { + UnlockPosition(); + } + InvalidateObjPos(); + } + // keep new top of line value + mnLastTopOfLine = nTopOfLine; +} + +void SwAnchoredObject::ClearCharRectAndTopOfLine() +{ + maLastCharRect.Clear(); + mnLastTopOfLine = 0; +} + +void SwAnchoredObject::SetCurrRelPos( Point _aRelPos ) +{ + maRelPos = _aRelPos; +} + +void SwAnchoredObject::ObjectAttachedToAnchorFrame() +{ + // default behaviour: + // update layout direction, the anchored object is assigned to + UpdateLayoutDir(); +} + +/** method update layout direction the layout direction, the anchored + object is in + + #i31698# + method has typically to be called, if the anchored object gets its + anchor frame assigned. +*/ +void SwAnchoredObject::UpdateLayoutDir() +{ + SwFrameFormat::tLayoutDir nLayoutDir = SwFrameFormat::HORI_L2R; + const SwFrame* pAnchorFrame = GetAnchorFrame(); + if ( pAnchorFrame ) + { + const bool bVert = pAnchorFrame->IsVertical(); + const bool bR2L = pAnchorFrame->IsRightToLeft(); + if ( bVert ) + { + nLayoutDir = SwFrameFormat::VERT_R2L; + } + else if ( bR2L ) + { + nLayoutDir = SwFrameFormat::HORI_R2L; + } + } + GetFrameFormat().SetLayoutDir( nLayoutDir ); +} + +/** method to perform necessary invalidations for the positioning of + objects, for whose the wrapping style influence has to be considered + on the object positioning. + + #i28701# +*/ +void SwAnchoredObject::InvalidateObjPosForConsiderWrapInfluence() +{ + if ( ConsiderObjWrapInfluenceOnObjPos() ) + { + // indicate that object has not to be considered for text wrap + SetConsiderForTextWrap( false ); + // unlock position + UnlockPosition(); + // invalidate position + InvalidateObjPos(); + // invalidate 'background' + NotifyBackground( GetPageFrame(), GetObjRectWithSpaces(), PrepareHint::FlyFrameLeave ); + } +} + +/** method to determine, if wrapping style influence of the anchored + object has to be considered on the object positioning + + #i28701# + Note: result of this method also decides, if the booleans for the + layout process are of relevance. +*/ +bool SwAnchoredObject::ConsiderObjWrapInfluenceOnObjPos() const +{ + bool bRet( false ); + + const SwFrameFormat& rObjFormat = GetFrameFormat(); + + // --> #i3317# - add condition <IsTmpConsiderWrapInfluence()> + // --> #i55204# + // - correction: wrapping style influence has been considered, if condition + // <IsTmpConsiderWrapInfluence()> is hold, regardless of its anchor type + // or its wrapping style. + if ( IsTmpConsiderWrapInfluence() ) + { + bRet = true; + } + else if ( rObjFormat.getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) + { + const SwFormatAnchor& rAnchor = rObjFormat.GetAnchor(); + if ( ((rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) || + (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA)) && + rObjFormat.GetSurround().GetSurround() != css::text::WrapTextMode_THROUGH ) + { + // --> #i34520# - text also wraps around anchored + // objects in the layer Hell - see the text formatting. + // Thus, it hasn't to be checked here. + bRet = true; + } + } + + return bRet; +} + +/** method to determine, if other anchored objects, also attached at + to the anchor frame, have to consider its wrap influence. + + // --> #i43255# +*/ +bool SwAnchoredObject::ConsiderObjWrapInfluenceOfOtherObjs() const +{ + bool bRet( false ); + + const SwSortedObjs* pObjs = GetAnchorFrame()->GetDrawObjs(); + if ( pObjs->size() > 1 ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( pAnchoredObj != this && + pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos() ) + { + bRet = true; + break; + } + } + } + + return bRet; +} + +bool SwAnchoredObject::ConsiderForTextWrap() const +{ + if ( ConsiderObjWrapInfluenceOnObjPos() ) + return mbConsiderForTextWrap; + else + return true; +} + +void SwAnchoredObject::SetConsiderForTextWrap( const bool _bConsiderForTextWrap ) +{ + mbConsiderForTextWrap = _bConsiderForTextWrap; +} + +bool SwAnchoredObject::PositionLocked() const +{ + if ( ConsiderObjWrapInfluenceOnObjPos() ) + return mbPositionLocked; + else + return false; +} + +bool SwAnchoredObject::RestartLayoutProcess() const +{ + if ( ConsiderObjWrapInfluenceOnObjPos() ) + return mbRestartLayoutProcess; + else + return false; +} + +void SwAnchoredObject::SetRestartLayoutProcess( const bool _bRestartLayoutProcess ) +{ + mbRestartLayoutProcess = _bRestartLayoutProcess; +} + +// --> #i35911# +bool SwAnchoredObject::ClearedEnvironment() const +{ + if ( ConsiderObjWrapInfluenceOnObjPos() ) + return mbClearedEnvironment; + else + return false; +} +void SwAnchoredObject::SetClearedEnvironment( const bool _bClearedEnvironment ) +{ + mbClearedEnvironment = _bClearedEnvironment; +} + +/** method to determine, if due to anchored object size and wrapping + style, its layout environment is cleared. + + #i35911# +*/ +bool SwAnchoredObject::HasClearedEnvironment() const +{ + bool bHasClearedEnvironment( false ); + + // --> #i43913# - layout frame, vertical position is orient at, has to be set. + OSL_ENSURE( GetVertPosOrientFrame(), + "<SwAnchoredObject::HasClearedEnvironment()> - layout frame missing, at which the vertical position is oriented at." ); + if ( GetVertPosOrientFrame() && + GetAnchorFrame()->IsTextFrame() && + !static_cast<const SwTextFrame*>(GetAnchorFrame())->IsFollow() && + static_cast<const SwTextFrame*>(GetAnchorFrame())->FindPageFrame()->GetPhyPageNum() >= + GetPageFrame()->GetPhyPageNum() ) + { + const SwFrame* pTmpFrame = GetVertPosOrientFrame()->Lower(); + while ( pTmpFrame && pTmpFrame->IsLayoutFrame() && !pTmpFrame->IsTabFrame() ) + { + pTmpFrame = static_cast<const SwLayoutFrame*>(pTmpFrame)->Lower(); + } + if ( !pTmpFrame ) + { + bHasClearedEnvironment = true; + } + else if ( pTmpFrame->IsTextFrame() && !pTmpFrame->GetNext() ) + { + const SwTextFrame* pTmpTextFrame = static_cast<const SwTextFrame*>(pTmpFrame); + if ( pTmpTextFrame->IsUndersized() || + ( pTmpTextFrame->GetFollow() && + pTmpTextFrame->GetFollow()->GetOffset() == TextFrameIndex(0))) + { + bHasClearedEnvironment = true; + } + } + } + + return bHasClearedEnvironment; +} + +/** method to add spacing to object area + + #i28701# + #i68520# - return constant reference and use cache +*/ +const SwRect& SwAnchoredObject::GetObjRectWithSpaces() const +{ + if ( mbObjRectWithSpacesValid && + maLastObjRect != GetObjRect() ) + { + OSL_FAIL( "<SwAnchoredObject::GetObjRectWithSpaces> - cache for object rectangle inclusive spaces marked as valid, but it couldn't be. Missing invalidation of cache." ); + InvalidateObjRectWithSpaces(); + } + if ( !mbObjRectWithSpacesValid ) + { + maObjRectWithSpaces = GetObjBoundRect(); + const SwFrameFormat& rFormat = GetFrameFormat(); + const SvxULSpaceItem& rUL = rFormat.GetULSpace(); + const SvxLRSpaceItem& rLR = rFormat.GetLRSpace(); + { + maObjRectWithSpaces.Top ( std::max( maObjRectWithSpaces.Top() - tools::Long(rUL.GetUpper()), tools::Long(0) )); + maObjRectWithSpaces.Left( std::max( maObjRectWithSpaces.Left()- rLR.GetLeft(), tools::Long(0) )); + maObjRectWithSpaces.AddHeight(rUL.GetLower() ); + maObjRectWithSpaces.AddWidth(rLR.GetRight() ); + } + + mbObjRectWithSpacesValid = true; + maLastObjRect = GetObjRect(); + } + + return maObjRectWithSpaces; +} + +// --> #i68520# +void SwAnchoredObject::SetObjTop( const SwTwips _nTop) +{ + const bool bTopChanged( SetObjTop_( _nTop ) ); + if ( bTopChanged ) + { + mbObjRectWithSpacesValid = false; + } +} + +void SwAnchoredObject::SetObjLeft( const SwTwips _nLeft) +{ + const bool bLeftChanged( SetObjLeft_( _nLeft ) ); + if ( bLeftChanged ) + { + mbObjRectWithSpacesValid = false; + } +} + +/** method to update anchored object in the <SwSortedObjs> lists + + #i28701# + If document compatibility option 'Consider wrapping style influence + on object positioning' is ON, additionally all anchored objects + at the anchor frame and all following anchored objects on the page + frame are invalidated. +*/ + +void SwAnchoredObject::UpdateObjInSortedList() +{ + if(!GetAnchorFrame()) + return; + + if ( GetFrameFormat().getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) + { + // invalidate position of all anchored objects at anchor frame + if ( GetAnchorFrame()->GetDrawObjs() ) + { + const SwSortedObjs* pObjs = GetAnchorFrame()->GetDrawObjs(); + for(auto it = pObjs->begin(); it != pObjs->end(); ++it) + { + SwAnchoredObject* pAnchoredObj = *it; + if(pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos()) + pAnchoredObj->InvalidateObjPosForConsiderWrapInfluence(); + else + pAnchoredObj->InvalidateObjPos(); + } + } + // invalidate all following anchored objects on the page frame + if ( GetPageFrame() && GetPageFrame()->GetSortedObjs() ) + { + const SwSortedObjs* pObjs = GetPageFrame()->GetSortedObjs(); + // determine start index + for ( size_t i = pObjs->ListPosOf( *this ) + 1; i < pObjs->size(); ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pObjs)[i]; + if ( pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos() ) + pAnchoredObj->InvalidateObjPosForConsiderWrapInfluence(); + else + pAnchoredObj->InvalidateObjPos(); + } + } + } + // update its position in the sorted object list of its anchor frame + AnchorFrame()->GetDrawObjs()->Update( *this ); + // update its position in the sorted object list of its page frame + // note: as-character anchored object aren't registered at a page frame + if ( GetPageFrame() && GetPageFrame()->GetSortedObjs() && + GetFrameFormat().GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR ) + { + GetPageFrame()->GetSortedObjs()->Update( *this ); + } +} + +/** method to determine, if invalidation of position is allowed + + #i28701# +*/ +bool SwAnchoredObject::InvalidationOfPosAllowed() const +{ + // --> Check, if page frame layout is in progress, + // isn't needed, because of anchored object, whose are moved forward. + return !PositionLocked(); +} + +/** method to determine the page frame, on which the 'anchor' of + the given anchored object is. + + #i28701# + #i33751#, #i34060# + Adjust meaning of method and thus its name: If the anchored object + or its anchor isn't correctly inserted in the layout, no page frame + can be found. Thus, the return type changed to be a pointer and can + be NULL. +*/ +SwPageFrame* SwAnchoredObject::FindPageFrameOfAnchor() +{ + SwPageFrame* pRetPageFrame = nullptr; + + // --> #i44339# - check, if anchor frame exists. + if ( mpAnchorFrame ) + { + // --> #i26945# - use new method <GetAnchorFrameContainingAnchPos()> + pRetPageFrame = GetAnchorFrameContainingAnchPos()->FindPageFrame(); + } + + return pRetPageFrame; +} + +/** get frame, which contains the anchor character, if the object + is anchored at-character or as-character. + + #i26945# + + @return SwTextFrame* + text frame containing the anchor character. It's NULL, if the object + isn't anchored at-character resp. as-character. +*/ +SwTextFrame* SwAnchoredObject::FindAnchorCharFrame() +{ + SwTextFrame* pAnchorCharFrame( nullptr ); + + // --> #i44339# - check, if anchor frame exists. + if ( mpAnchorFrame ) + { + const SwFormatAnchor& rAnch = GetFrameFormat().GetAnchor(); + if ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR) || + (rAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR)) + { + SwTextFrame *const pFrame(static_cast<SwTextFrame*>(AnchorFrame())); + TextFrameIndex const nOffset(pFrame->MapModelToViewPos(*rAnch.GetContentAnchor())); + pAnchorCharFrame = &pFrame->GetFrameAtOfst(nOffset); + } + } + + return pAnchorCharFrame; +} + +/** method to determine, if a format on the anchored object is possible + + #i28701# + A format is possible, if anchored object is in an invisible layer. + Note: method is virtual to refine the conditions for the sub-classes. +*/ +bool SwAnchoredObject::IsFormatPossible() const +{ + return GetFrameFormat().GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( GetDrawObj()->GetLayer() ); +} + +bool SwAnchoredObject::IsDraggingOffPageAllowed(const SwFrameFormat* pFrameFormat) +{ + OSL_ASSERT(pFrameFormat); + const bool bDisablePositioning = pFrameFormat->getIDocumentSettingAccess().get(DocumentSettingId::DISABLE_OFF_PAGE_POSITIONING); + const bool bIsWrapThrough = pFrameFormat->GetSurround().GetSurround() == text::WrapTextMode::WrapTextMode_THROUGH; + + return bDisablePositioning && bIsWrapThrough; +} + +// --> #i3317# +void SwAnchoredObject::SetTmpConsiderWrapInfluence( const bool _bTmpConsiderWrapInfluence ) +{ + mbTmpConsiderWrapInfluence = _bTmpConsiderWrapInfluence; + // --> #i35911# + if ( mbTmpConsiderWrapInfluence ) + { + SwLayouter::InsertObjForTmpConsiderWrapInfluence( *(GetFrameFormat().GetDoc()), + *this ); + } +} + +void SwAnchoredObject::ClearTmpConsiderWrapInfluence() +{ + mbTmpConsiderWrapInfluence = false; + mbClearedEnvironment = false; + SetClearedEnvironment( false ); + SwLayouter::RemoveObjForTmpConsiderWrapInfluence( *(GetFrameFormat().GetDoc()), + *this ); +} +void SwAnchoredObject::SetTmpConsiderWrapInfluenceOfOtherObjs() +{ + const SwSortedObjs* pObjs = GetAnchorFrame()->GetDrawObjs(); + if ( pObjs->size() > 1 ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( pAnchoredObj != this ) + { + pAnchoredObj->SetTmpConsiderWrapInfluence( true/*bTmpConsiderWrapInfluence*/ ); + } + } + } +} + +/** method to determine, if the anchored object is overlapping with a + previous column + + #i3317# + overlapping with a previous column means, that the object overlaps + with a column, which is a previous one of the column its anchor + frame is in. + Only applied for at-paragraph and at-character anchored objects. +*/ +bool SwAnchoredObject::OverlapsPrevColumn() const +{ + bool bOverlapsPrevColumn( false ); + + if ( mpAnchorFrame && mpAnchorFrame->IsTextFrame() ) + { + const SwFrame* pColFrame = mpAnchorFrame->FindColFrame(); + if ( pColFrame && pColFrame->GetPrev() ) + { + const SwFrame* pTmpColFrame = pColFrame->GetPrev(); + SwRect aChkRect; + while ( pTmpColFrame ) + { + aChkRect.Union( pTmpColFrame->getFrameArea() ); + pTmpColFrame = pTmpColFrame->GetPrev(); + } + bOverlapsPrevColumn = GetObjRect().Overlaps( aChkRect ); + } + } + + return bOverlapsPrevColumn; +} + +/** method to determine position of anchored object relative to + anchor frame + + #i30669# + Usage: Needed layout information for WW8 export +*/ +Point SwAnchoredObject::GetRelPosToAnchorFrame() const +{ + Point aRelPos; + + assert(GetAnchorFrame()); + aRelPos = GetObjRect().Pos(); + aRelPos -= GetAnchorFrame()->getFrameArea().Pos(); + + return aRelPos; +} + +/** method to determine position of anchored object relative to + page frame + + #i30669# + Usage: Needed layout information for WW8 export + #i33818# - add parameters <_bFollowTextFlow> and + <_obRelToTableCell> + If <_bFollowTextFlow> is set and object is anchored inside table, + the position relative to the table cell is determined. Output + parameter <_obRelToTableCell> reflects this situation +*/ +Point SwAnchoredObject::GetRelPosToPageFrame( const bool _bFollowTextFlow, + bool& _obRelToTableCell ) const +{ + Point aRelPos; + _obRelToTableCell = false; + + assert(GetAnchorFrame()); + assert(GetAnchorFrame()->FindPageFrame()); + + aRelPos = GetObjRect().Pos(); + // --> #i33818# - search for cell frame, if object has to + // follow the text flow. + const SwFrame* pFrame( nullptr ); + if ( _bFollowTextFlow && !GetAnchorFrame()->IsPageFrame() ) + { + pFrame = GetAnchorFrame()->GetUpper(); + while ( !pFrame->IsCellFrame() && !pFrame->IsPageFrame() ) + { + pFrame = pFrame->GetUpper(); + } + } + else + { + pFrame = GetAnchorFrame()->FindPageFrame(); + } + if ( pFrame->IsCellFrame() ) + { + aRelPos -= pFrame->getFrameArea().Pos() + pFrame->getFramePrintArea().Pos(); + _obRelToTableCell = true; + } + else + { + aRelPos -= pFrame->getFrameArea().Pos(); + } + + return aRelPos; +} + +/** method to determine position of anchored object relative to + anchor character + + #i30669# + Usage: Needed layout information for WW8 export +*/ +Point SwAnchoredObject::GetRelPosToChar() const +{ + Point aRelPos = GetObjRect().Pos(); + aRelPos -= GetLastCharRect().Pos(); + + return aRelPos; +} + +/** method to determine position of anchored object relative to + top of line + + #i30669# + Usage: Needed layout information for WW8 export +*/ +Point SwAnchoredObject::GetRelPosToLine() const +{ + Point aRelPos = GetObjRect().Pos(); + aRelPos.AdjustY( -(GetLastTopOfLine()) ); + + return aRelPos; +} + +const SwFlyFrame* SwAnchoredObject::DynCastFlyFrame() const +{ + return nullptr; +} + +SwFlyFrame* SwAnchoredObject::DynCastFlyFrame() +{ + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/atrfrm.cxx b/sw/source/core/layout/atrfrm.cxx new file mode 100644 index 000000000..df2d8e974 --- /dev/null +++ b/sw/source/core/layout/atrfrm.cxx @@ -0,0 +1,3711 @@ +/* -*- 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 <sal/config.h> + +#include <com/sun/star/style/VerticalAlignment.hpp> +#include <com/sun/star/text/ColumnSeparatorStyle.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/text/TextGridMode.hpp> +#include <com/sun/star/text/XTextColumns.hpp> +#include <sal/log.hxx> +#include <o3tl/any.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <svtools/unoimap.hxx> +#include <tools/UnitConversion.hxx> +#include <vcl/imap.hxx> +#include <vcl/imapobj.hxx> +#include <unotools/intlwrapper.hxx> +#include <unotools/syslocale.hxx> +#include <frmfmt.hxx> +#include <unocoll.hxx> +#include <fmtclds.hxx> +#include <fmtornt.hxx> +#include <fmthdft.hxx> +#include <fmtpdsc.hxx> +#include <fmtcntnt.hxx> +#include <fmtfsize.hxx> +#include <fmtfordr.hxx> +#include <fmtsrnd.hxx> +#include <fmtlsplt.hxx> +#include <fmtrowsplt.hxx> +#include <fmtftntx.hxx> +#include <fmteiro.hxx> +#include <fmturl.hxx> +#include <fmtcnct.hxx> +#include <section.hxx> +#include <fmtline.hxx> +#include <tgrditem.hxx> +#include <hfspacingitem.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentContentOperations.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <cntfrm.hxx> +#include <notxtfrm.hxx> +#include <txtfrm.hxx> +#include <crsrsh.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <frmtool.hxx> +#include <flyfrms.hxx> +#include <pagedesc.hxx> +#include <grfatr.hxx> +#include <ndnotxt.hxx> +#include <node2lay.hxx> +#include <fmtclbl.hxx> +#include <swunohelper.hxx> +#include <unoframe.hxx> +#include <SwStyleNameMapper.hxx> +#include <editeng/brushitem.hxx> +#include <vcl/GraphicObject.hxx> +#include <unomid.h> +#include <strings.hrc> +#include <svx/svdundo.hxx> +#include <svx/SvxXTextColumns.hxx> +#include <sortedobjs.hxx> +#include <HandleAnchorNodeChg.hxx> +#include <calbck.hxx> +#include <pagedeschint.hxx> +#include <drawdoc.hxx> +#include <hints.hxx> +#include <frameformats.hxx> +#include <unoprnms.hxx> + +#include <ndtxt.hxx> + +#include <svx/sdr/attribute/sdrallfillattributeshelper.hxx> +#include <svl/itemiter.hxx> +#include <wrtsh.hxx> +#include <txtfld.hxx> +#include <cellatr.hxx> + +using namespace ::com::sun::star; + +namespace sw { + +bool GetAtPageRelOrientation(sal_Int16 & rOrientation, bool const isIgnorePrintArea) +{ + switch (rOrientation) + { + case text::RelOrientation::CHAR: + case text::RelOrientation::FRAME: + rOrientation = text::RelOrientation::PAGE_FRAME; + return true; + case text::RelOrientation::PRINT_AREA: + if (isIgnorePrintArea) + { + return false; + } + else + { + rOrientation = text::RelOrientation::PAGE_PRINT_AREA; + return true; + } + case text::RelOrientation::FRAME_LEFT: + rOrientation = text::RelOrientation::PAGE_LEFT; + return true; + case text::RelOrientation::FRAME_RIGHT: + rOrientation = text::RelOrientation::PAGE_RIGHT; + return true; + default: + return false; + } +} + +} // namespace sw + +SfxPoolItem* SwFormatLineNumber::CreateDefault() { return new SwFormatLineNumber; } + +static sal_Int16 lcl_IntToRelation(const uno::Any& rVal) +{ + sal_Int16 nVal = text::RelOrientation::FRAME; + if (!(rVal >>= nVal)) + SAL_WARN("sw.core", "lcl_IntToRelation: read from Any failed!"); + return nVal; +} + +static void lcl_DelHFFormat( SwClient *pToRemove, SwFrameFormat *pFormat ) +{ + //If the client is the last one who uses this format, then we have to delete + //it - before this is done, we may need to delete the content-section. + SwDoc* pDoc = pFormat->GetDoc(); + pFormat->Remove( pToRemove ); + if( pDoc->IsInDtor() ) + { + delete pFormat; + return; + } + + // Anything other than frames registered? + bool bDel = true; + { + // nested scope because DTOR of SwClientIter resets the flag bTreeChg. + // It's suboptimal if the format is deleted beforehand. + SwIterator<SwClient,SwFrameFormat> aIter(*pFormat); + for(SwClient* pLast = aIter.First(); bDel && pLast; pLast = aIter.Next()) + if (dynamic_cast<const SwFrame*>(pLast) == nullptr) + bDel = false; + } + + if ( !bDel ) + return; + + // If there is a Cursor registered in one of the nodes, we need to call the + // ParkCursor in an (arbitrary) shell. + SwFormatContent& rCnt = const_cast<SwFormatContent&>(pFormat->GetContent()); + if ( rCnt.GetContentIdx() ) + { + SwNode *pNode = nullptr; + { + // #i92993# + // Begin with start node of page header/footer to assure that + // complete content is checked for cursors and the complete content + // is deleted on below made method call <pDoc->getIDocumentContentOperations().DeleteSection(pNode)> + SwNodeIndex aIdx( *rCnt.GetContentIdx(), 0 ); + // If there is a Cursor registered in one of the nodes, we need to call the + // ParkCursor in an (arbitrary) shell. + pNode = & aIdx.GetNode(); + SwNodeOffset nEnd = pNode->EndOfSectionIndex(); + while ( aIdx < nEnd ) + { + if ( pNode->IsContentNode() && + static_cast<SwContentNode*>(pNode)->HasWriterListeners() ) + { + SwCursorShell *pShell = SwIterator<SwCursorShell,SwContentNode>( *static_cast<SwContentNode*>(pNode) ).First(); + if( pShell ) + { + pShell->ParkCursor( aIdx ); + aIdx = nEnd-1; + } + } + ++aIdx; + pNode = & aIdx.GetNode(); + } + } + rCnt.SetNewContentIdx( nullptr ); + + // When deleting a header/footer-format, we ALWAYS need to disable + // the undo function (Bug 31069) + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + OSL_ENSURE( pNode, "A big problem." ); + pDoc->getIDocumentContentOperations().DeleteSection( pNode ); + } + delete pFormat; +} + +void SwFormatFrameSize::ScaleMetrics(tools::Long lMult, tools::Long lDiv) { + // Don't inherit the SvxSizeItem override (might or might not be relevant; added "just in case" + // when changing SwFormatFrameSize to derive from SvxSizeItem instead of directly from + // SfxPoolItem): + return SfxPoolItem::ScaleMetrics(lMult, lDiv); +} + +bool SwFormatFrameSize::HasMetrics() const { + // Don't inherit the SvxSizeItem override (might or might not be relevant; added "just in case" + // when changing SwFormatFrameSize to derive from SvxSizeItem instead of directly from + // SfxPoolItem): + return SfxPoolItem::HasMetrics(); +} + +// Partially implemented inline in hxx +SwFormatFrameSize::SwFormatFrameSize( SwFrameSize eSize, SwTwips nWidth, SwTwips nHeight ) + : SvxSizeItem( RES_FRM_SIZE, {nWidth, nHeight} ), + m_eFrameHeightType( eSize ), + m_eFrameWidthType( SwFrameSize::Fixed ) +{ + m_nWidthPercent = m_eWidthPercentRelation = m_nHeightPercent = m_eHeightPercentRelation = 0; +} + +bool SwFormatFrameSize::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return( m_eFrameHeightType == static_cast<const SwFormatFrameSize&>(rAttr).m_eFrameHeightType && + m_eFrameWidthType == static_cast<const SwFormatFrameSize&>(rAttr).m_eFrameWidthType && + SvxSizeItem::operator==(rAttr)&& + m_nWidthPercent == static_cast<const SwFormatFrameSize&>(rAttr).GetWidthPercent() && + m_eWidthPercentRelation == static_cast<const SwFormatFrameSize&>(rAttr).GetWidthPercentRelation() && + m_nHeightPercent == static_cast<const SwFormatFrameSize&>(rAttr).GetHeightPercent() && + m_eHeightPercentRelation == static_cast<const SwFormatFrameSize&>(rAttr).GetHeightPercentRelation() ); +} + +SwFormatFrameSize* SwFormatFrameSize::Clone( SfxItemPool* ) const +{ + return new SwFormatFrameSize( *this ); +} + +bool SwFormatFrameSize::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_FRMSIZE_SIZE: + { + awt::Size aTmp; + aTmp.Height = convertTwipToMm100(GetHeight()); + aTmp.Width = convertTwipToMm100(GetWidth()); + rVal <<= aTmp; + } + break; + case MID_FRMSIZE_REL_HEIGHT: + rVal <<= static_cast<sal_Int16>(GetHeightPercent() != SwFormatFrameSize::SYNCED ? GetHeightPercent() : 0); + break; + case MID_FRMSIZE_REL_HEIGHT_RELATION: + rVal <<= GetHeightPercentRelation(); + break; + case MID_FRMSIZE_REL_WIDTH: + rVal <<= static_cast<sal_Int16>(GetWidthPercent() != SwFormatFrameSize::SYNCED ? GetWidthPercent() : 0); + break; + case MID_FRMSIZE_REL_WIDTH_RELATION: + rVal <<= GetWidthPercentRelation(); + break; + case MID_FRMSIZE_IS_SYNC_HEIGHT_TO_WIDTH: + rVal <<= SwFormatFrameSize::SYNCED == GetHeightPercent(); + break; + case MID_FRMSIZE_IS_SYNC_WIDTH_TO_HEIGHT: + rVal <<= SwFormatFrameSize::SYNCED == GetWidthPercent(); + break; + case MID_FRMSIZE_WIDTH : + rVal <<= static_cast<sal_Int32>(convertTwipToMm100(GetWidth())); + break; + case MID_FRMSIZE_HEIGHT: + // #95848# returned size should never be zero. + // (there was a bug that allowed for setting height to 0. + // Thus there some documents existing with that not allowed + // attribute value which may cause problems on import.) + rVal <<= static_cast<sal_Int32>(convertTwipToMm100(GetHeight() < MINLAY ? MINLAY : GetHeight() )); + break; + case MID_FRMSIZE_SIZE_TYPE: + rVal <<= static_cast<sal_Int16>(GetHeightSizeType()); + break; + case MID_FRMSIZE_IS_AUTO_HEIGHT: + rVal <<= SwFrameSize::Fixed != GetHeightSizeType(); + break; + case MID_FRMSIZE_WIDTH_TYPE: + rVal <<= static_cast<sal_Int16>(GetWidthSizeType()); + break; + } + return true; +} + +bool SwFormatFrameSize::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0 != (nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_FRMSIZE_SIZE: + { + awt::Size aVal; + if(!(rVal >>= aVal)) + bRet = false; + else + { + Size aTmp(aVal.Width, aVal.Height); + if(bConvert) + { + aTmp.setHeight(o3tl::toTwips(aTmp.Height(), o3tl::Length::mm100)); + aTmp.setWidth(o3tl::toTwips(aTmp.Width(), o3tl::Length::mm100)); + } + SetSize(aTmp); + } + } + break; + case MID_FRMSIZE_REL_HEIGHT: + { + sal_Int16 nSet = 0; + rVal >>= nSet; + if(nSet >= 0 && nSet < SwFormatFrameSize::SYNCED) + SetHeightPercent(static_cast<sal_uInt8>(nSet)); + else + bRet = false; + } + break; + case MID_FRMSIZE_REL_HEIGHT_RELATION: + { + sal_Int16 eSet = 0; + rVal >>= eSet; + SetHeightPercentRelation(eSet); + } + break; + case MID_FRMSIZE_REL_WIDTH: + { + sal_Int16 nSet = 0; + rVal >>= nSet; + if(nSet >= 0 && nSet < SwFormatFrameSize::SYNCED) + SetWidthPercent(static_cast<sal_uInt8>(nSet)); + else + bRet = false; + } + break; + case MID_FRMSIZE_REL_WIDTH_RELATION: + { + sal_Int16 eSet = 0; + rVal >>= eSet; + SetWidthPercentRelation(eSet); + } + break; + case MID_FRMSIZE_IS_SYNC_HEIGHT_TO_WIDTH: + { + bool bSet = *o3tl::doAccess<bool>(rVal); + if(bSet) + SetHeightPercent(SwFormatFrameSize::SYNCED); + else if( SwFormatFrameSize::SYNCED == GetHeightPercent() ) + SetHeightPercent( 0 ); + } + break; + case MID_FRMSIZE_IS_SYNC_WIDTH_TO_HEIGHT: + { + bool bSet = *o3tl::doAccess<bool>(rVal); + if(bSet) + SetWidthPercent(SwFormatFrameSize::SYNCED); + else if( SwFormatFrameSize::SYNCED == GetWidthPercent() ) + SetWidthPercent(0); + } + break; + case MID_FRMSIZE_WIDTH : + { + sal_Int32 nWd = 0; + if(rVal >>= nWd) + { + if(bConvert) + nWd = o3tl::toTwips(nWd, o3tl::Length::mm100); + if(nWd < MINLAY) + nWd = MINLAY; + SetWidth(nWd); + } + else + bRet = false; + } + break; + case MID_FRMSIZE_HEIGHT: + { + sal_Int32 nHg = 0; + if(rVal >>= nHg) + { + if(bConvert) + nHg = o3tl::toTwips(nHg, o3tl::Length::mm100); + if(nHg < MINLAY) + nHg = MINLAY; + SetHeight(nHg); + } + else + bRet = false; + } + break; + case MID_FRMSIZE_SIZE_TYPE: + { + sal_Int16 nType = 0; + if((rVal >>= nType) && nType >= 0 && nType <= static_cast<int>(SwFrameSize::Minimum) ) + { + SetHeightSizeType(static_cast<SwFrameSize>(nType)); + } + else + bRet = false; + } + break; + case MID_FRMSIZE_IS_AUTO_HEIGHT: + { + bool bSet = *o3tl::doAccess<bool>(rVal); + SetHeightSizeType(bSet ? SwFrameSize::Variable : SwFrameSize::Fixed); + } + break; + case MID_FRMSIZE_WIDTH_TYPE: + { + sal_Int16 nType = 0; + if((rVal >>= nType) && nType >= 0 && nType <= static_cast<int>(SwFrameSize::Minimum) ) + { + SetWidthSizeType(static_cast<SwFrameSize>(nType)); + } + else + bRet = false; + } + break; + default: + bRet = false; + } + return bRet; +} + +void SwFormatFrameSize::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatFrameSize")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + + std::stringstream aSize; + aSize << GetSize(); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("size"), BAD_CAST(aSize.str().c_str())); + + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eFrameHeightType"), BAD_CAST(OString::number(static_cast<int>(m_eFrameHeightType)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eFrameWidthType"), BAD_CAST(OString::number(static_cast<int>(m_eFrameWidthType)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nWidthPercent"), BAD_CAST(OString::number(m_nWidthPercent).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eWidthPercentRelation"), BAD_CAST(OString::number(m_eWidthPercentRelation).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nHeightPercent"), BAD_CAST(OString::number(m_nHeightPercent).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eHeightPercentRelation"), BAD_CAST(OString::number(m_eHeightPercentRelation).getStr())); + + (void)xmlTextWriterEndElement(pWriter); +} + +// Partially implemented inline in hxx +SwFormatFillOrder::SwFormatFillOrder( SwFillOrder nFO ) + : SfxEnumItem( RES_FILL_ORDER, nFO ) +{} + +SwFormatFillOrder* SwFormatFillOrder::Clone( SfxItemPool* ) const +{ + return new SwFormatFillOrder( GetValue() ); +} + +sal_uInt16 SwFormatFillOrder::GetValueCount() const +{ + return SW_FILL_ORDER_END - SW_FILL_ORDER_BEGIN; +} + +// Partially implemented inline in hxx +SwFormatHeader::SwFormatHeader( SwFrameFormat *pHeaderFormat ) + : SfxPoolItem( RES_HEADER ), + SwClient( pHeaderFormat ), + m_bActive( pHeaderFormat ) +{ +} + +SwFormatHeader::SwFormatHeader( const SwFormatHeader &rCpy ) + : SfxPoolItem( RES_HEADER ), + SwClient( const_cast<sw::BroadcastingModify*>(static_cast<const sw::BroadcastingModify*>(rCpy.GetRegisteredIn())) ), + m_bActive( rCpy.IsActive() ) +{ +} + +SwFormatHeader::SwFormatHeader( bool bOn ) + : SfxPoolItem( RES_HEADER ), + SwClient( nullptr ), + m_bActive( bOn ) +{ +} + + SwFormatHeader::~SwFormatHeader() +{ + if ( GetHeaderFormat() ) + lcl_DelHFFormat( this, GetHeaderFormat() ); +} + +bool SwFormatHeader::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return ( GetRegisteredIn() == static_cast<const SwFormatHeader&>(rAttr).GetRegisteredIn() && + m_bActive == static_cast<const SwFormatHeader&>(rAttr).IsActive() ); +} + +SwFormatHeader* SwFormatHeader::Clone( SfxItemPool* ) const +{ + return new SwFormatHeader( *this ); +} + +void SwFormatHeader::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add(this); +} + +// Partially implemented inline in hxx +SwFormatFooter::SwFormatFooter( SwFrameFormat *pFooterFormat ) + : SfxPoolItem( RES_FOOTER ), + SwClient( pFooterFormat ), + m_bActive( pFooterFormat ) +{ +} + +SwFormatFooter::SwFormatFooter( const SwFormatFooter &rCpy ) + : SfxPoolItem( RES_FOOTER ), + SwClient( const_cast<sw::BroadcastingModify*>(static_cast<const sw::BroadcastingModify*>(rCpy.GetRegisteredIn())) ), + m_bActive( rCpy.IsActive() ) +{ +} + +SwFormatFooter::SwFormatFooter( bool bOn ) + : SfxPoolItem( RES_FOOTER ), + SwClient( nullptr ), + m_bActive( bOn ) +{ +} + + SwFormatFooter::~SwFormatFooter() +{ + if ( GetFooterFormat() ) + lcl_DelHFFormat( this, GetFooterFormat() ); +} + +void SwFormatFooter::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add(this); +} + +bool SwFormatFooter::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return ( GetRegisteredIn() == static_cast<const SwFormatFooter&>(rAttr).GetRegisteredIn() && + m_bActive == static_cast<const SwFormatFooter&>(rAttr).IsActive() ); +} + +SwFormatFooter* SwFormatFooter::Clone( SfxItemPool* ) const +{ + return new SwFormatFooter( *this ); +} + +// Partially implemented inline in hxx +SwFormatContent::SwFormatContent( const SwFormatContent &rCpy ) + : SfxPoolItem( RES_CNTNT ) +{ + m_pStartNode.reset( rCpy.GetContentIdx() ? + new SwNodeIndex( *rCpy.GetContentIdx() ) : nullptr); +} + +SwFormatContent::SwFormatContent( const SwStartNode *pStartNd ) + : SfxPoolItem( RES_CNTNT ) +{ + m_pStartNode.reset( pStartNd ? new SwNodeIndex( *pStartNd ) : nullptr); +} + +SwFormatContent::~SwFormatContent() +{ +} + +void SwFormatContent::SetNewContentIdx( const SwNodeIndex *pIdx ) +{ + m_pStartNode.reset( pIdx ? new SwNodeIndex( *pIdx ) : nullptr ); +} + +bool SwFormatContent::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + if( static_cast<bool>(m_pStartNode) != static_cast<bool>(static_cast<const SwFormatContent&>(rAttr).m_pStartNode) ) + return false; + if( m_pStartNode ) + return ( *m_pStartNode == *static_cast<const SwFormatContent&>(rAttr).GetContentIdx() ); + return true; +} + +SwFormatContent* SwFormatContent::Clone( SfxItemPool* ) const +{ + return new SwFormatContent( *this ); +} + +void SwFormatContent::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatContent")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + if (m_pStartNode) + { + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("startNode"), + BAD_CAST(OString::number(sal_Int32(m_pStartNode->GetNode().GetIndex())).getStr())); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("startNodePtr"), "%p", + &m_pStartNode->GetNode()); + } + (void)xmlTextWriterEndElement(pWriter); +} + +// Partially implemented inline in hxx +SwFormatPageDesc::SwFormatPageDesc( const SwFormatPageDesc &rCpy ) + : SfxPoolItem( RES_PAGEDESC ), + SwClient( const_cast<SwPageDesc*>(rCpy.GetPageDesc()) ), + m_oNumOffset( rCpy.m_oNumOffset ), + m_pDefinedIn( nullptr ) +{ +} + +SwFormatPageDesc::SwFormatPageDesc( const SwPageDesc *pDesc ) + : SfxPoolItem( RES_PAGEDESC ), + SwClient( const_cast<SwPageDesc*>(pDesc) ), + m_pDefinedIn( nullptr ) +{ +} + +SwFormatPageDesc &SwFormatPageDesc::operator=(const SwFormatPageDesc &rCpy) +{ + if(this == &rCpy) + return *this; + + if (rCpy.GetPageDesc()) + RegisterToPageDesc(*const_cast<SwPageDesc*>(rCpy.GetPageDesc())); + m_oNumOffset = rCpy.m_oNumOffset; + m_pDefinedIn = nullptr; + + return *this; +} + + SwFormatPageDesc::~SwFormatPageDesc() {} + +bool SwFormatPageDesc::KnowsPageDesc() const +{ + return (GetRegisteredIn() != nullptr); +} + +bool SwFormatPageDesc::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return ( m_pDefinedIn == static_cast<const SwFormatPageDesc&>(rAttr).m_pDefinedIn ) && + ( m_oNumOffset == static_cast<const SwFormatPageDesc&>(rAttr).m_oNumOffset ) && + ( GetPageDesc() == static_cast<const SwFormatPageDesc&>(rAttr).GetPageDesc() ); +} + +SwFormatPageDesc* SwFormatPageDesc::Clone( SfxItemPool* ) const +{ + return new SwFormatPageDesc( *this ); +} + +void SwFormatPageDesc::SwClientNotify(const SwModify&, const SfxHint& rHint) +{ + if (const SwPageDescHint* pHint = dynamic_cast<const SwPageDescHint*>(&rHint)) + { + // mba: shouldn't that be broadcasted also? + SwFormatPageDesc aDfltDesc(pHint->GetPageDesc()); + SwPageDesc* pDesc = pHint->GetPageDesc(); + const sw::BroadcastingModify* pMod = GetDefinedIn(); + if(pMod) + { + if(auto pContentNode = dynamic_cast<const SwContentNode*>(pMod)) + const_cast<SwContentNode*>(pContentNode)->SetAttr(aDfltDesc); + else if(auto pFormat = dynamic_cast<const SwFormat*>(pMod)) + const_cast<SwFormat*>(pFormat)->SetFormatAttr( aDfltDesc ); + else + { + SAL_WARN("sw.core", "SwFormatPageDesc registered at " << typeid(pMod).name() << "."); + RegisterToPageDesc(*pDesc); + } + } + else + // there could be an Undo-copy + RegisterToPageDesc(*pDesc); + } + else if (rHint.GetId() == SfxHintId::SwLegacyModify) + { + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + if(RES_OBJECTDYING == pLegacy->GetWhich()) + { + m_pDefinedIn = nullptr; + EndListeningAll(); + } + } +} + +void SwFormatPageDesc::RegisterToPageDesc( SwPageDesc& rDesc ) +{ + rDesc.Add( this ); +} + +bool SwFormatPageDesc::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_PAGEDESC_PAGENUMOFFSET: + { + ::std::optional<sal_uInt16> oOffset = GetNumOffset(); + if (oOffset) + { + rVal <<= static_cast<sal_Int16>(*oOffset); + } + else + { + rVal.clear(); + } + } + break; + + case MID_PAGEDESC_PAGEDESCNAME: + { + const SwPageDesc* pDesc = GetPageDesc(); + if( pDesc ) + { + OUString aString; + SwStyleNameMapper::FillProgName(pDesc->GetName(), aString, SwGetPoolIdFromName::PageDesc); + rVal <<= aString; + } + else + rVal.clear(); + } + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatPageDesc::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_PAGEDESC_PAGENUMOFFSET: + { + sal_Int16 nOffset = 0; + if (!rVal.hasValue()) + { + SetNumOffset(std::nullopt); + } + else if (rVal >>= nOffset) + SetNumOffset( nOffset ); + else + bRet = false; + } + break; + + case MID_PAGEDESC_PAGEDESCNAME: + /* Doesn't work, because the attribute doesn't need the name but a + * pointer to the PageDesc (it's a client of it). The pointer can + * only be requested from the document using the name. + */ + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +void SwFormatPageDesc::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatPageDesc")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + if (m_oNumOffset) + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("oNumOffset"), BAD_CAST(OString::number(*m_oNumOffset).getStr())); + else + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("oNumOffset"), BAD_CAST("none")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("pPageDesc"), "%p", GetPageDesc()); + if (const SwPageDesc* pPageDesc = GetPageDesc()) + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"), BAD_CAST(pPageDesc->GetName().toUtf8().getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SwFormatCol +// Partially implemented inline in hxx + +SwColumn::SwColumn() : + m_nWish ( 0 ), + m_nLeft ( 0 ), + m_nRight( 0 ) +{ +} + +bool SwColumn::operator==( const SwColumn &rCmp ) const +{ + return m_nWish == rCmp.GetWishWidth() && + GetLeft() == rCmp.GetLeft() && + GetRight() == rCmp.GetRight(); +} + +void SwColumn::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwColumn")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nWish"), BAD_CAST(OString::number(m_nWish).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nUpper"), BAD_CAST(OString::number(0).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nLower"), BAD_CAST(OString::number(0).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nLeft"), BAD_CAST(OString::number(m_nLeft).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nRight"), BAD_CAST(OString::number(m_nRight).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +SwFormatCol::SwFormatCol( const SwFormatCol& rCpy ) + : SfxPoolItem( RES_COL ), + m_eLineStyle( rCpy.m_eLineStyle ), + m_nLineWidth( rCpy.m_nLineWidth), + m_aLineColor( rCpy.m_aLineColor), + m_nLineHeight( rCpy.GetLineHeight() ), + m_eAdj( rCpy.GetLineAdj() ), + m_nWidth( rCpy.GetWishWidth() ), + m_aWidthAdjustValue( rCpy.m_aWidthAdjustValue ), + m_bOrtho( rCpy.IsOrtho() ) +{ + m_aColumns.reserve(rCpy.GetNumCols()); + for ( sal_uInt16 i = 0; i < rCpy.GetNumCols(); ++i ) + { + m_aColumns.emplace_back(rCpy.GetColumns()[i] ); + } +} + +SwFormatCol::~SwFormatCol() {} + +SwFormatCol& SwFormatCol::operator=( const SwFormatCol& rCpy ) +{ + if (this != &rCpy) + { + m_eLineStyle = rCpy.m_eLineStyle; + m_nLineWidth = rCpy.m_nLineWidth; + m_aLineColor = rCpy.m_aLineColor; + m_nLineHeight = rCpy.GetLineHeight(); + m_eAdj = rCpy.GetLineAdj(); + m_nWidth = rCpy.GetWishWidth(); + m_aWidthAdjustValue = rCpy.m_aWidthAdjustValue; + m_bOrtho = rCpy.IsOrtho(); + + m_aColumns.clear(); + for ( sal_uInt16 i = 0; i < rCpy.GetNumCols(); ++i ) + { + m_aColumns.emplace_back(rCpy.GetColumns()[i] ); + } + } + return *this; +} + +SwFormatCol::SwFormatCol() + : SfxPoolItem( RES_COL ) + , m_eLineStyle( SvxBorderLineStyle::NONE) + , + m_nLineWidth(0), + m_nLineHeight( 100 ), + m_eAdj( COLADJ_NONE ), + m_nWidth( USHRT_MAX ), + m_aWidthAdjustValue( 0 ), + m_bOrtho( true ) +{ +} + +bool SwFormatCol::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + const SwFormatCol &rCmp = static_cast<const SwFormatCol&>(rAttr); + if( !(m_eLineStyle == rCmp.m_eLineStyle && + m_nLineWidth == rCmp.m_nLineWidth && + m_aLineColor == rCmp.m_aLineColor && + m_nLineHeight == rCmp.GetLineHeight() && + m_eAdj == rCmp.GetLineAdj() && + m_nWidth == rCmp.GetWishWidth() && + m_bOrtho == rCmp.IsOrtho() && + m_aColumns.size() == rCmp.GetNumCols() && + m_aWidthAdjustValue == rCmp.GetAdjustValue() + ) ) + return false; + + for ( size_t i = 0; i < m_aColumns.size(); ++i ) + if ( !(m_aColumns[i] == rCmp.GetColumns()[i]) ) + return false; + + return true; +} + +SwFormatCol* SwFormatCol::Clone( SfxItemPool* ) const +{ + return new SwFormatCol( *this ); +} + +sal_uInt16 SwFormatCol::GetGutterWidth( bool bMin ) const +{ + sal_uInt16 nRet = 0; + if ( m_aColumns.size() == 2 ) + nRet = m_aColumns[0].GetRight() + m_aColumns[1].GetLeft(); + else if ( m_aColumns.size() > 2 ) + { + bool bSet = false; + for ( size_t i = 1; i+1 < m_aColumns.size(); ++i ) + { + const sal_uInt16 nTmp = m_aColumns[i].GetRight() + m_aColumns[i+1].GetLeft(); + if ( bSet ) + { + if ( nTmp != nRet ) + { + if ( !bMin ) + return USHRT_MAX; + if ( nRet > nTmp ) + nRet = nTmp; + } + } + else + { + bSet = true; + nRet = nTmp; + } + } + } + return nRet; +} + +void SwFormatCol::SetGutterWidth( sal_uInt16 nNew, sal_uInt16 nAct ) +{ + if ( m_bOrtho ) + Calc( nNew, nAct ); + else + { + sal_uInt16 nHalf = nNew / 2; + for (size_t i = 0; i < m_aColumns.size(); ++i) + { + SwColumn &rCol = m_aColumns[i]; + rCol.SetLeft(nHalf); + rCol.SetRight(nHalf); + if ( i == 0 ) + rCol.SetLeft(0); + else if ( i+1 == m_aColumns.size() ) + rCol.SetRight(0); + } + } +} + +void SwFormatCol::Init( sal_uInt16 nNumCols, sal_uInt16 nGutterWidth, sal_uInt16 nAct ) +{ + // Deleting seems to be a bit radical on the first sight; but otherwise we + // have to initialize all values of the remaining SwColumns. + m_aColumns.clear(); + for ( sal_uInt16 i = 0; i < nNumCols; ++i ) + { + m_aColumns.emplace_back( ); + } + m_bOrtho = true; + m_nWidth = USHRT_MAX; + if( nNumCols ) + Calc( nGutterWidth, nAct ); +} + +void SwFormatCol::SetOrtho( bool bNew, sal_uInt16 nGutterWidth, sal_uInt16 nAct ) +{ + m_bOrtho = bNew; + if ( bNew && !m_aColumns.empty() ) + Calc( nGutterWidth, nAct ); +} + +sal_uInt16 SwFormatCol::CalcColWidth( sal_uInt16 nCol, sal_uInt16 nAct ) const +{ + assert(nCol < m_aColumns.size()); + if ( m_nWidth != nAct ) + { + tools::Long nW = m_aColumns[nCol].GetWishWidth(); + nW *= nAct; + nW /= m_nWidth; + return sal_uInt16(nW); + } + else + return m_aColumns[nCol].GetWishWidth(); +} + +sal_uInt16 SwFormatCol::CalcPrtColWidth( sal_uInt16 nCol, sal_uInt16 nAct ) const +{ + assert(nCol < m_aColumns.size()); + sal_uInt16 nRet = CalcColWidth( nCol, nAct ); + const SwColumn *pCol = &m_aColumns[nCol]; + nRet = nRet - pCol->GetLeft(); + nRet = nRet - pCol->GetRight(); + return nRet; +} + +void SwFormatCol::Calc( sal_uInt16 nGutterWidth, sal_uInt16 nAct ) +{ + if (!GetNumCols()) + return; + + //First set the column widths with the current width, then calculate the + //column's requested width using the requested total width. + const sal_uInt16 nGutterHalf = nGutterWidth ? nGutterWidth / 2 : 0; + + //Width of PrtAreas is totalwidth - spacings / count + sal_uInt16 nSpacings; + bool bFail = o3tl::checked_multiply<sal_uInt16>(GetNumCols() - 1, nGutterWidth, nSpacings); + if (bFail) + { + SAL_WARN("sw.core", "SwFormatVertOrient::Calc: overflow"); + return; + } + + const sal_uInt16 nPrtWidth = (nAct - nSpacings) / GetNumCols(); + sal_uInt16 nAvail = nAct; + + //The first column is PrtWidth + (gap width / 2) + const sal_uInt16 nLeftWidth = nPrtWidth + nGutterHalf; + SwColumn &rFirstCol = m_aColumns.front(); + rFirstCol.SetWishWidth(nLeftWidth); + rFirstCol.SetRight(nGutterHalf); + rFirstCol.SetLeft(0); + nAvail = nAvail - nLeftWidth; + + //Column 2 to n-1 is PrtWidth + gap width + const sal_uInt16 nMidWidth = nPrtWidth + nGutterWidth; + + for (sal_uInt16 i = 1; i < GetNumCols()-1; ++i) + { + SwColumn &rCol = m_aColumns[i]; + rCol.SetWishWidth(nMidWidth); + rCol.SetLeft(nGutterHalf); + rCol.SetRight(nGutterHalf); + nAvail = nAvail - nMidWidth; + } + + //The last column is equivalent to the first one - to compensate rounding + //errors we add the remaining space of the other columns to the last one. + SwColumn &rLastCol = m_aColumns.back(); + rLastCol.SetWishWidth(nAvail); + rLastCol.SetLeft(nGutterHalf); + rLastCol.SetRight(0); + + assert(nAct != 0); + //Convert the current width to the requested width. + for (SwColumn &rCol: m_aColumns) + { + tools::Long nTmp = rCol.GetWishWidth(); + nTmp *= GetWishWidth(); + nTmp = nAct == 0 ? nTmp : nTmp / nAct; + rCol.SetWishWidth(sal_uInt16(nTmp)); + } +} + +bool SwFormatCol::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + if(MID_COLUMN_SEPARATOR_LINE == nMemberId) + { + OSL_FAIL("not implemented"); + } + else + { + uno::Reference<text::XTextColumns> xCols(SvxXTextColumns_createInstance(), + css::uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xProps(xCols, css::uno::UNO_QUERY_THROW); + + if (GetNumCols() > 0) + { + xCols->setColumnCount(GetNumCols()); + const sal_uInt16 nItemGutterWidth = GetGutterWidth(); + sal_Int32 nAutoDistance = IsOrtho() ? USHRT_MAX == nItemGutterWidth + ? DEF_GUTTER_WIDTH + : static_cast<sal_Int32>(nItemGutterWidth) + : 0; + nAutoDistance = convertTwipToMm100(nAutoDistance); + xProps->setPropertyValue(UNO_NAME_AUTOMATIC_DISTANCE, uno::Any(nAutoDistance)); + + if (!IsOrtho()) + { + auto aTextColumns = xCols->getColumns(); + text::TextColumn* pColumns = aTextColumns.getArray(); + const SwColumns& rCols = GetColumns(); + for (sal_Int32 i = 0; i < aTextColumns.getLength(); ++i) + { + const SwColumn* pCol = &rCols[i]; + + pColumns[i].Width = pCol->GetWishWidth(); + pColumns[i].LeftMargin = convertTwipToMm100(pCol->GetLeft()); + pColumns[i].RightMargin = convertTwipToMm100(pCol->GetRight()); + } + xCols->setColumns(aTextColumns); // sets "IsAutomatic" property to false + } + } + uno::Any aVal; + aVal <<= o3tl::narrowing<sal_Int32>( + o3tl::convert(GetLineWidth(), o3tl::Length::twip, o3tl::Length::mm100)); + xProps->setPropertyValue(UNO_NAME_SEPARATOR_LINE_WIDTH, aVal); + aVal <<= GetLineColor(); + xProps->setPropertyValue(UNO_NAME_SEPARATOR_LINE_COLOR, aVal); + aVal <<= static_cast<sal_Int32>(GetLineHeight()); + xProps->setPropertyValue(UNO_NAME_SEPARATOR_LINE_RELATIVE_HEIGHT, aVal); + aVal <<= GetLineAdj() != COLADJ_NONE; + xProps->setPropertyValue(UNO_NAME_SEPARATOR_LINE_IS_ON, aVal); + sal_Int16 nStyle; + switch (GetLineStyle()) + { + case SvxBorderLineStyle::SOLID: + nStyle = css::text::ColumnSeparatorStyle::SOLID; + break; + case SvxBorderLineStyle::DOTTED: + nStyle = css::text::ColumnSeparatorStyle::DOTTED; + break; + case SvxBorderLineStyle::DASHED: + nStyle = css::text::ColumnSeparatorStyle::DASHED; + break; + case SvxBorderLineStyle::NONE: + default: + nStyle = css::text::ColumnSeparatorStyle::NONE; + break; + } + aVal <<= nStyle; + xProps->setPropertyValue(UNO_NAME_SEPARATOR_LINE_STYLE, aVal); + style::VerticalAlignment eAlignment; + switch (GetLineAdj()) + { + case COLADJ_TOP: + eAlignment = style::VerticalAlignment_TOP; + break; + case COLADJ_BOTTOM: + eAlignment = style::VerticalAlignment_BOTTOM; + break; + case COLADJ_CENTER: + case COLADJ_NONE: + default: + eAlignment = style::VerticalAlignment_MIDDLE; + } + aVal <<= eAlignment; + xProps->setPropertyValue(UNO_NAME_SEPARATOR_LINE_VERTIVAL_ALIGNMENT, aVal); + rVal <<= xCols; + } + return true; +} + +bool SwFormatCol::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = false; + if(MID_COLUMN_SEPARATOR_LINE == nMemberId) + { + OSL_FAIL("not implemented"); + } + else + { + uno::Reference< text::XTextColumns > xCols; + rVal >>= xCols; + if(xCols.is()) + { + uno::Sequence<text::TextColumn> aSetColumns = xCols->getColumns(); + const text::TextColumn* pArray = aSetColumns.getConstArray(); + m_aColumns.clear(); + //max count is 64k here - this is something the array can't do + sal_uInt16 nCount = std::min( o3tl::narrowing<sal_uInt16>(aSetColumns.getLength()), + sal_uInt16(0x3fff) ); + sal_uInt16 nWidthSum = 0; + // #101224# one column is no column + + if(nCount > 1) + for(sal_uInt16 i = 0; i < nCount; i++) + { + SwColumn aCol; + aCol.SetWishWidth(pArray[i].Width ); + nWidthSum = nWidthSum + pArray[i].Width; + aCol.SetLeft (o3tl::toTwips(pArray[i].LeftMargin, o3tl::Length::mm100)); + aCol.SetRight(o3tl::toTwips(pArray[i].RightMargin, o3tl::Length::mm100)); + m_aColumns.insert(m_aColumns.begin() + i, aCol); + } + bRet = true; + m_nWidth = nWidthSum; + m_bOrtho = false; + + if (uno::Reference<beans::XPropertySet> xProps{ xCols, css::uno::UNO_QUERY }) + { + xProps->getPropertyValue(UNO_NAME_IS_AUTOMATIC) >>= m_bOrtho; + xProps->getPropertyValue(UNO_NAME_SEPARATOR_LINE_WIDTH) >>= m_nLineWidth; + m_nLineWidth = o3tl::toTwips(m_nLineWidth, o3tl::Length::mm100); + xProps->getPropertyValue(UNO_NAME_SEPARATOR_LINE_COLOR) >>= m_aLineColor; + if (sal_Int32 nHeight; + xProps->getPropertyValue(UNO_NAME_SEPARATOR_LINE_RELATIVE_HEIGHT) >>= nHeight) + m_nLineHeight = nHeight; + switch (xProps->getPropertyValue(UNO_NAME_SEPARATOR_LINE_STYLE).get<sal_Int16>()) + { + default: + case css::text::ColumnSeparatorStyle::NONE: + m_eLineStyle = SvxBorderLineStyle::NONE; + break; + case css::text::ColumnSeparatorStyle::SOLID: + m_eLineStyle = SvxBorderLineStyle::SOLID; + break; + case css::text::ColumnSeparatorStyle::DOTTED: + m_eLineStyle = SvxBorderLineStyle::DOTTED; + break; + case css::text::ColumnSeparatorStyle::DASHED: + m_eLineStyle = SvxBorderLineStyle::DASHED; + break; + } + if (!xProps->getPropertyValue(UNO_NAME_SEPARATOR_LINE_IS_ON).get<bool>()) + m_eAdj = COLADJ_NONE; + else switch (xProps->getPropertyValue(UNO_NAME_SEPARATOR_LINE_VERTIVAL_ALIGNMENT).get<style::VerticalAlignment>()) + { + case style::VerticalAlignment_TOP: m_eAdj = COLADJ_TOP; break; + case style::VerticalAlignment_MIDDLE: m_eAdj = COLADJ_CENTER; break; + case style::VerticalAlignment_BOTTOM: m_eAdj = COLADJ_BOTTOM; break; + default: OSL_ENSURE( false, "unknown alignment" ); break; + } + } + } + } + return bRet; +} + +void SwFormatCol::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatCol")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eLineStyle"), BAD_CAST(OString::number(static_cast<sal_Int16>(m_eLineStyle)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nLineWidth"), BAD_CAST(OString::number(m_nLineWidth).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("aLineColor"), BAD_CAST(m_aLineColor.AsRGBHexString().toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nLineHeight"), BAD_CAST(OString::number(m_nLineHeight).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eAdj"), BAD_CAST(OString::number(m_eAdj).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nWidth"), BAD_CAST(OString::number(m_nWidth).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nWidthAdjustValue"), BAD_CAST(OString::number(m_aWidthAdjustValue).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bOrtho"), BAD_CAST(OString::boolean(m_bOrtho).getStr())); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("aColumns")); + for (const SwColumn& rColumn : m_aColumns) + rColumn.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +// Partially implemented inline in hxx +SwFormatSurround::SwFormatSurround( css::text::WrapTextMode eFly ) : + SfxEnumItem( RES_SURROUND, eFly ) +{ + m_bAnchorOnly = m_bContour = m_bOutside = false; +} + +bool SwFormatSurround::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return ( GetValue() == static_cast<const SwFormatSurround&>(rAttr).GetValue() && + m_bAnchorOnly== static_cast<const SwFormatSurround&>(rAttr).m_bAnchorOnly && + m_bContour== static_cast<const SwFormatSurround&>(rAttr).m_bContour && + m_bOutside== static_cast<const SwFormatSurround&>(rAttr).m_bOutside ); +} + +SwFormatSurround* SwFormatSurround::Clone( SfxItemPool* ) const +{ + return new SwFormatSurround( *this ); +} + +sal_uInt16 SwFormatSurround::GetValueCount() const +{ + return 6; +} + +bool SwFormatSurround::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_SURROUND_SURROUNDTYPE: + rVal <<= GetSurround(); + break; + case MID_SURROUND_ANCHORONLY: + rVal <<= IsAnchorOnly(); + break; + case MID_SURROUND_CONTOUR: + rVal <<= IsContour(); + break; + case MID_SURROUND_CONTOUROUTSIDE: + rVal <<= IsOutside(); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatSurround::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_SURROUND_SURROUNDTYPE: + { + css::text::WrapTextMode eVal = static_cast<css::text::WrapTextMode>(SWUnoHelper::GetEnumAsInt32( rVal )); + if( eVal >= css::text::WrapTextMode_NONE && eVal <= css::text::WrapTextMode_RIGHT ) + SetValue( eVal ); + else { + //exception + ; + } + } + break; + + case MID_SURROUND_ANCHORONLY: + SetAnchorOnly( *o3tl::doAccess<bool>(rVal) ); + break; + case MID_SURROUND_CONTOUR: + SetContour( *o3tl::doAccess<bool>(rVal) ); + break; + case MID_SURROUND_CONTOUROUTSIDE: + SetOutside( *o3tl::doAccess<bool>(rVal) ); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +void SwFormatSurround::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatSurround")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::number(static_cast<sal_Int32>(GetValue())).getStr())); + + OUString aPresentation; + IntlWrapper aIntlWrapper(SvtSysLocale().GetUILanguageTag()); + GetPresentation(SfxItemPresentation::Nameless, MapUnit::Map100thMM, MapUnit::Map100thMM, aPresentation, aIntlWrapper); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"), BAD_CAST(aPresentation.toUtf8().getStr())); + + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bAnchorOnly"), BAD_CAST(OString::boolean(m_bAnchorOnly).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bContour"), BAD_CAST(OString::boolean(m_bContour).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bOutside"), BAD_CAST(OString::boolean(m_bOutside).getStr())); + + (void)xmlTextWriterEndElement(pWriter); +} + +// Partially implemented inline in hxx +SwFormatVertOrient::SwFormatVertOrient( SwTwips nY, sal_Int16 eVert, + sal_Int16 eRel ) + : SfxPoolItem( RES_VERT_ORIENT ), + m_nYPos( nY ), + m_eOrient( eVert ), + m_eRelation( eRel ) +{} + +bool SwFormatVertOrient::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return ( m_nYPos == static_cast<const SwFormatVertOrient&>(rAttr).m_nYPos && + m_eOrient == static_cast<const SwFormatVertOrient&>(rAttr).m_eOrient && + m_eRelation == static_cast<const SwFormatVertOrient&>(rAttr).m_eRelation ); +} + +SwFormatVertOrient* SwFormatVertOrient::Clone( SfxItemPool* ) const +{ + return new SwFormatVertOrient( *this ); +} + +bool SwFormatVertOrient::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_VERTORIENT_ORIENT: + { + rVal <<= m_eOrient; + } + break; + case MID_VERTORIENT_RELATION: + rVal <<= m_eRelation; + break; + case MID_VERTORIENT_POSITION: + rVal <<= static_cast<sal_Int32>(convertTwipToMm100(GetPos())); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatVertOrient::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0 != (nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_VERTORIENT_ORIENT: + { + sal_uInt16 nVal = text::VertOrientation::NONE; + rVal >>= nVal; + m_eOrient = nVal; + } + break; + case MID_VERTORIENT_RELATION: + { + m_eRelation = lcl_IntToRelation(rVal); + } + break; + case MID_VERTORIENT_POSITION: + { + sal_Int32 nVal = 0; + rVal >>= nVal; + if(bConvert) + nVal = o3tl::toTwips(nVal, o3tl::Length::mm100); + SetPos( nVal ); + } + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +void SwFormatVertOrient::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatVertOrient")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nYPos"), BAD_CAST(OString::number(m_nYPos).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eOrient"), BAD_CAST(OString::number(m_eOrient).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eRelation"), BAD_CAST(OString::number(m_eRelation).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// Partially implemented inline in hxx +SwFormatHoriOrient::SwFormatHoriOrient( SwTwips nX, sal_Int16 eHori, + sal_Int16 eRel, bool bPos ) + : SfxPoolItem( RES_HORI_ORIENT ), + m_nXPos( nX ), + m_eOrient( eHori ), + m_eRelation( eRel ), + m_bPosToggle( bPos ) +{} + +bool SwFormatHoriOrient::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return ( m_nXPos == static_cast<const SwFormatHoriOrient&>(rAttr).m_nXPos && + m_eOrient == static_cast<const SwFormatHoriOrient&>(rAttr).m_eOrient && + m_eRelation == static_cast<const SwFormatHoriOrient&>(rAttr).m_eRelation && + m_bPosToggle == static_cast<const SwFormatHoriOrient&>(rAttr).m_bPosToggle ); +} + +SwFormatHoriOrient* SwFormatHoriOrient::Clone( SfxItemPool* ) const +{ + return new SwFormatHoriOrient( *this ); +} + +bool SwFormatHoriOrient::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_HORIORIENT_ORIENT: + { + rVal <<= m_eOrient; + } + break; + case MID_HORIORIENT_RELATION: + rVal <<= m_eRelation; + break; + case MID_HORIORIENT_POSITION: + rVal <<= static_cast<sal_Int32>(convertTwipToMm100(GetPos())); + break; + case MID_HORIORIENT_PAGETOGGLE: + rVal <<= IsPosToggle(); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatHoriOrient::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0 != (nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_HORIORIENT_ORIENT: + { + sal_Int16 nVal = text::HoriOrientation::NONE; + rVal >>= nVal; + m_eOrient = nVal; + } + break; + case MID_HORIORIENT_RELATION: + { + m_eRelation = lcl_IntToRelation(rVal); + } + break; + case MID_HORIORIENT_POSITION: + { + sal_Int32 nVal = 0; + if(!(rVal >>= nVal)) + bRet = false; + if(bConvert) + nVal = o3tl::toTwips(nVal, o3tl::Length::mm100); + SetPos( nVal ); + } + break; + case MID_HORIORIENT_PAGETOGGLE: + SetPosToggle( *o3tl::doAccess<bool>(rVal)); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +void SwFormatHoriOrient::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatHoriOrient")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nXPos"), BAD_CAST(OString::number(m_nXPos).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eOrient"), BAD_CAST(OString::number(m_eOrient).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eRelation"), BAD_CAST(OString::number(m_eRelation).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bPosToggle"), BAD_CAST(OString::boolean(m_bPosToggle).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// Partially implemented inline in hxx +SwFormatAnchor::SwFormatAnchor( RndStdIds nRnd, sal_uInt16 nPage ) + : SfxPoolItem( RES_ANCHOR ), + m_eAnchorId( nRnd ), + m_nPageNumber( nPage ), + // OD 2004-05-05 #i28701# - get always new increased order number + m_nOrder( ++s_nOrderCounter ) +{} + +SwFormatAnchor::SwFormatAnchor( const SwFormatAnchor &rCpy ) + : SfxPoolItem( RES_ANCHOR ) + , m_pContentAnchor( (rCpy.GetContentAnchor()) + ? new SwPosition( *rCpy.GetContentAnchor() ) : nullptr ) + , m_eAnchorId( rCpy.GetAnchorId() ) + , m_nPageNumber( rCpy.GetPageNum() ) + // OD 2004-05-05 #i28701# - get always new increased order number + , m_nOrder( ++s_nOrderCounter ) +{ +} + +SwFormatAnchor::~SwFormatAnchor() +{ +} + +void SwFormatAnchor::SetAnchor( const SwPosition *pPos ) +{ + // anchor only to paragraphs, or start nodes in case of RndStdIds::FLY_AT_FLY + // also allow table node, this is used when a table is selected and is converted to a frame by the UI + assert(!pPos + || ((RndStdIds::FLY_AT_FLY == m_eAnchorId) && + dynamic_cast<SwStartNode*>(&pPos->nNode.GetNode())) + || (RndStdIds::FLY_AT_PARA == m_eAnchorId && dynamic_cast<SwTableNode*>(&pPos->nNode.GetNode())) + || dynamic_cast<SwTextNode*>(&pPos->nNode.GetNode())); + m_pContentAnchor .reset( pPos ? new SwPosition( *pPos ) : nullptr ); + // Flys anchored AT paragraph should not point into the paragraph content + if (m_pContentAnchor && + ((RndStdIds::FLY_AT_PARA == m_eAnchorId) || (RndStdIds::FLY_AT_FLY == m_eAnchorId))) + { + m_pContentAnchor->nContent.Assign( nullptr, 0 ); + } +} + +SwFormatAnchor& SwFormatAnchor::operator=(const SwFormatAnchor& rAnchor) +{ + if (this != &rAnchor) + { + m_eAnchorId = rAnchor.GetAnchorId(); + m_nPageNumber = rAnchor.GetPageNum(); + // OD 2004-05-05 #i28701# - get always new increased order number + m_nOrder = ++s_nOrderCounter; + + m_pContentAnchor.reset( (rAnchor.GetContentAnchor()) + ? new SwPosition(*(rAnchor.GetContentAnchor())) + : nullptr ); + } + return *this; +} + +bool SwFormatAnchor::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + SwFormatAnchor const& rFormatAnchor(static_cast<SwFormatAnchor const&>(rAttr)); + // OD 2004-05-05 #i28701# - Note: <mnOrder> hasn't to be considered. + return ( m_eAnchorId == rFormatAnchor.GetAnchorId() && + m_nPageNumber == rFormatAnchor.GetPageNum() && + // compare anchor: either both do not point into a textnode or + // both do (valid m_pContentAnchor) and the positions are equal + ((m_pContentAnchor.get() == rFormatAnchor.m_pContentAnchor.get()) || + (m_pContentAnchor && rFormatAnchor.GetContentAnchor() && + (*m_pContentAnchor == *rFormatAnchor.GetContentAnchor())))); +} + +SwFormatAnchor* SwFormatAnchor::Clone( SfxItemPool* ) const +{ + return new SwFormatAnchor( *this ); +} + +// OD 2004-05-05 #i28701# +sal_uInt32 SwFormatAnchor::s_nOrderCounter = 0; + +// OD 2004-05-05 #i28701# + +bool SwFormatAnchor::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_ANCHOR_ANCHORTYPE: + + text::TextContentAnchorType eRet; + switch (GetAnchorId()) + { + case RndStdIds::FLY_AT_CHAR: + eRet = text::TextContentAnchorType_AT_CHARACTER; + break; + case RndStdIds::FLY_AT_PAGE: + eRet = text::TextContentAnchorType_AT_PAGE; + break; + case RndStdIds::FLY_AT_FLY: + eRet = text::TextContentAnchorType_AT_FRAME; + break; + case RndStdIds::FLY_AS_CHAR: + eRet = text::TextContentAnchorType_AS_CHARACTER; + break; + //case RndStdIds::FLY_AT_PARA: + default: + eRet = text::TextContentAnchorType_AT_PARAGRAPH; + } + rVal <<= eRet; + break; + case MID_ANCHOR_PAGENUM: + rVal <<= static_cast<sal_Int16>(GetPageNum()); + break; + case MID_ANCHOR_ANCHORFRAME: + { + if (m_pContentAnchor && RndStdIds::FLY_AT_FLY == m_eAnchorId) + { + SwFrameFormat* pFormat = m_pContentAnchor->nNode.GetNode().GetFlyFormat(); + if(pFormat) + { + uno::Reference<text::XTextFrame> const xRet( + SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat)); + rVal <<= xRet; + } + } + } + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatAnchor::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_ANCHOR_ANCHORTYPE: + { + RndStdIds eAnchor; + switch( static_cast<text::TextContentAnchorType>(SWUnoHelper::GetEnumAsInt32( rVal )) ) + { + case text::TextContentAnchorType_AS_CHARACTER: + eAnchor = RndStdIds::FLY_AS_CHAR; + break; + case text::TextContentAnchorType_AT_PAGE: + eAnchor = RndStdIds::FLY_AT_PAGE; + if( GetPageNum() > 0 ) + { + // If the anchor type is page and a valid page number + // has been set, the content position isn't required + // any longer. + m_pContentAnchor.reset(); + } + break; + case text::TextContentAnchorType_AT_FRAME: + eAnchor = RndStdIds::FLY_AT_FLY; + break; + case text::TextContentAnchorType_AT_CHARACTER: + eAnchor = RndStdIds::FLY_AT_CHAR; + break; + //case text::TextContentAnchorType_AT_PARAGRAPH: + default: + eAnchor = RndStdIds::FLY_AT_PARA; + break; + } + SetType( eAnchor ); + } + break; + case MID_ANCHOR_PAGENUM: + { + sal_Int16 nVal = 0; + if((rVal >>= nVal) && nVal > 0) + { + SetPageNum( nVal ); + if (RndStdIds::FLY_AT_PAGE == GetAnchorId()) + { + // If the anchor type is page and a valid page number + // is set, the content position has to be deleted to not + // confuse the layout (frmtool.cxx). However, if the + // anchor type is not page, any content position will + // be kept. + m_pContentAnchor.reset(); + } + } + else + bRet = false; + } + break; + case MID_ANCHOR_ANCHORFRAME: + //no break here!; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +void SwFormatAnchor::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatAnchor")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + + if (m_pContentAnchor) + { + std::stringstream aContentAnchor; + aContentAnchor << *m_pContentAnchor; + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_pContentAnchor"), BAD_CAST(aContentAnchor.str().c_str())); + } + else + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("m_pContentAnchor"), "%p", m_pContentAnchor.get()); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_eAnchorType"), BAD_CAST(OString::number(static_cast<int>(m_eAnchorId)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nPageNumber"), BAD_CAST(OString::number(m_nPageNumber).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nOrder"), BAD_CAST(OString::number(m_nOrder).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("s_nOrderCounter"), BAD_CAST(OString::number(s_nOrderCounter).getStr())); + OUString aPresentation; + IntlWrapper aIntlWrapper(SvtSysLocale().GetUILanguageTag()); + GetPresentation(SfxItemPresentation::Nameless, MapUnit::Map100thMM, MapUnit::Map100thMM, aPresentation, aIntlWrapper); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"), BAD_CAST(aPresentation.toUtf8().getStr())); + + (void)xmlTextWriterEndElement(pWriter); +} + +// Partially implemented inline in hxx +SwFormatURL::SwFormatURL() : + SfxPoolItem( RES_URL ), + m_bIsServerMap( false ) +{ +} + +SwFormatURL::SwFormatURL( const SwFormatURL &rURL) : + SfxPoolItem( RES_URL ), + m_sTargetFrameName( rURL.GetTargetFrameName() ), + m_sURL( rURL.GetURL() ), + m_sName( rURL.GetName() ), + m_bIsServerMap( rURL.IsServerMap() ) +{ + if (rURL.GetMap()) + m_pMap.reset( new ImageMap( *rURL.GetMap() ) ); +} + +SwFormatURL::~SwFormatURL() +{ +} + +bool SwFormatURL::operator==( const SfxPoolItem &rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + const SwFormatURL &rCmp = static_cast<const SwFormatURL&>(rAttr); + bool bRet = m_bIsServerMap == rCmp.IsServerMap() && + m_sURL == rCmp.GetURL() && + m_sTargetFrameName == rCmp.GetTargetFrameName() && + m_sName == rCmp.GetName(); + if ( bRet ) + { + if ( m_pMap && rCmp.GetMap() ) + bRet = *m_pMap == *rCmp.GetMap(); + else + bRet = m_pMap.get() == rCmp.GetMap(); + } + return bRet; +} + +SwFormatURL* SwFormatURL::Clone( SfxItemPool* ) const +{ + return new SwFormatURL( *this ); +} + +void SwFormatURL::SetURL(const OUString &rURL, bool bServerMap) +{ + m_sURL = rURL; + m_bIsServerMap = bServerMap; +} + +void SwFormatURL::SetMap( const ImageMap *pM ) +{ + m_pMap.reset( pM ? new ImageMap( *pM ) : nullptr); +} + +bool SwFormatURL::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_URL_URL: + rVal <<= GetURL(); + break; + case MID_URL_TARGET: + rVal <<= GetTargetFrameName(); + break; + case MID_URL_HYPERLINKNAME: + rVal <<= GetName(); + break; + case MID_URL_CLIENTMAP: + { + uno::Reference< uno::XInterface > xInt; + if(m_pMap) + { + xInt = SvUnoImageMap_createInstance( *m_pMap, sw_GetSupportedMacroItems() ); + } + else + { + ImageMap aEmptyMap; + xInt = SvUnoImageMap_createInstance( aEmptyMap, sw_GetSupportedMacroItems() ); + } + uno::Reference< container::XIndexContainer > xCont(xInt, uno::UNO_QUERY); + rVal <<= xCont; + } + break; + case MID_URL_SERVERMAP: + rVal <<= IsServerMap(); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatURL::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_URL_URL: + { + OUString sTmp; + rVal >>= sTmp; + SetURL( sTmp, m_bIsServerMap ); + } + break; + case MID_URL_TARGET: + { + OUString sTmp; + rVal >>= sTmp; + SetTargetFrameName( sTmp ); + } + break; + case MID_URL_HYPERLINKNAME: + { + OUString sTmp; + rVal >>= sTmp; + SetName( sTmp ); + } + break; + case MID_URL_CLIENTMAP: + { + uno::Reference<container::XIndexContainer> xCont; + if(!rVal.hasValue()) + m_pMap.reset(); + else if(rVal >>= xCont) + { + if(!m_pMap) + m_pMap.reset(new ImageMap); + bRet = SvUnoImageMap_fillImageMap( xCont, *m_pMap ); + } + else + bRet = false; + } + break; + case MID_URL_SERVERMAP: + m_bIsServerMap = *o3tl::doAccess<bool>(rVal); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +SwFormatEditInReadonly* SwFormatEditInReadonly::Clone( SfxItemPool* ) const +{ + return new SwFormatEditInReadonly( *this ); +} + +SwFormatLayoutSplit* SwFormatLayoutSplit::Clone( SfxItemPool* ) const +{ + return new SwFormatLayoutSplit( *this ); +} + +SwFormatRowSplit* SwFormatRowSplit::Clone( SfxItemPool* ) const +{ + return new SwFormatRowSplit( *this ); +} + +SwFormatNoBalancedColumns* SwFormatNoBalancedColumns::Clone( SfxItemPool* ) const +{ + return new SwFormatNoBalancedColumns( *this ); +} + +void SwFormatNoBalancedColumns::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatNoBalancedColumns")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::boolean(GetValue()).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SwFormatFootnoteEndAtTextEnd + +sal_uInt16 SwFormatFootnoteEndAtTextEnd::GetValueCount() const +{ + return sal_uInt16( FTNEND_ATTXTEND_END ); +} + +SwFormatFootnoteEndAtTextEnd& SwFormatFootnoteEndAtTextEnd::operator=( + const SwFormatFootnoteEndAtTextEnd& rAttr ) +{ + SfxEnumItem::SetValue( rAttr.GetValue() ); + m_aFormat = rAttr.m_aFormat; + m_nOffset = rAttr.m_nOffset; + m_sPrefix = rAttr.m_sPrefix; + m_sSuffix = rAttr.m_sSuffix; + return *this; +} + +bool SwFormatFootnoteEndAtTextEnd::operator==( const SfxPoolItem& rItem ) const +{ + const SwFormatFootnoteEndAtTextEnd& rAttr = static_cast<const SwFormatFootnoteEndAtTextEnd&>(rItem); + return SfxEnumItem::operator==( rItem ) && + m_aFormat.GetNumberingType() == rAttr.m_aFormat.GetNumberingType() && + m_nOffset == rAttr.m_nOffset && + m_sPrefix == rAttr.m_sPrefix && + m_sSuffix == rAttr.m_sSuffix; +} + +bool SwFormatFootnoteEndAtTextEnd::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_COLLECT : + rVal <<= GetValue() >= FTNEND_ATTXTEND; + break; + case MID_RESTART_NUM : + rVal <<= GetValue() >= FTNEND_ATTXTEND_OWNNUMSEQ; + break; + case MID_NUM_START_AT: rVal <<= static_cast<sal_Int16>(m_nOffset); break; + case MID_OWN_NUM : + rVal <<= GetValue() >= FTNEND_ATTXTEND_OWNNUMANDFMT; + break; + case MID_NUM_TYPE : rVal <<= static_cast<sal_Int16>(m_aFormat.GetNumberingType()); break; + case MID_PREFIX : rVal <<= m_sPrefix; break; + case MID_SUFFIX : rVal <<= m_sSuffix; break; + default: return false; + } + return true; +} + +bool SwFormatFootnoteEndAtTextEnd::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bRet = true; + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_COLLECT : + { + bool bVal = *o3tl::doAccess<bool>(rVal); + if(!bVal && GetValue() >= FTNEND_ATTXTEND) + SetValue(FTNEND_ATPGORDOCEND); + else if(bVal && GetValue() < FTNEND_ATTXTEND) + SetValue(FTNEND_ATTXTEND); + } + break; + case MID_RESTART_NUM : + { + bool bVal = *o3tl::doAccess<bool>(rVal); + if(!bVal && GetValue() >= FTNEND_ATTXTEND_OWNNUMSEQ) + SetValue(FTNEND_ATTXTEND); + else if(bVal && GetValue() < FTNEND_ATTXTEND_OWNNUMSEQ) + SetValue(FTNEND_ATTXTEND_OWNNUMSEQ); + } + break; + case MID_NUM_START_AT: + { + sal_Int16 nVal = 0; + rVal >>= nVal; + if(nVal >= 0) + m_nOffset = nVal; + else + bRet = false; + } + break; + case MID_OWN_NUM : + { + bool bVal = *o3tl::doAccess<bool>(rVal); + if(!bVal && GetValue() >= FTNEND_ATTXTEND_OWNNUMANDFMT) + SetValue(FTNEND_ATTXTEND_OWNNUMSEQ); + else if(bVal && GetValue() < FTNEND_ATTXTEND_OWNNUMANDFMT) + SetValue(FTNEND_ATTXTEND_OWNNUMANDFMT); + } + break; + case MID_NUM_TYPE : + { + sal_Int16 nVal = 0; + rVal >>= nVal; + if(nVal >= 0 && + (nVal <= SVX_NUM_ARABIC || + SVX_NUM_CHARS_UPPER_LETTER_N == nVal || + SVX_NUM_CHARS_LOWER_LETTER_N == nVal )) + m_aFormat.SetNumberingType(static_cast<SvxNumType>(nVal)); + else + bRet = false; + } + break; + case MID_PREFIX : + { + OUString sVal; rVal >>= sVal; + m_sPrefix = sVal; + } + break; + case MID_SUFFIX : + { + OUString sVal; rVal >>= sVal; + m_sSuffix = sVal; + } + break; + default: bRet = false; + } + return bRet; +} + +// class SwFormatFootnoteAtTextEnd + +SwFormatFootnoteAtTextEnd* SwFormatFootnoteAtTextEnd::Clone( SfxItemPool* ) const +{ + return new SwFormatFootnoteAtTextEnd(*this); +} + +// class SwFormatEndAtTextEnd + +SwFormatEndAtTextEnd* SwFormatEndAtTextEnd::Clone( SfxItemPool* ) const +{ + return new SwFormatEndAtTextEnd(*this); +} + +//class SwFormatChain + +bool SwFormatChain::operator==( const SfxPoolItem &rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return GetPrev() == static_cast<const SwFormatChain&>(rAttr).GetPrev() && + GetNext() == static_cast<const SwFormatChain&>(rAttr).GetNext(); +} + +SwFormatChain::SwFormatChain( const SwFormatChain &rCpy ) : + SfxPoolItem( RES_CHAIN ) +{ + SetPrev( rCpy.GetPrev() ); + SetNext( rCpy.GetNext() ); +} + +SwFormatChain* SwFormatChain::Clone( SfxItemPool* ) const +{ + SwFormatChain *pRet = new SwFormatChain; + pRet->SetPrev( GetPrev() ); + pRet->SetNext( GetNext() ); + return pRet; +} + +void SwFormatChain::SetPrev( SwFlyFrameFormat *pFormat ) +{ + if ( pFormat ) + pFormat->Add( &m_aPrev ); + else + m_aPrev.EndListeningAll(); +} + +void SwFormatChain::SetNext( SwFlyFrameFormat *pFormat ) +{ + if ( pFormat ) + pFormat->Add( &m_aNext ); + else + m_aNext.EndListeningAll(); +} + +bool SwFormatChain::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + OUString aRet; + switch ( nMemberId ) + { + case MID_CHAIN_PREVNAME: + if ( GetPrev() ) + aRet = GetPrev()->GetName(); + break; + case MID_CHAIN_NEXTNAME: + if ( GetNext() ) + aRet = GetNext()->GetName(); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + rVal <<= aRet; + return bRet; +} + +SwFormatLineNumber::SwFormatLineNumber() : + SfxPoolItem( RES_LINENUMBER ) +{ + m_nStartValue = 0; + m_bCountLines = true; +} + +SwFormatLineNumber::~SwFormatLineNumber() +{ +} + +bool SwFormatLineNumber::operator==( const SfxPoolItem &rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return m_nStartValue == static_cast<const SwFormatLineNumber&>(rAttr).GetStartValue() && + m_bCountLines == static_cast<const SwFormatLineNumber&>(rAttr).IsCount(); +} + +SwFormatLineNumber* SwFormatLineNumber::Clone( SfxItemPool* ) const +{ + return new SwFormatLineNumber( *this ); +} + +bool SwFormatLineNumber::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_LINENUMBER_COUNT: + rVal <<= IsCount(); + break; + case MID_LINENUMBER_STARTVALUE: + rVal <<= static_cast<sal_Int32>(GetStartValue()); + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +bool SwFormatLineNumber::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + // here we convert always! + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch ( nMemberId ) + { + case MID_LINENUMBER_COUNT: + SetCountLines( *o3tl::doAccess<bool>(rVal) ); + break; + case MID_LINENUMBER_STARTVALUE: + { + sal_Int32 nVal = 0; + if(rVal >>= nVal) + SetStartValue( nVal ); + else + bRet = false; + } + break; + default: + OSL_ENSURE( false, "unknown MemberId" ); + bRet = false; + } + return bRet; +} + +SwTextGridItem::SwTextGridItem() + : SfxPoolItem( RES_TEXTGRID ), m_aColor( COL_LIGHTGRAY ), m_nLines( 20 ) + , m_nBaseHeight( 400 ), m_nRubyHeight( 200 ), m_eGridType( GRID_NONE ) + , m_bRubyTextBelow( false ), m_bPrintGrid( true ), m_bDisplayGrid( true ) + , m_nBaseWidth(400), m_bSnapToChars( true ), m_bSquaredMode(true) +{ +} + +SwTextGridItem::~SwTextGridItem() +{ +} + +bool SwTextGridItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + SwTextGridItem const& rOther(static_cast<SwTextGridItem const&>(rAttr)); + return m_eGridType == rOther.GetGridType() + && m_nLines == rOther.GetLines() + && m_nBaseHeight == rOther.GetBaseHeight() + && m_nRubyHeight == rOther.GetRubyHeight() + && m_bRubyTextBelow == rOther.GetRubyTextBelow() + && m_bDisplayGrid == rOther.GetDisplayGrid() + && m_bPrintGrid == rOther.GetPrintGrid() + && m_aColor == rOther.GetColor() + && m_nBaseWidth == rOther.GetBaseWidth() + && m_bSnapToChars == rOther.GetSnapToChars() + && m_bSquaredMode == rOther.GetSquaredMode(); +} + +SwTextGridItem* SwTextGridItem::Clone( SfxItemPool* ) const +{ + return new SwTextGridItem( *this ); +} + +bool SwTextGridItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bRet = true; + + switch( nMemberId & ~CONVERT_TWIPS ) + { + case MID_GRID_COLOR: + rVal <<= GetColor(); + break; + case MID_GRID_LINES: + rVal <<= GetLines(); + break; + case MID_GRID_RUBY_BELOW: + rVal <<= m_bRubyTextBelow; + break; + case MID_GRID_PRINT: + rVal <<= m_bPrintGrid; + break; + case MID_GRID_DISPLAY: + rVal <<= m_bDisplayGrid; + break; + case MID_GRID_BASEHEIGHT: + OSL_ENSURE( (nMemberId & CONVERT_TWIPS) != 0, + "This value needs TWIPS-MM100 conversion" ); + rVal <<= static_cast<sal_Int32>(convertTwipToMm100(m_nBaseHeight)); + break; + case MID_GRID_BASEWIDTH: + OSL_ENSURE( (nMemberId & CONVERT_TWIPS) != 0, + "This value needs TWIPS-MM100 conversion" ); + rVal <<= static_cast<sal_Int32>(convertTwipToMm100(m_nBaseWidth)); + break; + case MID_GRID_RUBYHEIGHT: + OSL_ENSURE( (nMemberId & CONVERT_TWIPS) != 0, + "This value needs TWIPS-MM100 conversion" ); + rVal <<= static_cast<sal_Int32>(convertTwipToMm100(m_nRubyHeight)); + break; + case MID_GRID_TYPE: + switch( GetGridType() ) + { + case GRID_NONE: + rVal <<= text::TextGridMode::NONE; + break; + case GRID_LINES_ONLY: + rVal <<= text::TextGridMode::LINES; + break; + case GRID_LINES_CHARS: + rVal <<= text::TextGridMode::LINES_AND_CHARS; + break; + default: + OSL_FAIL("unknown SwTextGrid value"); + bRet = false; + break; + } + break; + case MID_GRID_SNAPTOCHARS: + rVal <<= m_bSnapToChars; + break; + case MID_GRID_STANDARD_MODE: + rVal <<= !m_bSquaredMode; + break; + default: + OSL_FAIL("Unknown SwTextGridItem member"); + bRet = false; + break; + } + + return bRet; +} + +bool SwTextGridItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bRet = true; + switch( nMemberId & ~CONVERT_TWIPS ) + { + case MID_GRID_COLOR: + { + Color nTmp; + bRet = (rVal >>= nTmp); + if( bRet ) + SetColor( nTmp ); + } + break; + case MID_GRID_LINES: + { + sal_Int16 nTmp = 0; + bRet = (rVal >>= nTmp); + if( bRet && (nTmp >= 0) ) + SetLines( o3tl::narrowing<sal_uInt16>(nTmp) ); + else + bRet = false; + } + break; + case MID_GRID_RUBY_BELOW: + SetRubyTextBelow( *o3tl::doAccess<bool>(rVal) ); + break; + case MID_GRID_PRINT: + SetPrintGrid( *o3tl::doAccess<bool>(rVal) ); + break; + case MID_GRID_DISPLAY: + SetDisplayGrid( *o3tl::doAccess<bool>(rVal) ); + break; + case MID_GRID_BASEHEIGHT: + case MID_GRID_BASEWIDTH: + case MID_GRID_RUBYHEIGHT: + { + OSL_ENSURE( (nMemberId & CONVERT_TWIPS) != 0, + "This value needs TWIPS-MM100 conversion" ); + sal_Int32 nTmp = 0; + bRet = (rVal >>= nTmp); + nTmp = o3tl::toTwips(nTmp, o3tl::Length::mm100); + if( bRet && (nTmp >= 0) && ( nTmp <= SAL_MAX_UINT16) ) + { + // rhbz#1043551 round up to 5pt -- 0 causes divide-by-zero + // in layout; 1pt ties the painting code up in knots for + // minutes with bazillion lines... +#define MIN_TEXTGRID_SIZE 100 + if( (nMemberId & ~CONVERT_TWIPS) == MID_GRID_BASEHEIGHT ) + { + nTmp = std::max<sal_Int32>(nTmp, MIN_TEXTGRID_SIZE); + SetBaseHeight( o3tl::narrowing<sal_uInt16>(nTmp) ); + } + else if( (nMemberId & ~CONVERT_TWIPS) == MID_GRID_BASEWIDTH ) + { + nTmp = std::max<sal_Int32>(nTmp, MIN_TEXTGRID_SIZE); + SetBaseWidth( o3tl::narrowing<sal_uInt16>(nTmp) ); + } + else + SetRubyHeight( o3tl::narrowing<sal_uInt16>(nTmp) ); + } + else + bRet = false; + } + break; + case MID_GRID_TYPE: + { + sal_Int16 nTmp = 0; + bRet = (rVal >>= nTmp); + if( bRet ) + { + switch( nTmp ) + { + case text::TextGridMode::NONE: + SetGridType( GRID_NONE ); + break; + case text::TextGridMode::LINES: + SetGridType( GRID_LINES_ONLY ); + break; + case text::TextGridMode::LINES_AND_CHARS: + SetGridType( GRID_LINES_CHARS ); + break; + default: + bRet = false; + break; + } + } + break; + } + case MID_GRID_SNAPTOCHARS: + SetSnapToChars( *o3tl::doAccess<bool>(rVal) ); + break; + case MID_GRID_STANDARD_MODE: + { + bool bStandard = *o3tl::doAccess<bool>(rVal); + SetSquaredMode( !bStandard ); + break; + } + default: + OSL_FAIL("Unknown SwTextGridItem member"); + bRet = false; + } + + return bRet; +} + +void SwTextGridItem::SwitchPaperMode(bool bNew) +{ + if (bNew == m_bSquaredMode) + { + //same paper mode, not switch + return; + } + + // use default value when grid is disable + if (m_eGridType == GRID_NONE) + { + m_bSquaredMode = bNew; + Init(); + return; + } + + if (m_bSquaredMode) + { + //switch from "squared mode" to "standard mode" + m_nBaseWidth = m_nBaseHeight; + m_nBaseHeight = m_nBaseHeight + m_nRubyHeight; + m_nRubyHeight = 0; + } + else + { + //switch from "standard mode" to "squared mode" + m_nRubyHeight = m_nBaseHeight/3; + m_nBaseHeight = m_nBaseHeight - m_nRubyHeight; + m_nBaseWidth = m_nBaseHeight; + } + m_bSquaredMode = !m_bSquaredMode; +} + +void SwTextGridItem::Init() +{ + if (m_bSquaredMode) + { + m_nLines = 20; + m_nBaseHeight = 400; + m_nRubyHeight = 200; + m_eGridType = GRID_NONE; + m_bRubyTextBelow = false; + m_bPrintGrid = true; + m_bDisplayGrid = true; + m_bSnapToChars = true; + m_nBaseWidth = 400; + } + else + { + m_nLines = 44; + m_nBaseHeight = 312; + m_nRubyHeight = 0; + m_eGridType = GRID_NONE; + m_bRubyTextBelow = false; + m_bPrintGrid = true; + m_bDisplayGrid = true; + m_nBaseWidth = 210; + m_bSnapToChars = true; + } +} + +SwHeaderAndFooterEatSpacingItem* SwHeaderAndFooterEatSpacingItem::Clone( SfxItemPool* ) const +{ + return new SwHeaderAndFooterEatSpacingItem( Which(), GetValue() ); +} + +SwFrameFormat::SwFrameFormat( + SwAttrPool& rPool, + const char* pFormatNm, + SwFrameFormat *pDrvdFrame, + sal_uInt16 nFormatWhich, + const WhichRangesContainer& pWhichRange) +: SwFormat(rPool, pFormatNm, pWhichRange, pDrvdFrame, nFormatWhich), + m_ffList(nullptr) +{ +} + +SwFrameFormat::SwFrameFormat( + SwAttrPool& rPool, + const OUString &rFormatNm, + SwFrameFormat *pDrvdFrame, + sal_uInt16 nFormatWhich, + const WhichRangesContainer& pWhichRange) +: SwFormat(rPool, rFormatNm, pWhichRange, pDrvdFrame, nFormatWhich), + m_ffList(nullptr) +{ +} + +SwFrameFormat::~SwFrameFormat() +{ + if( !GetDoc()->IsInDtor()) + { + const SwFormatAnchor& rAnchor = GetAnchor(); + if (rAnchor.GetContentAnchor() != nullptr) + { + rAnchor.GetContentAnchor()->nNode.GetNode().RemoveAnchoredFly(this); + } + } + + // Check if there any textboxes attached to this format. + if( nullptr == m_pOtherTextBoxFormats ) + return; + + // This is a fly-frame-format just delete this + // textbox entry from the textbox collection. + // Note: Do not delete it from the doc, as that + // is already in progress. + if (Which() == RES_FLYFRMFMT) + m_pOtherTextBoxFormats->DelTextBox(this); + + // This is a draw-frame-format what belongs to + // a shape with textbox(es). Delete all of them. + if (Which() == RES_DRAWFRMFMT) + m_pOtherTextBoxFormats->ClearAll(); + + // Release the pointer. + m_pOtherTextBoxFormats.reset(); +} + +void SwFrameFormat::SetName( const OUString& rNewName, bool bBroadcast ) +{ + if (m_ffList != nullptr) { + SwFrameFormats::iterator it = m_ffList->find( this ); + assert( m_ffList->end() != it ); + SAL_INFO_IF(m_aFormatName == rNewName, "sw.core", "SwFrmFmt not really renamed, as both names are equal"); + + const SwStringMsgPoolItem aOld( RES_NAME_CHANGED, m_aFormatName ); + // As it's a non-unique list, rename should never fail! + bool const renamed = + m_ffList->m_PosIndex.modify( it, + change_name( rNewName ), change_name( m_aFormatName ) ); + assert(renamed); + (void)renamed; // unused in NDEBUG + if (bBroadcast) { + const SwStringMsgPoolItem aNew( RES_NAME_CHANGED, rNewName ); + GetNotifier().Broadcast(sw::LegacyModifyHint( &aOld, &aNew )); + } + } + else + SwFormat::SetName( rNewName, bBroadcast ); +} + +bool SwFrameFormat::supportsFullDrawingLayerFillAttributeSet() const +{ + return true; +} + +void SwFrameFormat::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + const sal_uInt16 nNewWhich = pLegacy->m_pNew ? pLegacy->m_pNew->Which() : 0; + const SwAttrSetChg* pNewAttrSetChg = nullptr; + const SwFormatHeader* pH = nullptr; + const SwFormatFooter* pF = nullptr; + const SwPosition* pNewAnchorPosition = nullptr; + switch(nNewWhich) + { + case RES_ATTRSET_CHG: + { + pNewAttrSetChg = static_cast<const SwAttrSetChg*>(pLegacy->m_pNew); + pH = pNewAttrSetChg->GetChgSet()->GetItem(RES_HEADER, false); + pF = pNewAttrSetChg->GetChgSet()->GetItem(RES_FOOTER, false); + + // reset fill information + if(maFillAttributes && supportsFullDrawingLayerFillAttributeSet()) + { + SfxItemIter aIter(*pNewAttrSetChg->GetChgSet()); + for(const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + if(!IsInvalidItem(pItem) && pItem->Which() >= XATTR_FILL_FIRST && pItem->Which() <= XATTR_FILL_LAST) + { + maFillAttributes.reset(); + break; + } + } + } + const SwFormatAnchor* pAnchor = pNewAttrSetChg->GetChgSet()->GetItem(RES_ANCHOR, false); + if(pAnchor) + { + pNewAnchorPosition = pAnchor->GetContentAnchor(); + assert(pNewAnchorPosition == nullptr || // style's set must not contain position! + pNewAttrSetChg->GetTheChgdSet() == &m_aSet); + } + break; + } + case RES_FMT_CHG: + { + // reset fill information on format change (e.g. style changed) + if(maFillAttributes && supportsFullDrawingLayerFillAttributeSet()) + maFillAttributes.reset(); + break; + } + case RES_HEADER: + pH = static_cast<const SwFormatHeader*>(pLegacy->m_pNew); + break; + case RES_FOOTER: + pF = static_cast<const SwFormatFooter*>(pLegacy->m_pNew); + break; + case RES_ANCHOR: + pNewAnchorPosition = static_cast<const SwFormatAnchor*>(pLegacy->m_pNew)->GetContentAnchor(); + break; + } + const sal_uInt16 nOldWhich = pLegacy->m_pOld ? pLegacy->m_pOld->Which() : 0; + const SwPosition* pOldAnchorPosition = nullptr; + switch(nOldWhich) + { + case RES_ATTRSET_CHG: + { + const SwAttrSetChg* pOldAttrSetChg = nullptr; + pOldAttrSetChg = static_cast<const SwAttrSetChg*>(pLegacy->m_pOld); + const SwFormatAnchor* pAnchor = pOldAttrSetChg->GetChgSet()->GetItem(RES_ANCHOR, false); + if(pAnchor) + { + pOldAnchorPosition = pAnchor->GetContentAnchor(); + assert(pOldAnchorPosition == nullptr || // style's set must not contain position! + pOldAttrSetChg->GetTheChgdSet() == &m_aSet); + } + break; + } + case RES_ANCHOR: + pOldAnchorPosition = static_cast<const SwFormatAnchor*>(pLegacy->m_pOld)->GetContentAnchor(); + break; + case RES_REMOVE_UNO_OBJECT: + SetXObject(uno::Reference<uno::XInterface>(nullptr)); + break; + } + + assert(nOldWhich == nNewWhich || !nOldWhich || !nNewWhich); + if(pH && pH->IsActive() && !pH->GetHeaderFormat()) + { //If he doesn't have one, I'll add one + SwFrameFormat* pFormat = GetDoc()->getIDocumentLayoutAccess().MakeLayoutFormat(RndStdIds::HEADER, nullptr); + const_cast<SwFormatHeader*>(pH)->RegisterToFormat(*pFormat); + } + if(pF && pF->IsActive() && !pF->GetFooterFormat()) + { //If he doesn't have one, I'll add one + SwFrameFormat* pFormat = GetDoc()->getIDocumentLayoutAccess().MakeLayoutFormat(RndStdIds::FOOTER, nullptr); + const_cast<SwFormatFooter*>(pF)->RegisterToFormat(*pFormat); + } + SwFormat::SwClientNotify(rMod, rHint); + if(pOldAnchorPosition != nullptr && (pNewAnchorPosition == nullptr || pOldAnchorPosition->nNode.GetIndex() != pNewAnchorPosition->nNode.GetIndex())) + pOldAnchorPosition->nNode.GetNode().RemoveAnchoredFly(this); + if(pNewAnchorPosition != nullptr && (pOldAnchorPosition == nullptr || pOldAnchorPosition->nNode.GetIndex() != pNewAnchorPosition->nNode.GetIndex())) + pNewAnchorPosition->nNode.GetNode().AddAnchoredFly(this); +} + +void SwFrameFormat::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add( this ); +} + +/// Delete all Frames that are registered in aDepend. +void SwFrameFormat::DelFrames() +{ + SwIterator<SwFrame,SwFormat> aIter( *this ); + SwFrame * pLast = aIter.First(); + if( pLast ) + do { + pLast->Cut(); + SwFrame::DestroyFrame(pLast); + } while( nullptr != ( pLast = aIter.Next() )); +} + +void SwFrameFormat::MakeFrames() +{ + assert(false); // unimplemented in base class +} + +SwRect SwFrameFormat::FindLayoutRect( const bool bPrtArea, const Point* pPoint ) const +{ + SwRect aRet; + SwFrame *pFrame = nullptr; + if( auto pSectionFormat = dynamic_cast<const SwSectionFormat*>( this )) + { + // get the Frame using Node2Layout + const SwSectionNode* pSectNd = pSectionFormat->GetSectionNode(); + if( pSectNd ) + { + SwNode2Layout aTmp( *pSectNd, pSectNd->GetIndex() - 1 ); + pFrame = aTmp.NextFrame(); + + if( pFrame && !pFrame->KnowsFormat(*this) ) + { + // the Section doesn't have his own Frame, so if someone + // needs the real size, we have to implement this by requesting + // the matching Frame from the end. + // PROBLEM: what happens if SectionFrames overlaps multiple + // pages? + if( bPrtArea ) + aRet = pFrame->getFramePrintArea(); + else + { + aRet = pFrame->getFrameArea(); + aRet.Pos().AdjustY( -1 ); + } + pFrame = nullptr; // the rect is finished by now + } + } + } + else + { + const SwFrameType nFrameType = RES_FLYFRMFMT == Which() ? SwFrameType::Fly : FRM_ALL; + std::pair<Point, bool> tmp; + if (pPoint) + { + tmp.first = *pPoint; + tmp.second = false; + } + pFrame = ::GetFrameOfModify(nullptr, *this, nFrameType, nullptr, pPoint ? &tmp : nullptr); + } + + if( pFrame ) + { + if( bPrtArea ) + aRet = pFrame->getFramePrintArea(); + else + aRet = pFrame->getFrameArea(); + } + return aRet; +} + +SdrObject* SwFrameFormat::FindRealSdrObject() +{ + if( RES_FLYFRMFMT == Which() ) + { + Point aNullPt; + std::pair<Point, bool> const tmp(aNullPt, false); + SwFlyFrame* pFly = static_cast<SwFlyFrame*>(::GetFrameOfModify( nullptr, *this, SwFrameType::Fly, + nullptr, &tmp)); + return pFly ? pFly->GetVirtDrawObj() : nullptr; + } + return FindSdrObject(); +} + +bool SwFrameFormat::IsLowerOf( const SwFrameFormat& rFormat ) const +{ + //Also linking from inside to outside or from outside to inside is not + //allowed. + SwFlyFrame *pSFly = SwIterator<SwFlyFrame,SwFormat>(*this).First(); + if( pSFly ) + { + SwFlyFrame *pAskFly = SwIterator<SwFlyFrame,SwFormat>(rFormat).First(); + if( pAskFly ) + return pSFly->IsLowerOf( pAskFly ); + } + + // let's try it using the node positions + const SwFormatAnchor* pAnchor = &rFormat.GetAnchor(); + if ((RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId()) && pAnchor->GetContentAnchor()) + { + const SwFrameFormats& rFormats = *GetDoc()->GetSpzFrameFormats(); + const SwNode* pFlyNd = pAnchor->GetContentAnchor()->nNode.GetNode(). + FindFlyStartNode(); + while( pFlyNd ) + { + // then we walk up using the anchor + size_t n; + for( n = 0; n < rFormats.size(); ++n ) + { + const SwFrameFormat* pFormat = rFormats[ n ]; + const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx(); + if( pIdx && pFlyNd == &pIdx->GetNode() ) + { + if( pFormat == this ) + return true; + + pAnchor = &pFormat->GetAnchor(); + if ((RndStdIds::FLY_AT_PAGE == pAnchor->GetAnchorId()) || + !pAnchor->GetContentAnchor() ) + { + return false; + } + + pFlyNd = pAnchor->GetContentAnchor()->nNode.GetNode(). + FindFlyStartNode(); + break; + } + } + if( n >= rFormats.size() ) + { + OSL_ENSURE( false, "Fly section but no format found" ); + return false; + } + } + } + return false; +} + +// #i31698# +SwFrameFormat::tLayoutDir SwFrameFormat::GetLayoutDir() const +{ + return SwFrameFormat::HORI_L2R; +} + +void SwFrameFormat::SetLayoutDir( const SwFrameFormat::tLayoutDir ) +{ + // empty body, because default implementation does nothing +} + +// #i28749# +sal_Int16 SwFrameFormat::GetPositionLayoutDir() const +{ + return text::PositionLayoutDir::PositionInLayoutDirOfAnchor; +} +void SwFrameFormat::SetPositionLayoutDir( const sal_Int16 ) +{ + // empty body, because default implementation does nothing +} + +OUString SwFrameFormat::GetDescription() const +{ + return SwResId(STR_FRAME); +} + +void SwFrameFormat::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFrameFormat")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(GetName().toUtf8().getStr())); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("whichId"), "%d", Which()); + + const char* pWhich = nullptr; + switch (Which()) + { + case RES_FLYFRMFMT: + pWhich = "fly frame format"; + break; + case RES_DRAWFRMFMT: + pWhich = "draw frame format"; + break; + } + if (pWhich) + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("which"), BAD_CAST(pWhich)); + + if (m_pOtherTextBoxFormats) + { + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("OtherTextBoxFormat"), "%p", m_pOtherTextBoxFormats.get()); + } + + GetAttrSet().dumpAsXml(pWriter); + + if (const SdrObject* pSdrObject = FindSdrObject()) + pSdrObject->dumpAsXml(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +void SwFrameFormats::dumpAsXml(xmlTextWriterPtr pWriter, const char* pName) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST(pName)); + for (const SwFrameFormat *pFormat : m_PosIndex) + pFormat->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + + +SwFlyFrameFormat::SwFlyFrameFormat( SwAttrPool& rPool, const OUString &rFormatNm, SwFrameFormat *pDrvdFrame ) + : SwFrameFormat( rPool, rFormatNm, pDrvdFrame, RES_FLYFRMFMT ) +{} + +SwFlyFrameFormat::~SwFlyFrameFormat() +{ + SwIterator<SwFlyFrame,SwFormat> aIter( *this ); + SwFlyFrame * pLast = aIter.First(); + if( pLast ) + do + { + SwFrame::DestroyFrame(pLast); + } while( nullptr != ( pLast = aIter.Next() )); + +} + +SwFlyDrawContact* SwFlyFrameFormat::GetOrCreateContact() +{ + if(!m_pContact) + { + SwDrawModel* pDrawModel(GetDoc()->getIDocumentDrawModelAccess().GetOrCreateDrawModel()); + m_pContact.reset(new SwFlyDrawContact(this, *pDrawModel)); + } + + return m_pContact.get(); +} + +/// Creates the Frames if the format describes a paragraph-bound frame. +/// MA: 1994-02-14: creates the Frames also for frames anchored at page. +void SwFlyFrameFormat::MakeFrames() +{ + // is there a layout? + if( !GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell() ) + return; + + sw::BroadcastingModify *pModify = nullptr; + // OD 24.07.2003 #111032# - create local copy of anchor attribute for possible changes. + SwFormatAnchor aAnchorAttr( GetAnchor() ); + switch( aAnchorAttr.GetAnchorId() ) + { + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: + if( aAnchorAttr.GetContentAnchor() ) + { + pModify = aAnchorAttr.GetContentAnchor()->nNode.GetNode().GetContentNode(); + } + break; + + case RndStdIds::FLY_AT_FLY: + if( aAnchorAttr.GetContentAnchor() ) + { + //First search in the content because this is O(1) + //This can go wrong for linked frames because in this case it's + //possible, that no Frame exists for this content. + //In such a situation we also need to search from StartNode to + //FrameFormat. + SwNodeIndex aIdx( aAnchorAttr.GetContentAnchor()->nNode ); + SwContentNode *pCNd = GetDoc()->GetNodes().GoNext( &aIdx ); + // #i105535# + if ( pCNd == nullptr ) + { + pCNd = aAnchorAttr.GetContentAnchor()->nNode.GetNode().GetContentNode(); + } + if ( pCNd ) + { + if (SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*pCNd).First()) + { + pModify = pCNd; + } + } + // #i105535# + if ( pModify == nullptr ) + { + const SwNodeIndex &rIdx = aAnchorAttr.GetContentAnchor()->nNode; + SwFrameFormats& rFormats = *GetDoc()->GetSpzFrameFormats(); + for( size_t i = 0; i < rFormats.size(); ++i ) + { + SwFrameFormat* pFlyFormat = rFormats[i]; + if( pFlyFormat->GetContent().GetContentIdx() && + rIdx == *pFlyFormat->GetContent().GetContentIdx() ) + { + pModify = pFlyFormat; + break; + } + } + } + } + break; + + case RndStdIds::FLY_AT_PAGE: + { + sal_uInt16 nPgNum = aAnchorAttr.GetPageNum(); + SwPageFrame *pPage = static_cast<SwPageFrame*>(GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout()->Lower()); + if( nPgNum == 0 && aAnchorAttr.GetContentAnchor() ) + { + SwContentNode *pCNd = aAnchorAttr.GetContentAnchor()->nNode.GetNode().GetContentNode(); + SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*pCNd); + for ( SwFrame* pFrame = aIter.First(); pFrame != nullptr; pFrame = aIter.Next() ) + { + pPage = pFrame->FindPageFrame(); + if( pPage ) + { + nPgNum = pPage->GetPhyPageNum(); + aAnchorAttr.SetPageNum( nPgNum ); + aAnchorAttr.SetAnchor( nullptr ); + SetFormatAttr( aAnchorAttr ); + break; + } + } + } + while ( pPage ) + { + if ( pPage->GetPhyPageNum() == nPgNum ) + { + // #i50432# - adjust synopsis of <PlaceFly(..)> + pPage->PlaceFly( nullptr, this ); + break; + } + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + } + break; + default: + break; + } + + if( !pModify ) + return; + + SwIterator<SwFrame, sw::BroadcastingModify, sw::IteratorMode::UnwrapMulti> aIter(*pModify); + for( SwFrame *pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + { + bool bAdd = !pFrame->IsContentFrame() || + !static_cast<SwContentFrame*>(pFrame)->IsFollow(); + + if ( RndStdIds::FLY_AT_FLY == aAnchorAttr.GetAnchorId() && !pFrame->IsFlyFrame() ) + { + SwFrame* pFlyFrame = pFrame->FindFlyFrame(); + if ( pFlyFrame ) + { + pFrame = pFlyFrame; + } + else + { + aAnchorAttr.SetType( RndStdIds::FLY_AT_PARA ); + SetFormatAttr( aAnchorAttr ); + MakeFrames(); + return; + } + } + + if (bAdd) + { + switch (aAnchorAttr.GetAnchorId()) + { + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: + { + assert(pFrame->IsTextFrame()); + bAdd = IsAnchoredObjShown(*static_cast<SwTextFrame*>(pFrame), aAnchorAttr); + } + break; + default: + break; + } + } + + if (bAdd && pFrame->GetDrawObjs()) + { + // #i28701# - new type <SwSortedObjs> + SwSortedObjs &rObjs = *pFrame->GetDrawObjs(); + for(SwAnchoredObject* pObj : rObjs) + { + // #i28701# - consider changed type of + // <SwSortedObjs> entries. + if( pObj->DynCastFlyFrame() != nullptr && + (&pObj->GetFrameFormat()) == this ) + { + bAdd = false; + break; + } + } + } + + if( bAdd ) + { + SwFlyFrame *pFly = nullptr; // avoid warnings + switch( aAnchorAttr.GetAnchorId() ) + { + case RndStdIds::FLY_AT_FLY: + pFly = new SwFlyLayFrame( this, pFrame, pFrame ); + break; + + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_CHAR: + pFly = new SwFlyAtContentFrame( this, pFrame, pFrame ); + break; + + case RndStdIds::FLY_AS_CHAR: + pFly = new SwFlyInContentFrame( this, pFrame, pFrame ); + break; + + default: + assert(false && "New anchor type" ); + } + pFrame->AppendFly( pFly ); + pFly->GetFormat()->SetObjTitle(GetObjTitle()); + pFly->GetFormat()->SetObjDescription(GetObjDescription()); + SwPageFrame *pPage = pFly->FindPageFrame(); + if( pPage ) + ::RegistFlys( pPage, pFly ); + } + } +} + +SwFlyFrame* SwFlyFrameFormat::GetFrame( const Point* pPoint ) const +{ + std::pair<Point, bool> tmp; + if (pPoint) + { + tmp.first = *pPoint; + tmp.second = false; + } + return static_cast<SwFlyFrame*>(::GetFrameOfModify( nullptr, *this, SwFrameType::Fly, + nullptr, &tmp)); +} + +SwAnchoredObject* SwFlyFrameFormat::GetAnchoredObj() const +{ + SwFlyFrame* pFlyFrame( GetFrame() ); + if ( pFlyFrame ) + { + return pFlyFrame; + } + else + { + return nullptr; + } +} + +bool SwFlyFrameFormat::GetInfo( SfxPoolItem& rInfo ) const +{ + bool bRet = true; + switch( rInfo.Which() ) + { + case RES_CONTENT_VISIBLE: + { + static_cast<SwPtrMsgPoolItem&>(rInfo).pObject = SwIterator<SwFrame,SwFormat>( *this ).First(); + } + bRet = false; + break; + + default: + bRet = SwFrameFormat::GetInfo( rInfo ); + break; + } + return bRet; +} + +// #i73249# +void SwFlyFrameFormat::SetObjTitle( const OUString& rTitle, bool bBroadcast ) +{ + SdrObject* pMasterObject = FindSdrObject(); + OSL_ENSURE( pMasterObject, "<SwFlyFrameFormat::SetObjTitle(..)> - missing <SdrObject> instance" ); + msTitle = rTitle; + if ( !pMasterObject ) + { + return; + } + + const SwStringMsgPoolItem aOld(RES_TITLE_CHANGED, pMasterObject->GetTitle()); + pMasterObject->SetTitle(rTitle); + if(bBroadcast) + { + const SwStringMsgPoolItem aNew(RES_TITLE_CHANGED, rTitle); + GetNotifier().Broadcast(sw::LegacyModifyHint(&aOld, &aNew)); + } +} + +OUString SwFlyFrameFormat::GetObjTitle() const +{ + const SdrObject* pMasterObject = FindSdrObject(); + OSL_ENSURE( pMasterObject, "<SwFlyFrameFormat::GetObjTitle(..)> - missing <SdrObject> instance" ); + if ( !pMasterObject ) + { + return msTitle; + } + if (!pMasterObject->GetTitle().isEmpty()) + return pMasterObject->GetTitle(); + else + return msTitle; +} + +void SwFlyFrameFormat::SetObjTooltip(const OUString& rTooltip) +{ + msTooltip = rTooltip; +} + +const OUString & SwFlyFrameFormat::GetObjTooltip() const +{ + return msTooltip; +} + +void SwFlyFrameFormat::SetObjDescription( const OUString& rDescription, bool bBroadcast ) +{ + SdrObject* pMasterObject = FindSdrObject(); + OSL_ENSURE( pMasterObject, "<SwFlyFrameFormat::SetDescription(..)> - missing <SdrObject> instance" ); + msDesc = rDescription; + if ( !pMasterObject ) + { + return; + } + + const SwStringMsgPoolItem aOld( RES_DESCRIPTION_CHANGED, pMasterObject->GetDescription() ); + pMasterObject->SetDescription( rDescription ); + if(bBroadcast) + { + const SwStringMsgPoolItem aNew( RES_DESCRIPTION_CHANGED, rDescription ); + GetNotifier().Broadcast(sw::LegacyModifyHint(&aOld, &aNew)); + } +} + +OUString SwFlyFrameFormat::GetObjDescription() const +{ + const SdrObject* pMasterObject = FindSdrObject(); + OSL_ENSURE( pMasterObject, "<SwFlyFrameFormat::GetDescription(..)> - missing <SdrObject> instance" ); + if ( !pMasterObject ) + { + return msDesc; + } + if (!pMasterObject->GetDescription().isEmpty()) + return pMasterObject->GetDescription(); + else + return msDesc; +} + +/** SwFlyFrameFormat::IsBackgroundTransparent - for #99657# + + OD 22.08.2002 - overriding virtual method and its default implementation, + because format of fly frame provides transparent backgrounds. + Method determines, if background of fly frame is transparent. + + @return true, if background color is transparent, but not "no fill" + or the transparency of an existing background graphic is set. +*/ +bool SwFlyFrameFormat::IsBackgroundTransparent() const +{ + if (supportsFullDrawingLayerFillAttributeSet() && getSdrAllFillAttributesHelper()) + { + return getSdrAllFillAttributesHelper()->isTransparent(); + } + + // NOTE: If background color is "no fill"/"auto fill" (COL_TRANSPARENT) + // and there is no background graphic, it "inherites" the background + // from its anchor. + std::unique_ptr<SvxBrushItem> aBackground(makeBackgroundBrushItem()); + if ( aBackground->GetColor().IsTransparent() && + aBackground->GetColor() != COL_TRANSPARENT + ) + { + return true; + } + else + { + const GraphicObject *pTmpGrf = aBackground->GetGraphicObject(); + if ( pTmpGrf && + pTmpGrf->GetAttr().IsTransparent() + ) + { + return true; + } + } + + return false; +} + +/** SwFlyFrameFormat::IsBackgroundBrushInherited - for #103898# + + OD 08.10.2002 - method to determine, if the brush for drawing the + background is "inherited" from its parent/grandparent. + This is the case, if no background graphic is set and the background + color is "no fill"/"auto fill" + NOTE: condition is "copied" from method <SwFrame::GetBackgroundBrush(..). + + @return true, if background brush is "inherited" from parent/grandparent +*/ +bool SwFlyFrameFormat::IsBackgroundBrushInherited() const +{ + if (supportsFullDrawingLayerFillAttributeSet() && getSdrAllFillAttributesHelper()) + { + return !getSdrAllFillAttributesHelper()->isUsed(); + } + else + { + std::unique_ptr<SvxBrushItem> aBackground(makeBackgroundBrushItem()); + if ( (aBackground->GetColor() == COL_TRANSPARENT) && + !(aBackground->GetGraphicObject()) ) + { + return true; + } + } + + return false; +} + +SwHandleAnchorNodeChg::SwHandleAnchorNodeChg( SwFlyFrameFormat& _rFlyFrameFormat, + const SwFormatAnchor& _rNewAnchorFormat, + SwFlyFrame const * _pKeepThisFlyFrame ) + : mrFlyFrameFormat( _rFlyFrameFormat ), + mbAnchorNodeChanged( false ), + mpWrtShell(nullptr) +{ + const SwFormatAnchor& aOldAnchorFormat(_rFlyFrameFormat.GetAnchor()); + const RndStdIds nNewAnchorType( _rNewAnchorFormat.GetAnchorId() ); + if ( ((nNewAnchorType == RndStdIds::FLY_AT_PARA) || + (nNewAnchorType == RndStdIds::FLY_AT_CHAR)) && + _rNewAnchorFormat.GetContentAnchor() && + _rNewAnchorFormat.GetContentAnchor()->nNode.GetNode().GetContentNode() ) + { + if ( aOldAnchorFormat.GetAnchorId() == nNewAnchorType && + aOldAnchorFormat.GetContentAnchor() && + aOldAnchorFormat.GetContentAnchor()->nNode.GetNode().GetContentNode() && + aOldAnchorFormat.GetContentAnchor()->nNode != + _rNewAnchorFormat.GetContentAnchor()->nNode ) + { + // determine 'old' number of anchor frames + sal_uInt32 nOldNumOfAnchFrame( 0 ); + SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aOldIter( + *(aOldAnchorFormat.GetContentAnchor()->nNode.GetNode().GetContentNode()) ); + for( SwFrame* pOld = aOldIter.First(); pOld; pOld = aOldIter.Next() ) + { + ++nOldNumOfAnchFrame; + } + // determine 'new' number of anchor frames + sal_uInt32 nNewNumOfAnchFrame( 0 ); + SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aNewIter( + *(_rNewAnchorFormat.GetContentAnchor()->nNode.GetNode().GetContentNode()) ); + for( SwFrame* pNew = aNewIter.First(); pNew; pNew = aNewIter.Next() ) + { + ++nNewNumOfAnchFrame; + } + if ( nOldNumOfAnchFrame != nNewNumOfAnchFrame ) + { + // delete existing fly frames except <_pKeepThisFlyFrame> + SwIterator<SwFrame,SwFormat> aIter( mrFlyFrameFormat ); + SwFrame* pFrame = aIter.First(); + if ( pFrame ) + { + do { + if ( pFrame != _pKeepThisFlyFrame ) + { + pFrame->Cut(); + SwFrame::DestroyFrame(pFrame); + } + } while( nullptr != ( pFrame = aIter.Next() )); + } + // indicate, that re-creation of fly frames necessary + mbAnchorNodeChanged = true; + } + } + } + + if (aOldAnchorFormat.GetContentAnchor() + && aOldAnchorFormat.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + mpCommentAnchor.reset(new SwPosition(*aOldAnchorFormat.GetContentAnchor())); + } + + if (_pKeepThisFlyFrame) + { + SwViewShell* pViewShell = _pKeepThisFlyFrame->getRootFrame()->GetCurrShell(); + mpWrtShell = dynamic_cast<SwWrtShell*>(pViewShell); + } +} + +void SwHandleAnchorNodeChg::ImplDestroy() +{ + if ( mbAnchorNodeChanged ) + { + mrFlyFrameFormat.MakeFrames(); + } + + // See if the fly frame had a comment: if so, move it to the new anchor as well. + if (!mpCommentAnchor) + { + return; + } + + SwTextNode* pTextNode = mpCommentAnchor->nNode.GetNode().GetTextNode(); + if (!pTextNode) + { + return; + } + + const SwTextField* pField = pTextNode->GetFieldTextAttrAt(mpCommentAnchor->nContent.GetIndex()); + if (!pField || pField->GetFormatField().GetField()->GetTyp()->Which() != SwFieldIds::Postit) + { + return; + } + + if (!mpWrtShell) + { + return; + } + + // Save current cursor position, so we can restore it later. + mpWrtShell->Push(); + + // Set up the source of the move: the old comment anchor. + { + SwPaM& rCursor = mpWrtShell->GetCurrentShellCursor(); + *rCursor.GetPoint() = *mpCommentAnchor; + rCursor.SetMark(); + *rCursor.GetMark() = *mpCommentAnchor; + ++rCursor.GetMark()->nContent; + } + + // Set up the target of the move: the new comment anchor. + const SwFormatAnchor& rNewAnchorFormat = mrFlyFrameFormat.GetAnchor(); + mpWrtShell->CreateCursor(); + *mpWrtShell->GetCurrentShellCursor().GetPoint() = *rNewAnchorFormat.GetContentAnchor(); + + // Move by copying and deleting. + mpWrtShell->SwEditShell::Copy(*mpWrtShell); + mpWrtShell->DestroyCursor(); + + mpWrtShell->Delete(false); + + mpWrtShell->Pop(SwCursorShell::PopMode::DeleteCurrent); +} + +SwHandleAnchorNodeChg::~SwHandleAnchorNodeChg() +{ + suppress_fun_call_w_exception(ImplDestroy()); +} + +namespace sw +{ + DrawFrameFormatHint::~DrawFrameFormatHint() {} + CheckDrawFrameFormatLayerHint::~CheckDrawFrameFormatLayerHint() {} + ContactChangedHint::~ContactChangedHint() {} + DrawFormatLayoutCopyHint::~DrawFormatLayoutCopyHint() {} + WW8AnchorConvHint::~WW8AnchorConvHint() {} + RestoreFlyAnchorHint::~RestoreFlyAnchorHint() {} + CreatePortionHint::~CreatePortionHint() {} + FindSdrObjectHint::~FindSdrObjectHint() {} + CollectTextObjectsHint::~CollectTextObjectsHint() {} + GetZOrderHint::~GetZOrderHint() {} + GetObjectConnectedHint::~GetObjectConnectedHint() {} +} + +SwDrawFrameFormat::~SwDrawFrameFormat() +{ + CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::DYING)); +} + +void SwDrawFrameFormat::MakeFrames() +{ + CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::MAKE_FRAMES)); +} + +void SwDrawFrameFormat::DelFrames() +{ + CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::DELETE_FRAMES)); +} + +// #i31698# +SwFrameFormat::tLayoutDir SwDrawFrameFormat::GetLayoutDir() const +{ + return meLayoutDir; +} + +void SwDrawFrameFormat::SetLayoutDir( const SwFrameFormat::tLayoutDir _eLayoutDir ) +{ + meLayoutDir = _eLayoutDir; +} + +// #i28749# +sal_Int16 SwDrawFrameFormat::GetPositionLayoutDir() const +{ + return mnPositionLayoutDir; +} +void SwDrawFrameFormat::SetPositionLayoutDir( const sal_Int16 _nPositionLayoutDir ) +{ + switch ( _nPositionLayoutDir ) + { + case text::PositionLayoutDir::PositionInHoriL2R: + case text::PositionLayoutDir::PositionInLayoutDirOfAnchor: + { + mnPositionLayoutDir = _nPositionLayoutDir; + } + break; + default: + { + OSL_FAIL( "<SwDrawFrameFormat::SetPositionLayoutDir(..)> - invalid attribute value." ); + } + } +} + +OUString SwDrawFrameFormat::GetDescription() const +{ + OUString aResult; + const SdrObject * pSdrObj = FindSdrObject(); + + if (pSdrObj) + { + if (pSdrObj != m_pSdrObjectCached) + { + m_sSdrObjectCachedComment = SdrUndoNewObj::GetComment(*pSdrObj); + m_pSdrObjectCached = pSdrObj; + } + + aResult = m_sSdrObjectCachedComment; + } + else + aResult = SwResId(STR_GRAPHIC); + + return aResult; +} + +IMapObject* SwFrameFormat::GetIMapObject( const Point& rPoint, + const SwFlyFrame *pFly ) const +{ + const SwFormatURL &rURL = GetURL(); + if( !rURL.GetMap() ) + return nullptr; + + if( !pFly ) + { + pFly = SwIterator<SwFlyFrame,SwFormat>( *this ).First(); + if( !pFly ) + return nullptr; + } + + //Original size for OLE and graphic is TwipSize, otherwise the size of + //FrameFormat of the Fly. + const SwFrame *pRef; + const SwNoTextNode *pNd = nullptr; + Size aOrigSz; + if( pFly->Lower() && pFly->Lower()->IsNoTextFrame() ) + { + pRef = pFly->Lower(); + pNd = static_cast<const SwNoTextFrame*>(pRef)->GetNode()->GetNoTextNode(); + aOrigSz = pNd->GetTwipSize(); + } + else + { + pRef = pFly; + aOrigSz = pFly->GetFormat()->GetFrameSize().GetSize(); + } + + if( !aOrigSz.IsEmpty() ) + { + Point aPos( rPoint ); + Size aActSz ( pRef == pFly ? pFly->getFrameArea().SSize() : pRef->getFramePrintArea().SSize() ); + const o3tl::Length aSrc ( o3tl::Length::twip ); + const o3tl::Length aDest( o3tl::Length::mm100 ); + aOrigSz = o3tl::convert( aOrigSz, aSrc, aDest ); + aActSz = o3tl::convert( aActSz, aSrc, aDest ); + aPos -= pRef->getFrameArea().Pos(); + aPos -= pRef->getFramePrintArea().Pos(); + aPos = o3tl::convert( aPos, aSrc, aDest ); + sal_uInt32 nFlags = 0; + if ( pFly != pRef && pNd->IsGrfNode() ) + { + const MirrorGraph nMirror = pNd->GetSwAttrSet(). + GetMirrorGrf().GetValue(); + if ( MirrorGraph::Both == nMirror ) + nFlags = IMAP_MIRROR_HORZ | IMAP_MIRROR_VERT; + else if ( MirrorGraph::Vertical == nMirror ) + nFlags = IMAP_MIRROR_VERT; + else if ( MirrorGraph::Horizontal == nMirror ) + nFlags = IMAP_MIRROR_HORZ; + + } + return const_cast<ImageMap*>(rURL.GetMap())->GetHitIMapObject( aOrigSz, + aActSz, aPos, nFlags ); + } + + return nullptr; +} + +drawinglayer::attribute::SdrAllFillAttributesHelperPtr SwFrameFormat::getSdrAllFillAttributesHelper() const +{ + if (supportsFullDrawingLayerFillAttributeSet()) + { + // create FillAttributes on demand + if(!maFillAttributes) + { + const_cast< SwFrameFormat* >(this)->maFillAttributes = std::make_shared<drawinglayer::attribute::SdrAllFillAttributesHelper>(GetAttrSet()); + } + } + else + { + // FALLBACKBREAKHERE assert wrong usage + OSL_ENSURE(false, "getSdrAllFillAttributesHelper() call only valid for RES_FLYFRMFMT and RES_FRMFMT (!)"); + } + + return maFillAttributes; +} + +void SwFrameFormat::MoveTableBox(SwTableBox& rTableBox, const SwFrameFormat* pOldFormat) +{ + Add(&rTableBox); + if(!pOldFormat) + return; + const auto& rOld = pOldFormat->GetFormatAttr(RES_BOXATR_FORMAT); + const auto& rNew = GetFormatAttr(RES_BOXATR_FORMAT); + if(rOld != rNew) + SwClientNotify(*this, sw::LegacyModifyHint(&rOld, &rNew)); +} + + +namespace sw { + +bool IsFlyFrameFormatInHeader(const SwFrameFormat& rFormat) +{ + const SwFlyFrameFormat* pFlyFrameFormat = dynamic_cast<const SwFlyFrameFormat*>(&rFormat); + if (!pFlyFrameFormat) + return false; + SwFlyFrame* pFlyFrame = pFlyFrameFormat->GetFrame(); + if (!pFlyFrame) // fdo#54648: "hidden" drawing object has no layout frame + { + return false; + } + SwPageFrame* pPageFrame = pFlyFrame->FindPageFrameOfAnchor(); + SwFrame* pHeader = pPageFrame->Lower(); + if (pHeader->GetType() == SwFrameType::Header) + { + const SwFrame* pFrame = pFlyFrame->GetAnchorFrame(); + while (pFrame) + { + if (pFrame == pHeader) + return true; + pFrame = pFrame->GetUpper(); + } + } + return false; +} + +void CheckAnchoredFlyConsistency(SwDoc const& rDoc) +{ +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + SwNodes const& rNodes(rDoc.GetNodes()); + SwNodeOffset const count(rNodes.Count()); + for (SwNodeOffset i(0); i != count; ++i) + { + SwNode const*const pNode(rNodes[i]); + std::vector<SwFrameFormat*> const & rFlys(pNode->GetAnchoredFlys()); + for (const auto& rpFly : rFlys) + { + SwFormatAnchor const& rAnchor((*rpFly).GetAnchor(false)); + assert(&rAnchor.GetContentAnchor()->nNode.GetNode() == pNode); + } + } + SwFrameFormats const*const pSpzFrameFormats(rDoc.GetSpzFrameFormats()); + if (!pSpzFrameFormats) + return; + + for (auto it = pSpzFrameFormats->begin(); it != pSpzFrameFormats->end(); ++it) + { + SwFormatAnchor const& rAnchor((**it).GetAnchor(false)); + if (RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()) + { + assert(!rAnchor.GetContentAnchor() + // for invalid documents that lack text:anchor-page-number + // it may have an anchor before MakeFrames() is called + || (!SwIterator<SwFrame, SwFrameFormat>(**it).First())); + } + else + { + SwNode & rNode(rAnchor.GetContentAnchor()->nNode.GetNode()); + std::vector<SwFrameFormat*> const& rFlys(rNode.GetAnchoredFlys()); + assert(std::find(rFlys.begin(), rFlys.end(), *it) != rFlys.end()); + switch (rAnchor.GetAnchorId()) + { + case RndStdIds::FLY_AT_FLY: + assert(rNode.IsStartNode()); + break; + case RndStdIds::FLY_AT_PARA: + assert(rNode.IsTextNode() || rNode.IsTableNode()); + break; + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_CHAR: + assert(rNode.IsTextNode()); + break; + default: + assert(false); + break; + } + } + } +#else + (void) rDoc; +#endif +} + +} // namespace sw + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/calcmove.cxx b/sw/source/core/layout/calcmove.cxx new file mode 100644 index 000000000..184373585 --- /dev/null +++ b/sw/source/core/layout/calcmove.cxx @@ -0,0 +1,2215 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <viewopt.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <ndtxt.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/keepitem.hxx> +#include <osl/diagnose.h> +#include <svx/sdtaitm.hxx> + +#include <fmtfsize.hxx> +#include <fmtanchr.hxx> +#include <fmtclbl.hxx> + +#include <tabfrm.hxx> +#include <ftnfrm.hxx> +#include <txtfrm.hxx> +#include <sectfrm.hxx> +#include <dbg_lay.hxx> + +#include <sortedobjs.hxx> +#include <layouter.hxx> +#include <flyfrms.hxx> + +#include <DocumentSettingManager.hxx> +#include <IDocumentLayoutAccess.hxx> + +// Move methods + +/// Return value tells whether the Frame should be moved. +bool SwContentFrame::ShouldBwdMoved( SwLayoutFrame *pNewUpper, bool & ) +{ + if ( SwFlowFrame::IsMoveBwdJump() || !IsPrevObjMove() ) + { + // Floating back a frm uses a bit of time unfortunately. + // The most common case is the following: The Frame wants to float to + // somewhere where the FixSize is the same that the Frame itself has already. + // In that case it's pretty easy to check if the Frame has enough space + // for its VarSize. If this is NOT the case, we already know that + // we don't need to move. + // The Frame checks itself whether it has enough space - respecting the fact + // that it could possibly split itself if needed. + // If, however, the FixSize differs from the Frame or Flys are involved + // (either in the old or the new position), checking is pointless, + // and we have to move the Frame just to see what happens - if there's + // some space available to do it, that is. + + // The FixSize of the containers of Contents is always the width. + + // If we moved more than one sheet back (for example jumping over empty + // pages), we have to move either way. Otherwise, if the Frame doesn't fit + // into the page, empty pages wouldn't be respected anymore. + sal_uInt8 nMoveAnyway = 0; + SwPageFrame * const pNewPage = pNewUpper->FindPageFrame(); + SwPageFrame *pOldPage = FindPageFrame(); + + if ( SwFlowFrame::IsMoveBwdJump() ) + return true; + + if( IsInFootnote() && IsInSct() ) + { + SwFootnoteFrame* pFootnote = FindFootnoteFrame(); + SwSectionFrame* pMySect = pFootnote->FindSctFrame(); + if( pMySect && pMySect->IsFootnoteLock() ) + { + SwSectionFrame *pSect = pNewUpper->FindSctFrame(); + while( pSect && pSect->IsInFootnote() ) + pSect = pSect->GetUpper()->FindSctFrame(); + OSL_ENSURE( pSect, "Escaping footnote" ); + if( pSect != pMySect ) + return false; + } + } + SwRectFnSet aRectFnSet(this); + SwRectFnSet fnRectX(pNewUpper); + if( std::abs( fnRectX.GetWidth(pNewUpper->getFramePrintArea()) - + aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()) ) > 1 ) { + // In this case, only a WouldFit_ with test move is possible + nMoveAnyway = 2; + } + + // Do *not* move backward, if <nMoveAnyway> equals 3 and no space is left in new upper. + nMoveAnyway |= BwdMoveNecessary( pOldPage, getFrameArea() ); + { + const IDocumentSettingAccess& rIDSA = pNewPage->GetFormat()->getIDocumentSettingAccess(); + SwTwips nSpace = 0; + SwRect aRect( pNewUpper->getFramePrintArea() ); + aRect.Pos() += pNewUpper->getFrameArea().Pos(); + const SwFrame *pPrevFrame = pNewUpper->Lower(); + while ( pPrevFrame ) + { + SwTwips nNewTop = fnRectX.GetBottom(pPrevFrame->getFrameArea()); + // Consider lower spacing of last frame in a table cell + { + // Check if last frame is inside table and if it includes its lower spacing. + if ( !pPrevFrame->GetNext() && pPrevFrame->IsInTab() && + rIDSA.get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS) ) + { + const SwFrame* pLastFrame = pPrevFrame; + // if last frame is a section, take its last content + if ( pPrevFrame->IsSctFrame() ) + { + pLastFrame = static_cast<const SwSectionFrame*>(pPrevFrame)->FindLastContent(); + if ( pLastFrame && + pLastFrame->FindTabFrame() != pPrevFrame->FindTabFrame() ) + { + pLastFrame = pLastFrame->FindTabFrame(); + } + } + + if ( pLastFrame ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pLastFrame ); + const SwBorderAttrs& rAttrs = *aAccess.Get(); + nNewTop -= rAttrs.GetULSpace().GetLower(); + if (rIDSA.get(DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS)) + { + nNewTop -= rAttrs.CalcLineSpacing(); + } + } + } + } + fnRectX.SetTop( aRect, nNewTop ); + + pPrevFrame = pPrevFrame->GetNext(); + } + + nMoveAnyway |= BwdMoveNecessary( pNewPage, aRect); + + //determine space left in new upper frame + nSpace = fnRectX.GetHeight(aRect); + const SwViewShell *pSh = pNewUpper->getRootFrame()->GetCurrShell(); + if ( IsInFootnote() || + (pSh && pSh->GetViewOptions()->getBrowseMode()) || + pNewUpper->IsCellFrame() || + ( pNewUpper->IsInSct() && ( pNewUpper->IsSctFrame() || + ( pNewUpper->IsColBodyFrame() && + !pNewUpper->GetUpper()->GetPrev() && + !pNewUpper->GetUpper()->GetNext() ) ) ) ) + nSpace += pNewUpper->Grow( LONG_MAX, true ); + + if ( nMoveAnyway < 3 ) + { + if ( nSpace ) + { + // Do not notify footnotes which are stuck to the paragraph: + // This would require extremely confusing code, taking into + // account the widths + // and Flys, that in turn influence the footnotes, ... + + // WouldFit_ can only be used if the width is the same and + // ONLY self-anchored Flys are present. + + // WouldFit_ can also be used if ONLY Flys anchored + // somewhere else are present. + // In this case, the width doesn't even matter, + // because we're running a TestFormat in the new upper. + const sal_uInt8 nBwdMoveNecessaryResult = + BwdMoveNecessary( pNewPage, aRect); + const bool bObjsInNewUpper( nBwdMoveNecessaryResult == 2 || + nBwdMoveNecessaryResult == 3 ); + + return WouldFit_( nSpace, pNewUpper, nMoveAnyway == 2, + bObjsInNewUpper ); + } + // It's impossible for WouldFit_ to return a usable result if + // we have a fresh multi-column section - so we really have to + // float back unless there is no space. + return pNewUpper->IsInSct() && pNewUpper->IsColBodyFrame() && + !fnRectX.GetWidth(pNewUpper->getFramePrintArea()) && + ( pNewUpper->GetUpper()->GetPrev() || + pNewUpper->GetUpper()->GetNext() ); + } + + // Check for space left in new upper + return nSpace != 0; + } + } + return false; +} + +// Calc methods + +// Two little friendships form a secret society +inline void PrepareLock( SwFlowFrame *pTab ) +{ + pTab->LockJoin(); +} +inline void PrepareUnlock( SwFlowFrame *pTab ) +{ + pTab->UnlockJoin(); + +} + +// hopefully, one day this function simply will return 'false' +static bool lcl_IsCalcUpperAllowed( const SwFrame& rFrame ) +{ + return !rFrame.GetUpper()->IsSctFrame() && + !rFrame.GetUpper()->IsFooterFrame() && + // No format of upper Writer fly frame + !rFrame.GetUpper()->IsFlyFrame() && + !( rFrame.GetUpper()->IsTabFrame() && rFrame.GetUpper()->GetUpper()->IsInTab() ) && + !( rFrame.IsTabFrame() && rFrame.GetUpper()->IsInTab() ); +} + +/** Prepares the Frame for "formatting" (MakeAll()). + * + * This method serves to save stack space: To calculate the position of the Frame + * we have to make sure that the positions of Upper and Prev respectively are + * valid. This may require a recursive call (a loop would be quite expensive, + * as it's not required very often). + * + * Every call of MakeAll requires around 500 bytes on the stack - you easily + * see where this leads to. This method requires only a little bit of stack + * space, so the recursive call should not be a problem here. + * + * Another advantage is that one nice day, this method and with it the + * formatting of predecessors could be avoided. Then it could probably be + * possible to jump "quickly" to the document's end. + * + * @see MakeAll() + */ +void SwFrame::PrepareMake(vcl::RenderContext* pRenderContext) +{ + StackHack aHack; + if ( GetUpper() ) + { + SwFrameDeleteGuard aDeleteGuard(this); + if ( lcl_IsCalcUpperAllowed( *this ) ) + GetUpper()->Calc(pRenderContext); + OSL_ENSURE( GetUpper(), ":-( Layout unstable (Upper gone)." ); + if ( !GetUpper() ) + return; + + const bool bCnt = IsContentFrame(); + const bool bTab = IsTabFrame(); + bool bNoSect = IsInSct(); + bool bOldTabLock = false, bFoll = false; + SwFlowFrame* pThis = bCnt ? static_cast<SwContentFrame*>(this) : nullptr; + + if ( bTab ) + { + pThis = static_cast<SwTabFrame*>(this); + bOldTabLock = static_cast<SwTabFrame*>(this)->IsJoinLocked(); + ::PrepareLock( static_cast<SwTabFrame*>(this) ); + bFoll = pThis->IsFollow(); + } + else if( IsSctFrame() ) + { + pThis = static_cast<SwSectionFrame*>(this); + bFoll = pThis->IsFollow(); + bNoSect = false; + } + else if ( bCnt ) + { + bFoll = pThis->IsFollow(); + if ( bFoll && GetPrev() ) + { + // Do not follow the chain when we need only one instance + const SwTextFrame* pMaster = static_cast<SwContentFrame*>(this)->FindMaster(); + if ( pMaster && pMaster->IsLocked() ) + { + MakeAll(pRenderContext); + return; + } + } + } + + // There is no format of previous frame, if current frame is a table + // frame and its previous frame wants to keep with it. + const bool bFormatPrev = !bTab || + !GetPrev() || + !GetPrev()->GetAttrSet()->GetKeep().GetValue(); + if ( bFormatPrev ) + { + SwFrame *pFrame = GetUpper()->Lower(); + while ( pFrame != this ) + { + OSL_ENSURE( pFrame, ":-( Layout unstable (this not found)." ); + if ( !pFrame ) + return; //Oioioioi ... + + if ( !pFrame->isFrameAreaDefinitionValid() ) + { + // A small interference that hopefully improves on the stability: + // If I'm Follow AND neighbor of a Frame before me, it would delete + // me when formatting. This as you can see could easily become a + // confusing situation that we want to avoid. + if ( bFoll && pFrame->IsFlowFrame() && + SwFlowFrame::CastFlowFrame(pFrame)->IsAnFollow( pThis ) ) + break; + + bool const isLast(pFrame->GetNext() == this); + // note: this seems obvious but does *not* hold, a MakeAll() + // could move more than 1 frame backwards! + // that's why FindNext() is used below + // assert(pFrame->GetUpper() == GetUpper()); + pFrame->MakeAll(pRenderContext); + if( IsSctFrame() && !static_cast<SwSectionFrame*>(this)->GetSection() ) + break; + if (isLast && pFrame->GetUpper() != GetUpper()) + { + assert(GetUpper()->Lower() == this + // empty section frames are created all the time... + || GetUpper()->Lower()->IsSctFrame() + // tab frame/section frame may split multiple times + || ( SwFlowFrame::CastFlowFrame(pFrame) + && SwFlowFrame::CastFlowFrame(GetUpper()->Lower()) + && SwFlowFrame::CastFlowFrame(pFrame)->IsAnFollow( + SwFlowFrame::CastFlowFrame(GetUpper()->Lower())) + && (GetUpper()->Lower()->GetNext() == this + // if it's more than 10 pages long... + || (SwFlowFrame::CastFlowFrame(GetUpper()->Lower())->GetFollow() + == SwFlowFrame::CastFlowFrame(GetUpper()->Lower()->GetNext()) + && GetUpper()->Lower()->GetNext()->GetNext() == this) + // pre-existing empty section frames may end up between them... + || GetUpper()->Lower()->GetNext()->IsSctFrame()))); + break; // tdf#119109 frame was moved backward, prevent + // FindNext() returning a frame inside this if + } // this is a table! + } + // With ContentFrames, the chain may be broken while walking through + // it. Therefore we have to figure out the next frame in a bit more + // complicated way. However, I'll HAVE to get back to myself + // sometime again. + pFrame = pFrame->FindNext(); + + // If we started out in a SectionFrame, it might have happened that + // we landed in a Section Follow via the MakeAll calls. + // FindNext only gives us the SectionFrame, not it's content - we + // won't find ourselves anymore! + if( bNoSect && pFrame && pFrame->IsSctFrame() ) + { + SwFrame* pCnt = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pCnt ) + pFrame = pCnt; + } + } + OSL_ENSURE( GetUpper(), "Layout unstable (Upper gone II)." ); + if ( !GetUpper() ) + return; + + if ( lcl_IsCalcUpperAllowed( *this ) ) + GetUpper()->Calc(pRenderContext); + + OSL_ENSURE( GetUpper(), "Layout unstable (Upper gone III)." ); + } + + if ( bTab && !bOldTabLock ) + ::PrepareUnlock( static_cast<SwTabFrame*>(this) ); + } + MakeAll(pRenderContext); +} + +void SwFrame::OptPrepareMake() +{ + // #i23129#, #i36347# - no format of upper Writer fly frame + if ( GetUpper() && !GetUpper()->IsFooterFrame() && + !GetUpper()->IsFlyFrame() ) + { + { + SwFrameDeleteGuard aDeleteGuard(this); + GetUpper()->Calc(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); + } + OSL_ENSURE( GetUpper(), ":-( Layout unstable (Upper gone)." ); + if ( !GetUpper() ) + return; + } + if ( GetPrev() && !GetPrev()->isFrameAreaDefinitionValid() ) + { + PrepareMake(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); + } + else + { + StackHack aHack; + MakeAll(IsRootFrame() ? nullptr : getRootFrame()->GetCurrShell()->GetOut()); + } +} + +void SwFrame::PrepareCursor() +{ + StackHack aHack; + if( GetUpper() && !GetUpper()->IsSctFrame() ) + { + const bool bCnt = IsContentFrame(); + const bool bTab = IsTabFrame(); + bool bNoSect = IsInSct(); + + std::optional<FlowFrameJoinLockGuard> tabGuard; + std::optional<SwFrameDeleteGuard> rowGuard; + SwFlowFrame* pThis = bCnt ? static_cast<SwContentFrame*>(this) : nullptr; + + if ( bTab ) + { + tabGuard.emplace(static_cast<SwTabFrame*>(this)); // tdf#125741 + pThis = static_cast<SwTabFrame*>(this); + } + else if (IsRowFrame()) + { + rowGuard.emplace(this); // tdf#125741 keep this alive + } + else if( IsSctFrame() ) + { + pThis = static_cast<SwSectionFrame*>(this); + bNoSect = false; + } + + GetUpper()->PrepareCursor(); + GetUpper()->Calc(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); + + OSL_ENSURE( GetUpper(), ":-( Layout unstable (Upper gone)." ); + if ( !GetUpper() ) + return; + + bool const bFoll = pThis && pThis->IsFollow(); + + SwFrame *pFrame = GetUpper()->Lower(); + while ( pFrame != this ) + { + OSL_ENSURE( pFrame, ":-( Layout unstable (this not found)." ); + if ( !pFrame ) + return; //Oioioioi ... + + if ( !pFrame->isFrameAreaDefinitionValid() ) + { + // A small interference that hopefully improves on the stability: + // If I'm Follow AND neighbor of a Frame before me, it would delete + // me when formatting. This as you can see could easily become a + // confusing situation that we want to avoid. + if ( bFoll && pFrame->IsFlowFrame() && + SwFlowFrame::CastFlowFrame(pFrame)->IsAnFollow( pThis ) ) + break; + + bool const isLast(pFrame->GetNext() == this); + pFrame->MakeAll(getRootFrame()->GetCurrShell()->GetOut()); + if (isLast && pFrame->GetUpper() != GetUpper()) + { + assert(GetUpper()->Lower() == this + // empty section frames are created all the time... + || GetUpper()->Lower()->IsSctFrame() + // tab frame/section frame may split multiple times + || ( SwFlowFrame::CastFlowFrame(pFrame) + && SwFlowFrame::CastFlowFrame(GetUpper()->Lower()) + && SwFlowFrame::CastFlowFrame(pFrame)->IsAnFollow( + SwFlowFrame::CastFlowFrame(GetUpper()->Lower())) + && (GetUpper()->Lower()->GetNext() == this + // if it's more than 10 pages long... + || (SwFlowFrame::CastFlowFrame(GetUpper()->Lower())->GetFollow() + == SwFlowFrame::CastFlowFrame(GetUpper()->Lower()->GetNext()) + && GetUpper()->Lower()->GetNext()->GetNext() == this) + // pre-existing empty section frames may end up between them... + || GetUpper()->Lower()->GetNext()->IsSctFrame()))); + break; // tdf#119109 frame was moved backward, prevent + // FindNext() returning a frame inside this if + } // this is a table! + } + // With ContentFrames, the chain may be broken while walking through + // it. Therefore we have to figure out the next frame in a bit more + // complicated way. However, I'll HAVE to get back to myself + // sometime again. + pFrame = pFrame->FindNext(); + if( bNoSect && pFrame && pFrame->IsSctFrame() ) + { + SwFrame* pCnt = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pCnt ) + pFrame = pCnt; + } + } + OSL_ENSURE( GetUpper(), "Layout unstable (Upper gone II)." ); + if ( !GetUpper() ) + return; + + GetUpper()->Calc(getRootFrame()->GetCurrShell()->GetOut()); + + OSL_ENSURE( GetUpper(), "Layout unstable (Upper gone III)." ); + } + Calc(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); +} + +// Here we return GetPrev(); however we will ignore empty SectionFrames +static SwFrame* lcl_Prev( SwFrame* pFrame, bool bSectPrv = true ) +{ + SwFrame* pRet = pFrame->GetPrev(); + if( !pRet && pFrame->GetUpper() && pFrame->GetUpper()->IsSctFrame() && + bSectPrv && !pFrame->IsColumnFrame() ) + pRet = pFrame->GetUpper()->GetPrev(); + while( pRet && pRet->IsSctFrame() && + !static_cast<SwSectionFrame*>(pRet)->GetSection() ) + pRet = pRet->GetPrev(); + return pRet; +} + +static SwFrame* lcl_NotHiddenPrev( SwFrame* pFrame ) +{ + SwFrame *pRet = pFrame; + do + { + pRet = lcl_Prev( pRet ); + } while ( pRet && pRet->IsTextFrame() && static_cast<SwTextFrame*>(pRet)->IsHiddenNow() ); + return pRet; +} + +void SwFrame::MakePos() +{ + if ( isFrameAreaPositionValid() ) + return; + + setFrameAreaPositionValid(true); + bool bUseUpper = false; + SwFrame* pPrv = lcl_Prev( this ); + if ( pPrv && + ( !pPrv->IsContentFrame() || + ( static_cast<SwContentFrame*>(pPrv)->GetFollow() != this ) ) + ) + { + if ( !StackHack::IsLocked() && + ( !IsInSct() || IsSctFrame() ) && + !pPrv->IsSctFrame() && + !pPrv->GetAttrSet()->GetKeep().GetValue() + ) + { + pPrv->Calc(getRootFrame()->GetCurrShell() ? getRootFrame()->GetCurrShell()->GetOut() : nullptr); // This may cause Prev to vanish! + } + else if ( pPrv->getFrameArea().Top() == 0 ) + { + bUseUpper = true; + } + } + + pPrv = lcl_Prev( this, false ); + const SwFrameType nMyType = GetType(); + SwRectFnSet aRectFnSet((IsCellFrame() && GetUpper() ? GetUpper() : this)); + if ( !bUseUpper && pPrv ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos( pPrv->getFrameArea().Pos() ); + + if( FRM_NEIGHBOUR & nMyType ) + { + const bool bR2L = IsRightToLeft(); + + if( bR2L ) + { + aRectFnSet.SetPosX( aFrm, aRectFnSet.GetLeft(aFrm) - aRectFnSet.GetWidth(aFrm) ); + } + else + { + aRectFnSet.SetPosX( aFrm, aRectFnSet.GetLeft(aFrm) + aRectFnSet.GetWidth(pPrv->getFrameArea()) ); + } + + // cells may now leave their uppers + if( aRectFnSet.IsVert() && SwFrameType::Cell & nMyType ) + { + aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width() + pPrv->getFrameArea().Width()); + } + } + else if( aRectFnSet.IsVert() && FRM_NOTE_VERT & nMyType ) + { + if ( aRectFnSet.IsVertL2R() ) + { + aFrm.Pos().setX(aFrm.Pos().getX() + pPrv->getFrameArea().Width()); + } + else + { + aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width()); + } + } + else + { + aFrm.Pos().setY(aFrm.Pos().getY() + pPrv->getFrameArea().Height()); + } + } + else if ( GetUpper() ) + { + // If parent frame is a footer frame and its <ColLocked()>, then + // do *not* calculate it. + // NOTE: Footer frame is <ColLocked()> during its + // <FormatSize(..)>, which is called from <Format(..)>, which + // is called from <MakeAll()>, which is called from <Calc()>. + // #i56850# + // - no format of upper Writer fly frame, which is anchored + // at-paragraph or at-character. + if ( !GetUpper()->IsTabFrame() && + !( IsTabFrame() && GetUpper()->IsInTab() ) && + !GetUpper()->IsSctFrame() && + !dynamic_cast<SwFlyAtContentFrame*>(GetUpper()) && + !( GetUpper()->IsFooterFrame() && + GetUpper()->IsColLocked() ) + ) + { + GetUpper()->Calc(getRootFrame()->GetCurrShell()->GetOut()); + } + pPrv = lcl_Prev( this, false ); + if ( !bUseUpper && pPrv ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos( pPrv->getFrameArea().Pos() ); + + if( FRM_NEIGHBOUR & nMyType ) + { + const bool bR2L = IsRightToLeft(); + + if( bR2L ) + { + aRectFnSet.SetPosX( aFrm, aRectFnSet.GetLeft(aFrm) - aRectFnSet.GetWidth(aFrm) ); + } + else + { + aRectFnSet.SetPosX( aFrm, aRectFnSet.GetLeft(aFrm) + aRectFnSet.GetWidth(pPrv->getFrameArea()) ); + } + + // cells may now leave their uppers + if( aRectFnSet.IsVert() && SwFrameType::Cell & nMyType ) + { + aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width() + pPrv->getFrameArea().Width()); + } + } + else if( aRectFnSet.IsVert() && FRM_NOTE_VERT & nMyType ) + { + aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width()); + } + else + { + aFrm.Pos().setY(aFrm.Pos().getY() + pPrv->getFrameArea().Height()); + } + } + else + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos( GetUpper()->getFrameArea().Pos() ); + + if( GetUpper()->IsFlyFrame() ) + { + aFrm.Pos() += static_cast<SwFlyFrame*>(GetUpper())->ContentPos(); + } + else + { + aFrm.Pos() += GetUpper()->getFramePrintArea().Pos(); + } + + if( FRM_NEIGHBOUR & nMyType && IsRightToLeft() ) + { + if( aRectFnSet.IsVert() ) + { + aFrm.Pos().setY(aFrm.Pos().getY() + GetUpper()->getFramePrintArea().Height() - aFrm.Height()); + } + else + { + aFrm.Pos().setX(aFrm.Pos().getX() + GetUpper()->getFramePrintArea().Width() - aFrm.Width()); + } + } + else if( aRectFnSet.IsVert() && !aRectFnSet.IsVertL2R() && FRM_NOTE_VERT & nMyType ) + { + aFrm.Pos().setX(aFrm.Pos().getX() - aFrm.Width() + GetUpper()->getFramePrintArea().Width()); + } + } + } + else + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX(0); + aFrm.Pos().setY(0); + } + + if( IsBodyFrame() && aRectFnSet.IsVert() && !aRectFnSet.IsVertL2R() && GetUpper() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX(aFrm.Pos().getX() + GetUpper()->getFramePrintArea().Width() - aFrm.Width()); + } + + setFrameAreaPositionValid(true); +} + +// #i28701# - new type <SwSortedObjs> +static void lcl_CheckObjects(SwSortedObjs& rSortedObjs, const SwFrame* pFrame, tools::Long& rBot) +{ + // And then there can be paragraph anchored frames that sit below their paragraph. + tools::Long nMax = 0; + for (SwAnchoredObject* pObj : rSortedObjs) + { + // #i28701# - consider changed type of <SwSortedObjs> + // entries. + tools::Long nTmp = 0; + if ( auto pFly = pObj->DynCastFlyFrame() ) + { + if( pFly->getFrameArea().Top() != FAR_AWAY && + ( pFrame->IsPageFrame() ? pFly->IsFlyLayFrame() : + ( pFly->IsFlyAtContentFrame() && + ( pFrame->IsBodyFrame() ? pFly->GetAnchorFrame()->IsInDocBody() : + pFly->GetAnchorFrame()->IsInFootnote() ) ) ) ) + { + nTmp = pFly->getFrameArea().Bottom(); + } + } + else + nTmp = pObj->GetObjRect().Bottom(); + nMax = std::max( nTmp, nMax ); + } + ++nMax; // Lower edge vs. height! + rBot = std::max( rBot, nMax ); +} + +size_t SwPageFrame::GetContentHeight(const tools::Long nTop, const tools::Long nBottom) const +{ + OSL_ENSURE(!(FindBodyCont() && FindBodyCont()->Lower() && FindBodyCont()->Lower()->IsColumnFrame()), + "SwPageFrame::GetContentHeight(): No support for columns."); + + // In pages without columns, the content defines the size. + tools::Long nBot = getFrameArea().Top() + nTop; + const SwFrame *pFrame = Lower(); + while (pFrame) + { + tools::Long nTmp = 0; + const SwFrame *pCnt = static_cast<const SwLayoutFrame*>(pFrame)->ContainsAny(); + while (pCnt && (pCnt->GetUpper() == pFrame || + static_cast<const SwLayoutFrame*>(pFrame)->IsAnLower(pCnt))) + { + nTmp += pCnt->getFrameArea().Height(); + if (pCnt->IsTextFrame() && + static_cast<const SwTextFrame*>(pCnt)->IsUndersized()) + { + // This TextFrame would like to be a bit bigger. + nTmp += static_cast<const SwTextFrame*>(pCnt)->GetParHeight() + - pCnt->getFramePrintArea().Height(); + } + else if (pCnt->IsSctFrame()) + { + // Grow if undersized, but don't shrink if oversized. + const auto delta = static_cast<const SwSectionFrame*>(pCnt)->CalcUndersize(); + if (delta > 0) + nTmp += delta; + } + + pCnt = pCnt->FindNext(); + } + // Consider invalid body frame properties + if (pFrame->IsBodyFrame() && + (!pFrame->isFrameAreaSizeValid() || + !pFrame->isFramePrintAreaValid()) && + (pFrame->getFrameArea().Height() < pFrame->getFramePrintArea().Height()) + ) + { + nTmp = std::min(nTmp, pFrame->getFrameArea().Height()); + } + else + { + // Assert invalid lower property + OSL_ENSURE(!(pFrame->getFrameArea().Height() < pFrame->getFramePrintArea().Height()), + "SwPageFrame::GetContentHeight(): Lower with frame height < printing height"); + nTmp += pFrame->getFrameArea().Height() - pFrame->getFramePrintArea().Height(); + } + if (!pFrame->IsBodyFrame()) + nTmp = std::min(nTmp, pFrame->getFrameArea().Height()); + nBot += nTmp; + // Here we check whether paragraph anchored objects + // protrude outside the Body/FootnoteCont. + if (m_pSortedObjs && !pFrame->IsHeaderFrame() && + !pFrame->IsFooterFrame()) + lcl_CheckObjects(*m_pSortedObjs, pFrame, nBot); + pFrame = pFrame->GetNext(); + } + nBot += nBottom; + // And the page anchored ones + if (m_pSortedObjs) + lcl_CheckObjects(*m_pSortedObjs, this, nBot); + nBot -= getFrameArea().Top(); + + return nBot; +} + +void SwPageFrame::MakeAll(vcl::RenderContext* pRenderContext) +{ + PROTOCOL_ENTER( this, PROT::MakeAll, DbgAction::NONE, nullptr ) + + const SwRect aOldRect( getFrameArea() ); // Adjust root size + const SwLayNotify aNotify( this ); // takes care of the notification in the dtor + std::optional<SwBorderAttrAccess> oAccess; + const SwBorderAttrs*pAttrs = nullptr; + + while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + if ( !isFrameAreaPositionValid() ) + { + setFrameAreaPositionValid(true); // positioning of the pages is taken care of by the root frame + } + + if ( !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + if ( IsEmptyPage() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( 0 ); + aFrm.Height( 0 ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( 0 ); + aPrt.Height( 0 ); + aPrt.Left( 0 ); + aPrt.Top( 0 ); + + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + } + else + { + if (!oAccess) + { + oAccess.emplace(SwFrame::GetCache(), this); + pAttrs = oAccess->Get(); + } + assert(pAttrs); + + SwRootFrame* pRootFrame = getRootFrame(); + SwViewShell* pSh = pRootFrame->GetCurrShell(); + if (pSh && pSh->GetViewOptions()->getBrowseMode()) + { + // In BrowseView, we use fixed settings + const Size aBorder = pRenderContext->PixelToLogic( pSh->GetBrowseBorder() ); + const tools::Long nTop = pAttrs->CalcTopLine() + aBorder.Height(); + const tools::Long nBottom = pAttrs->CalcBottomLine()+ aBorder.Height(); + + tools::Long nWidth = GetUpper() ? static_cast<SwRootFrame*>(GetUpper())->GetBrowseWidth() : 0; + const auto nDefWidth = pSh->GetBrowseWidth(); + if (nWidth < nDefWidth) + nWidth = nDefWidth; + nWidth += + 2 * aBorder.Width(); + + constexpr tools::Long constTwips_2cm = o3tl::toTwips(2, o3tl::Length::cm); + nWidth = std::max(nWidth, 2L * aBorder.Width() + constTwips_2cm); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( nWidth ); + + SwLayoutFrame *pBody = FindBodyCont(); + if ( pBody && pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + { + // Columns have a fixed height + aFrm.Height( pAttrs->GetSize().Height() ); + } + else + { + // In pages without columns, the content defines the size. + tools::Long nBot = GetContentHeight(nTop, nBottom); + + // #i35143# - If second page frame + // exists, the first page doesn't have to fulfill the + // visible area. + if ( !GetPrev() && !GetNext() ) + { + nBot = std::max( nBot, pSh->VisArea().Height() ); + } + // #i35143# - Assure, that the page + // doesn't exceed the defined browse height. + aFrm.Height( std::min( nBot, BROWSE_HEIGHT ) ); + } + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Left ( pAttrs->CalcLeftLine() + aBorder.Width() ); + aPrt.Top ( nTop ); + aPrt.Width( getFrameArea().Width() - ( aPrt.Left() + pAttrs->CalcRightLine() + aBorder.Width() ) ); + aPrt.Height( getFrameArea().Height() - (nTop + nBottom) ); + } + + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + continue; + } + else if (pSh && pSh->GetViewOptions()->IsWhitespaceHidden() && pRootFrame->GetLastPage() != this) + { + tools::Long height = 0; + SwLayoutFrame *pBody = FindBodyCont(); + if ( pBody && pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + { + // Columns have a fixed height + height = pAttrs->GetSize().Height(); + } + else + { + // No need for borders. + height = GetContentHeight(0, 0); + } + + if (height > 0) + { + ChgSize(Size(getFrameArea().Width(), height)); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Top(0); + aPrt.Height(height); + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + continue; + } + + // Fallback to default formatting. Especially relevant + // when loading a doc when Hide Whitespace is enabled. + // Heights are zero initially. + } + + // Set FixSize. For pages, this is not done from Upper, but from + // the attribute. + //FIXME: This resets the size when (isFrameAreaSizeValid() && !isFramePrintAreaValid()). + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.SSize( pAttrs->GetSize() ); + } + Format( pRenderContext, pAttrs ); + } + } + } //while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + + if ( getFrameArea() != aOldRect && GetUpper() ) + static_cast<SwRootFrame*>(GetUpper())->CheckViewLayout( nullptr, nullptr ); + + OSL_ENSURE( !GetUpper() || GetUpper()->getFramePrintArea().Width() >= getFrameArea().Width(), + "Upper (Root) must be wide enough to contain the widest page"); +} + +void SwLayoutFrame::MakeAll(vcl::RenderContext* /*pRenderContext*/) +{ + PROTOCOL_ENTER( this, PROT::MakeAll, DbgAction::NONE, nullptr ) + + // takes care of the notification in the dtor + const SwLayNotify aNotify( this ); + bool bVert = IsVertical(); + + SwRectFn fnRect = ( IsNeighbourFrame() == bVert )? fnRectHori : ( IsVertLR() ? (IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ); + + std::optional<SwBorderAttrAccess> oAccess; + const SwBorderAttrs*pAttrs = nullptr; + + while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + if ( !isFrameAreaPositionValid() ) + MakePos(); + + if ( GetUpper() ) + { + // NEW TABLES + if ( IsLeaveUpperAllowed() ) + { + if ( !isFrameAreaSizeValid() ) + { + setFramePrintAreaValid(false); + } + } + else + { + if ( !isFrameAreaSizeValid() ) + { + // Set FixSize; VarSize is set by Format() after calculating the PrtArea + setFramePrintAreaValid(false); + + SwTwips nPrtWidth = (GetUpper()->getFramePrintArea().*fnRect->fnGetWidth)(); + if( bVert && ( IsBodyFrame() || IsFootnoteContFrame() ) ) + { + SwFrame* pNxt = GetPrev(); + while( pNxt && !pNxt->IsHeaderFrame() ) + pNxt = pNxt->GetPrev(); + if( pNxt ) + nPrtWidth -= pNxt->getFrameArea().Height(); + pNxt = GetNext(); + while( pNxt && !pNxt->IsFooterFrame() ) + pNxt = pNxt->GetNext(); + if( pNxt ) + nPrtWidth -= pNxt->getFrameArea().Height(); + } + + const tools::Long nDiff = nPrtWidth - (getFrameArea().*fnRect->fnGetWidth)(); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + // SwRectFn switched between horizontal and vertical when bVert == IsNeighbourFrame(). + // We pick fnSubLeft or fnAddRight that is correspondant to SwRectFn->fnAddBottom + if( ( IsCellFrame() && IsRightToLeft() ) || ( IsColumnFrame() && bVert && !IsVertLR() ) ) + { + (aFrm.*fnRect->fnSubLeft)( nDiff ); + } + else + { + (aFrm.*fnRect->fnAddRight)( nDiff ); + } + } + else + { + // Don't leave your upper + const SwTwips nDeadLine = (GetUpper()->*fnRect->fnGetPrtBottom)(); + if( (getFrameArea().*fnRect->fnOverStep)( nDeadLine ) ) + { + setFrameAreaSizeValid(false); + } + } + } + } + + if ( !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + if ( !oAccess ) + { + oAccess.emplace(SwFrame::GetCache(), this); + pAttrs = oAccess->Get(); + } + Format( getRootFrame()->GetCurrShell()->GetOut(), pAttrs ); + } + } //while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) +} + +bool SwTextNode::IsCollapse() const +{ + if (GetDoc().GetDocumentSettingManager().get( DocumentSettingId::COLLAPSE_EMPTY_CELL_PARA ) + && GetText().isEmpty()) + { + SwNodeOffset nIdx=GetIndex(); + const SwEndNode *pNdBefore=GetNodes()[nIdx-1]->GetEndNode(); + const SwEndNode *pNdAfter=GetNodes()[nIdx+1]->GetEndNode(); + + // The paragraph is collapsed only if the NdAfter is the end of a cell + bool bInTable = FindTableNode( ) != nullptr; + + SwSortedObjs* pObjs = getLayoutFrame( GetDoc().getIDocumentLayoutAccess().GetCurrentLayout() )->GetDrawObjs( ); + const size_t nObjs = ( pObjs != nullptr ) ? pObjs->size( ) : 0; + + return pNdBefore!=nullptr && pNdAfter!=nullptr && nObjs == 0 && bInTable; + } + + return false; +} + +bool SwFrame::IsCollapse() const +{ + if (!IsTextFrame()) + return false; + + const SwTextFrame *pTextFrame = static_cast<const SwTextFrame*>(this); + const SwTextNode *pTextNode = pTextFrame->GetTextNodeForParaProps(); + // TODO this SwTextNode function is pointless and should be merged in here + return pTextFrame->GetText().isEmpty() && pTextNode && pTextNode->IsCollapse(); +} + +void SwContentFrame::MakePrtArea( const SwBorderAttrs &rAttrs ) +{ + if ( isFramePrintAreaValid() ) + return; + + setFramePrintAreaValid(true); + SwRectFnSet aRectFnSet(this); + const bool bTextFrame = IsTextFrame(); + SwTwips nUpper = 0; + if ( bTextFrame && static_cast<SwTextFrame*>(this)->IsHiddenNow() ) + { + if ( static_cast<SwTextFrame*>(this)->HasFollow() ) + static_cast<SwTextFrame*>(this)->JoinFrame(); + + if( aRectFnSet.GetHeight(getFramePrintArea()) ) + { + static_cast<SwTextFrame*>(this)->HideHidden(); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Pos().setX(0); + aPrt.Pos().setY(0); + aRectFnSet.SetWidth( aPrt, aRectFnSet.GetWidth(getFrameArea()) ); + aRectFnSet.SetHeight( aPrt, 0 ); + } + + nUpper = -( aRectFnSet.GetHeight(getFrameArea()) ); + } + else + { + // Simplification: ContentFrames are always variable in height! + + // At the FixSize, the surrounding Frame enforces the size; + // the borders are simply subtracted. + const tools::Long nLeft = rAttrs.CalcLeft( this ); + const tools::Long nRight = rAttrs.CalcRight( this ); + aRectFnSet.SetXMargins( *this, nLeft, nRight ); + + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + SwTwips nWidthArea; + if( pSh && 0!=(nWidthArea=aRectFnSet.GetWidth(pSh->VisArea())) && + GetUpper()->IsPageBodyFrame() && // but not for BodyFrames in Columns + pSh->GetViewOptions()->getBrowseMode() ) + { + // Do not protrude the edge of the visible area. The page may be + // wider, because there may be objects with excess width + // (RootFrame::ImplCalcBrowseWidth()) + tools::Long nMinWidth = 0; + + for (size_t i = 0; GetDrawObjs() && i < GetDrawObjs()->size(); ++i) + { + // #i28701# - consider changed type of + // <SwSortedObjs> entries + SwAnchoredObject* pObj = (*GetDrawObjs())[i]; + const SwFrameFormat& rFormat = pObj->GetFrameFormat(); + const bool bFly = pObj->DynCastFlyFrame() != nullptr; + if ((bFly && (FAR_AWAY == pObj->GetObjRect().Width())) + || rFormat.GetFrameSize().GetWidthPercent()) + { + continue; + } + + if ( RndStdIds::FLY_AS_CHAR == rFormat.GetAnchor().GetAnchorId() ) + { + nMinWidth = std::max( nMinWidth, + bFly ? rFormat.GetFrameSize().GetWidth() + : pObj->GetObjRect().Width() ); + } + } + + const Size aBorder = pSh->GetOut()->PixelToLogic( pSh->GetBrowseBorder() ); + tools::Long nWidth = nWidthArea - 2 * ( IsVertical() ? aBorder.Height() : aBorder.Width() ); + nWidth -= aRectFnSet.GetLeft(getFramePrintArea()); + nWidth -= rAttrs.CalcRightLine(); + nWidth = std::max( nMinWidth, nWidth ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetWidth( aPrt, std::min( nWidth, aRectFnSet.GetWidth(aPrt) ) ); + } + + if ( aRectFnSet.GetWidth(getFramePrintArea()) <= MINLAY ) + { + // The PrtArea should already be at least MINLAY wide, matching the + // minimal values of the UI + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetWidth( aPrt, std::min( tools::Long(MINLAY), aRectFnSet.GetWidth(getFrameArea()) ) ); + SwTwips nTmp = aRectFnSet.GetWidth(getFrameArea()) - aRectFnSet.GetWidth(aPrt); + + if( aRectFnSet.GetLeft(aPrt) > nTmp ) + { + aRectFnSet.SetLeft( aPrt, nTmp ); + } + } + + // The following rules apply for VarSize: + // 1. The first entry of a chain has no top border + // 2. There is never a bottom border + // 3. The top border is the maximum of the distance + // of Prev downwards and our own distance upwards + // Those three rules apply when calculating spacings + // that are given by UL- and LRSpace. There might be a spacing + // in all directions however; this may be caused by borders + // and / or shadows. + // 4. The spacing for TextFrames corresponds to the interline lead, + // at a minimum. + + nUpper = CalcUpperSpace( &rAttrs ); + + SwTwips nLower = CalcLowerSpace( &rAttrs ); + if (IsCollapse()) { + nUpper=0; + nLower=0; + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetPosY( aPrt, !aRectFnSet.IsVert() ? nUpper : nLower); + } + + nUpper += nLower; + nUpper -= aRectFnSet.GetHeight(getFrameArea()) - aRectFnSet.GetHeight(getFramePrintArea()); + } + // If there's a difference between old and new size, call Grow() or + // Shrink() respectively. + if ( nUpper ) + { + if ( nUpper > 0 ) + GrowFrame( nUpper ); + else + ShrinkFrame( -nUpper ); + } +} + +#define STOP_FLY_FORMAT 10 +// - loop prevention +const int cnStopFormat = 15; + +inline void ValidateSz( SwFrame *pFrame ) +{ + if ( pFrame ) + { + pFrame->setFrameAreaSizeValid(true); + pFrame->setFramePrintAreaValid(true); + } +} + +void SwContentFrame::MakeAll(vcl::RenderContext* /*pRenderContext*/) +{ + OSL_ENSURE( GetUpper(), "no Upper?" ); + OSL_ENSURE( IsTextFrame(), "MakeAll(), NoText" ); + + if ( !IsFollow() && StackHack::IsLocked() ) + return; + + if ( IsJoinLocked() ) + return; + + OSL_ENSURE( !static_cast<SwTextFrame*>(this)->IsSwapped(), "Calculation of a swapped frame" ); + + StackHack aHack; + + if ( static_cast<SwTextFrame*>(this)->IsLocked() ) + { + OSL_FAIL( "Format for locked TextFrame." ); + return; + } + + std::optional<SwFrameDeleteGuard> oDeleteGuard(std::in_place, this); + LockJoin(); + tools::Long nFormatCount = 0; + // - loop prevention + int nConsecutiveFormatsWithoutChange = 0; + PROTOCOL_ENTER( this, PROT::MakeAll, DbgAction::NONE, nullptr ) + + // takes care of the notification in the dtor + std::optional<SwContentNotify> oNotify( std::in_place, this ); + + // as long as bMakePage is true, a new page can be created (exactly once) + bool bMakePage = true; + // bMovedBwd gets set to true when the frame flows backwards + bool bMovedBwd = false; + // as long as bMovedFwd is false, the Frame may flow backwards (until + // it has been moved forward once) + bool bMovedFwd = false; + sal_Bool bFormatted = false; // For the widow/orphan rules, we encourage the + // last ContentFrame of a chain to format. This only + // needs to happen once. Every time the Frame is + // moved, the flag will have to be reset. + bool bMustFit = false; // Once the emergency brake is pulled, + // no other prepares will be triggered + bool bFitPromise = false; // If a paragraph didn't fit, but promises + // with WouldFit that it would adjust accordingly, + // this flag is set. If it turns out that it + // didn't keep it's promise, we can act in a + // controlled fashion. + const bool bFly = IsInFly(); + const bool bTab = IsInTab(); + const bool bFootnote = IsInFootnote(); + const bool bSct = IsInSct(); + Point aOldFramePos; // This is so we can compare with the last pos + Point aOldPrtPos; // and determine whether it makes sense to Prepare + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + if ( !IsFollow() && rAttrs.JoinedWithPrev( *(this) ) ) + { + oNotify->SetBordersJoinedWithPrev(); + } + + const bool bKeep = IsKeep(rAttrs.GetAttrSet().GetKeep(), GetBreakItem()); + + std::unique_ptr<SwSaveFootnoteHeight> pSaveFootnote; + if ( bFootnote ) + { + SwFootnoteFrame *pFootnote = FindFootnoteFrame(); + SwSectionFrame* pSct = pFootnote->FindSctFrame(); + if ( !static_cast<SwTextFrame*>(pFootnote->GetRef())->IsLocked() ) + { + SwFootnoteBossFrame* pBoss = pFootnote->GetRef()->FindFootnoteBossFrame( + pFootnote->GetAttr()->GetFootnote().IsEndNote() ); + if( !pSct || pSct->IsColLocked() || !pSct->Growable() ) + pSaveFootnote.reset( new SwSaveFootnoteHeight( pBoss, + static_cast<SwTextFrame*>(pFootnote->GetRef())->GetFootnoteLine( pFootnote->GetAttr() ) ) ); + } + } + + if ( GetUpper()->IsSctFrame() && + HasFollow() && !GetFollow()->IsDeleteForbidden() && + &GetFollow()->GetFrame() == GetNext() ) + { + static_cast<SwTextFrame&>(*this).JoinFrame(); + } + + // #i28701# - move master forward, if it has to move, + // because of its object positioning. + if ( !static_cast<SwTextFrame*>(this)->IsFollow() ) + { + sal_uInt32 nToPageNum = 0; + const bool bMoveFwdByObjPos = SwLayouter::FrameMovedFwdByObjPos( + *(GetAttrSet()->GetDoc()), + *static_cast<SwTextFrame*>(this), + nToPageNum ); + // #i58182# + // Also move a paragraph forward, which is the first one inside a table cell. + if ( bMoveFwdByObjPos && + FindPageFrame()->GetPhyPageNum() < nToPageNum && + ( lcl_Prev( this ) || + GetUpper()->IsCellFrame() || + ( GetUpper()->IsSctFrame() && + GetUpper()->GetUpper()->IsCellFrame() ) ) && + IsMoveable() ) + { + bMovedFwd = true; + MoveFwd( bMakePage, false ); + } + } + + // If a Follow sits next to its Master and doesn't fit, we know it can + // be moved right now. + if ( lcl_Prev( this ) && static_cast<SwTextFrame*>(this)->IsFollow() && IsMoveable() ) + { + bMovedFwd = true; + // If follow frame is in table, its master will be the last in the + // current table cell. Thus, invalidate the printing area of the master. + if ( IsInTab() ) + { + lcl_Prev( this )->InvalidatePrt(); + } + MoveFwd( bMakePage, false ); + } + + // Check footnote content for forward move. + // If a content of a footnote is on a prior page/column as its invalid + // reference, it can be moved forward. + if ( bFootnote && !isFrameAreaPositionValid() ) + { + SwFootnoteFrame* pFootnote = FindFootnoteFrame(); + SwContentFrame* pRefCnt = pFootnote ? pFootnote->GetRef() : nullptr; + + if ( pRefCnt && !pRefCnt->isFrameAreaDefinitionValid() ) + { + SwFootnoteBossFrame* pFootnoteBossOfFootnote = pFootnote->FindFootnoteBossFrame(); + SwFootnoteBossFrame* pFootnoteBossOfRef = pRefCnt->FindFootnoteBossFrame(); + //<loop of movefwd until condition held or no move> + if ( pFootnoteBossOfFootnote && pFootnoteBossOfRef && + pFootnoteBossOfFootnote != pFootnoteBossOfRef && + pFootnoteBossOfFootnote->IsBefore( pFootnoteBossOfRef ) ) + { + bMovedFwd = true; + MoveFwd( bMakePage, false ); + } + } + } + + SwRectFnSet aRectFnSet(this); + + SwFrame const* pMoveBwdPre(nullptr); + bool isMoveBwdPreValid(false); + + SwRect aOldFrame_StopFormat, aOldFrame_StopFormat2; + SwRect aOldPrt_StopFormat, aOldPrt_StopFormat2; + + while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + // - loop prevention + aOldFrame_StopFormat2 = aOldFrame_StopFormat; + aOldPrt_StopFormat2 = aOldPrt_StopFormat; + aOldFrame_StopFormat = getFrameArea(); + aOldPrt_StopFormat = getFramePrintArea(); + + bool bMoveable = IsMoveable(); + if (bMoveable) + { + SwFrame *pPre = GetIndPrev(); + if ( CheckMoveFwd( bMakePage, bKeep, bMovedBwd ) ) + { + aRectFnSet.Refresh(this); + bMovedFwd = true; + if ( bMovedBwd ) + { + // While flowing back, the Upper was encouraged to + // completely re-paint itself. We can skip this now after + // flowing back and forth. + GetUpper()->ResetCompletePaint(); + // The predecessor was invalidated, so this is obsolete as well now. + assert(pPre); + if ((pPre == pMoveBwdPre && isMoveBwdPreValid) && !pPre->IsSctFrame()) + ::ValidateSz( pPre ); + } + bMoveable = IsMoveable(); + } + } + + aOldFramePos = aRectFnSet.GetPos(getFrameArea()); + aOldPrtPos = aRectFnSet.GetPos(getFramePrintArea()); + + if ( !isFrameAreaPositionValid() ) + MakePos(); + + //Set FixSize. VarSize is being adjusted by Format(). + if ( !isFrameAreaSizeValid() ) + { + // invalidate printing area flag, if the following conditions are hold: + // - current frame width is 0. + // - current printing area width is 0. + // - frame width is adjusted to a value greater than 0. + // - printing area flag is true. + // Thus, it's assured that the printing area is adjusted, if the + // frame area width changes its width from 0 to something greater + // than 0. + // Note: A text frame can be in such a situation, if the format is + // triggered by method call <SwCursorShell::SetCursor()> after + // loading the document. + const SwTwips nNewFrameWidth = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()); + + if ( isFramePrintAreaValid() && + nNewFrameWidth > 0 && + aRectFnSet.GetWidth(getFrameArea()) == 0 && + aRectFnSet.GetWidth(getFramePrintArea()) == 0 ) + { + setFramePrintAreaValid(false); + } + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetWidth( aFrm, nNewFrameWidth ); + } + + // When a lower of a vertically aligned fly frame changes its size we need to recalculate content pos. + if( GetUpper() && GetUpper()->IsFlyFrame() && + GetUpper()->GetFormat()->GetTextVertAdjust().GetValue() != SDRTEXTVERTADJUST_TOP ) + { + static_cast<SwFlyFrame*>(GetUpper())->InvalidateContentPos(); + GetUpper()->SetCompletePaint(); + } + } + if ( !isFramePrintAreaValid() ) + { + const tools::Long nOldW = aRectFnSet.GetWidth(getFramePrintArea()); + // #i34730# - keep current frame height + const SwTwips nOldH = aRectFnSet.GetHeight(getFrameArea()); + MakePrtArea( rAttrs ); + if ( nOldW != aRectFnSet.GetWidth(getFramePrintArea()) ) + Prepare( PrepareHint::FixSizeChanged ); + // #i34730# - check, if frame height has changed. + // If yes, send a PrepareHint::AdjustSizeWithoutFormatting and invalidate the size flag to + // force a format. The format will check in its method + // <SwTextFrame::CalcPreps()>, if the already formatted lines still + // fit and if not, performs necessary actions. + // #i40150# - no check, if frame is undersized. + if ( isFrameAreaSizeValid() && !IsUndersized() && nOldH != aRectFnSet.GetHeight(getFrameArea()) ) + { + // #115759# - no PrepareHint::AdjustSizeWithoutFormatting and size + // invalidation, if height decreases only by the additional + // lower space as last content of a table cell and an existing + // follow containing one line exists. + const SwTwips nHDiff = nOldH - aRectFnSet.GetHeight(getFrameArea()); + const bool bNoPrepAdjustFrame = + nHDiff > 0 && IsInTab() && GetFollow() && + (1 == static_cast<SwTextFrame*>(GetFollow())->GetLineCount(TextFrameIndex(COMPLETE_STRING)) + || aRectFnSet.GetWidth(static_cast<SwTextFrame*>(GetFollow())->getFrameArea()) < 0) && + GetFollow()->CalcAddLowerSpaceAsLastInTableCell() == nHDiff; + if ( !bNoPrepAdjustFrame ) + { + Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + setFrameAreaSizeValid(false); + } + } + } + + // To make the widow and orphan rules work, we need to notify the ContentFrame. + // Criteria: + // - It needs to be movable (otherwise, splitting doesn't make sense) + // - It needs to overlap with the lower edge of the PrtArea of the Upper + if ( !bMustFit ) + { + bool bWidow = true; + const SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*GetUpper()); + if( bMoveable && !bFormatted && + ( GetFollow() || aRectFnSet.OverStep( getFrameArea(), nDeadLine ) ) ) + { + Prepare( PrepareHint::WidowsOrphans, nullptr, false ); + setFrameAreaSizeValid(false); + bWidow = false; + } + if( aRectFnSet.GetPos(getFrameArea()) != aOldFramePos || + aRectFnSet.GetPos(getFramePrintArea()) != aOldPrtPos ) + { + // In this Prepare, an InvalidateSize_() might happen. + // isFrameAreaSizeValid() becomes false and Format() gets called. + Prepare( PrepareHint::FramePositionChanged, static_cast<const void*>(&bFormatted), false ); + if ( bWidow && GetFollow() ) + { + Prepare( PrepareHint::WidowsOrphans, nullptr, false ); + setFrameAreaSizeValid(false); + } + } + } + if ( !isFrameAreaSizeValid() ) + { + setFrameAreaSizeValid(true); + bFormatted = true; + ++nFormatCount; + if( nFormatCount > STOP_FLY_FORMAT ) + SetFlyLock( true ); + // - loop prevention + // No format any longer, if <cnStopFormat> consecutive formats + // without change occur. + if ( nConsecutiveFormatsWithoutChange <= cnStopFormat ) + { + Format(getRootFrame()->GetCurrShell()->GetOut()); + } +#if OSL_DEBUG_LEVEL > 0 + else + { + OSL_FAIL( "debug assertion: <SwContentFrame::MakeAll()> - format of text frame suppressed by fix b6448963" ); + } +#endif + } + + // If this is the first one in a chain, check if this can flow + // backwards (if this is movable at all). + // To prevent oscillations/loops, check that this has not just + // flowed forwards. + bool bDummy; + auto const pTemp(GetIndPrev()); + auto const bTemp(pTemp && pTemp->isFrameAreaSizeValid() + && pTemp->isFramePrintAreaValid()); + if ( !lcl_Prev( this ) && + !bMovedFwd && + ( bMoveable || ( bFly && !bTab ) ) && + ( !bFootnote || !GetUpper()->FindFootnoteFrame()->GetPrev() ) + && MoveBwd( bDummy ) ) + { + aRectFnSet.Refresh(this); + pMoveBwdPre = pTemp; + isMoveBwdPreValid = bTemp; + bMovedBwd = true; + bFormatted = false; + if ( bKeep && bMoveable ) + { + if( CheckMoveFwd( bMakePage, false, bMovedBwd ) ) + { + bMovedFwd = true; + bMoveable = IsMoveable(); + aRectFnSet.Refresh(this); + } + Point aOldPos = aRectFnSet.GetPos(getFrameArea()); + MakePos(); + if( aOldPos != aRectFnSet.GetPos(getFrameArea()) ) + { + Prepare( PrepareHint::FramePositionChanged, static_cast<const void*>(&bFormatted), false ); + if ( !isFrameAreaSizeValid() ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetWidth( aFrm, aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()) ); + } + + if ( !isFramePrintAreaValid() ) + { + const tools::Long nOldW = aRectFnSet.GetWidth(getFramePrintArea()); + MakePrtArea( rAttrs ); + if( nOldW != aRectFnSet.GetWidth(getFramePrintArea()) ) + Prepare( PrepareHint::FixSizeChanged, nullptr, false ); + } + if( GetFollow() ) + { + Prepare( PrepareHint::WidowsOrphans, nullptr, false ); + } + + setFrameAreaSizeValid(true); + bFormatted = true; + Format(getRootFrame()->GetCurrShell()->GetOut()); + } + } + SwFrame *pNxt = HasFollow() ? nullptr : FindNext(); + while( pNxt && pNxt->IsSctFrame() ) + { // Leave empty sections out, go into the other ones. + if( static_cast<SwSectionFrame*>(pNxt)->GetSection() ) + { + SwFrame* pTmp = static_cast<SwSectionFrame*>(pNxt)->ContainsAny(); + if( pTmp ) + { + pNxt = pTmp; + break; + } + } + pNxt = pNxt->FindNext(); + } + if ( pNxt ) + { + pNxt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + if( isFrameAreaPositionValid() && !GetIndNext() ) + { + SwSectionFrame *pSct = FindSctFrame(); + if( pSct && !pSct->isFrameAreaSizeValid() ) + { + SwSectionFrame* pNxtSct = pNxt->FindSctFrame(); + if( pNxtSct && pSct->IsAnFollow( pNxtSct ) ) + { + setFrameAreaPositionValid(false); + } + } + else + { + setFrameAreaPositionValid(false); + } + } + } + } + } + + // In footnotes, the TextFrame may validate itself, which can lead to the + // situation that it's position is wrong despite being "valid". + if ( isFrameAreaPositionValid() ) + { + // #i59341# + // Workaround for inadequate layout algorithm: + // suppress invalidation and calculation of position, if paragraph + // has formatted itself at least STOP_FLY_FORMAT times and + // has anchored objects. + // Thus, the anchored objects get the possibility to format itself + // and this probably solve the layout loop. + if ( bFootnote && + nFormatCount <= STOP_FLY_FORMAT && + !GetDrawObjs() ) + { + setFrameAreaPositionValid(false); + MakePos(); + aOldFramePos = aRectFnSet.GetPos(getFrameArea()); + aOldPrtPos = aRectFnSet.GetPos(getFramePrintArea()); + } + } + + // - loop prevention + { + if ( (aOldFrame_StopFormat == getFrameArea() || aOldFrame_StopFormat2 == getFrameArea() ) && + (aOldPrt_StopFormat == getFramePrintArea() || aOldPrt_StopFormat2 == getFramePrintArea())) + { + ++nConsecutiveFormatsWithoutChange; + } + else + { + nConsecutiveFormatsWithoutChange = 0; + } + } + + // Yet again an invalid value? Repeat from the start... + if ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + continue; + + // Done? + // Attention: because height == 0, it's better to use Top()+Height() instead of + // Bottom(). This might happen with undersized TextFrames on the lower edge of a + // multi-column section + const tools::Long nPrtBottom = aRectFnSet.GetPrtBottom(*GetUpper()); + tools::Long nBottomDist = aRectFnSet.BottomDist(getFrameArea(), nPrtBottom); + + // Hide whitespace may require not to insert a new page. + SwPageFrame* pPageFrame = FindPageFrame(); + const bool bHeightValid = pPageFrame->CheckPageHeightValidForHideWhitespace(nBottomDist); + if (!bHeightValid) + { + pPageFrame->InvalidateSize(); + nBottomDist = 0; + } + + if( nBottomDist >= 0 ) + { + if ( bKeep && bMoveable ) + { + // We make sure the successor will be formatted the same. + // This way, we keep control until (almost) everything is stable, + // allowing us to avoid endless loops caused by ever repeating + // retries. + + // bMoveFwdInvalid is required for #38407#. This was originally solved + // in flowfrm.cxx rev 1.38, but broke the above schema and + // preferred to play towers of hanoi (#43669#). + SwFrame *pNxt = HasFollow() ? nullptr : FindNext(); + // For sections we prefer the content, because it can change + // the page if required. + while( pNxt && pNxt->IsSctFrame() ) + { + if( static_cast<SwSectionFrame*>(pNxt)->GetSection() ) + { + pNxt = static_cast<SwSectionFrame*>(pNxt)->ContainsAny(); + break; + } + pNxt = pNxt->FindNext(); + } + if ( pNxt ) + { + const bool bMoveFwdInvalid = nullptr != GetIndNext(); + const bool bNxtNew = + ( 0 == aRectFnSet.GetHeight(pNxt->getFramePrintArea()) ) && + (!pNxt->IsTextFrame() ||!static_cast<SwTextFrame*>(pNxt)->IsHiddenNow()); + + pNxt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + + if ( !bMovedBwd && + ((bMoveFwdInvalid && !GetIndNext()) || + bNxtNew) ) + { + if( bMovedFwd ) + oNotify->SetInvaKeep(); + bMovedFwd = false; + } + } + } + continue; + } + + // I don't fit into my parents, so it's time to make changes + // as constructively as possible. + + //If I'm NOT allowed to leave the parent Frame, I've got a problem. + // Following Arthur Dent, we do the only thing that you can do with + // an unsolvable problem: We ignore it with all our power. + if ( !bMoveable || IsUndersized() ) + { + break; + } + + // If there's no way I can make myself fit into my Upper, the situation + // could still probably be mitigated by splitting up. + // This situation arises with freshly created Follows that had been moved + // to the next page but is still too big for it - ie. needs to be split + // as well. + + // If I'm unable to split (WouldFit()) and can't be fitted, I'm going + // to tell my TextFrame part that, if possible, we still need to split despite + // the "don't split" attribute. + bool bMoveOrFit = false; + bool bDontMoveMe = !GetIndPrev(); + if( bDontMoveMe && IsInSct() ) + { + SwFootnoteBossFrame* pBoss = FindFootnoteBossFrame(); + bDontMoveMe = !pBoss->IsInSct() || + ( !pBoss->Lower()->GetNext() && !pBoss->GetPrev() ); + } + + // Finally, we are able to split table rows. Therefore, bDontMoveMe + // can be set to false: + if( bDontMoveMe && IsInTab() && + nullptr != GetNextCellLeaf() ) + bDontMoveMe = false; + + assert(bMoveable); + + if ( bDontMoveMe && aRectFnSet.GetHeight(getFrameArea()) > + aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()) ) + { + if ( !bFitPromise ) + { + SwTwips nTmp = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()) - + aRectFnSet.GetTop(getFramePrintArea()); + bool bSplit = !IsFwdMoveAllowed(); + if (nTmp > 0 && WouldFit(nTmp, bSplit, false, false)) + { + Prepare( PrepareHint::WidowsOrphans, nullptr, false ); + setFrameAreaSizeValid(false); + bFitPromise = true; + continue; + } + /* + * In earlier days, we never tried to fit TextFrames in + * frames and sections using bMoveOrFit by ignoring + * its attributes (Widows, Keep). + * This should have been done at least for column frames; + * as it must be tried anyway with linked frames and sections. + * Exception: If we sit in FormatWidthCols, we must not ignore + * the attributes. + */ + else if ( !bFootnote && + ( !bFly || !FindFlyFrame()->IsColLocked() ) && + ( !bSct || !FindSctFrame()->IsColLocked() ) ) + bMoveOrFit = true; + } +#if OSL_DEBUG_LEVEL > 0 + else + { + OSL_FAIL( "+TextFrame didn't respect WouldFit promise." ); + } +#endif + } + + // Let's see if I can find some space somewhere... + // footnotes in the neighbourhood are moved into _MoveFootnoteCntFwd + SwFrame *pPre = GetIndPrev(); + SwFrame *pOldUp = GetUpper(); + +/* MA 13. Oct. 98: What is this supposed to be!? + * AMA 14. Dec 98: If a column section can't find any space for its first ContentFrame, it should be + * moved not only to the next column, but probably even to the next page, creating + * a section-follow there. + */ + if( IsInSct() && bMovedFwd && bMakePage && pOldUp->IsColBodyFrame() && + pOldUp->GetUpper()->GetUpper()->IsSctFrame() && + ( pPre || pOldUp->GetUpper()->GetPrev() ) && + static_cast<SwSectionFrame*>(pOldUp->GetUpper()->GetUpper())->MoveAllowed(this) ) + { + bMovedFwd = false; + } + + const bool bCheckForGrownBody = pOldUp->IsBodyFrame(); + const tools::Long nOldBodyHeight = aRectFnSet.GetHeight(pOldUp->getFrameArea()); + + if ( !bMovedFwd && !MoveFwd( bMakePage, false ) ) + bMakePage = false; + aRectFnSet.Refresh(this); + if (!bMovedFwd && bFootnote && GetIndPrev() != pPre) + { // SwFlowFrame::CutTree() could have formatted and deleted pPre + auto const pPrevFootnoteFrame(static_cast<SwFootnoteFrame const*>( + FindFootnoteFrame())->GetMaster()); + bool bReset = true; + if (pPrevFootnoteFrame) + { // use GetIndNext() in case there are sections + for (auto p = pPrevFootnoteFrame->Lower(); p; p = p->GetIndNext()) + { + if (p == pPre) + { + bReset = false; + break; + } + } + } + if (bReset) + { + pPre = nullptr; + } + } + + // If MoveFwd moves the paragraph to the next page, a following + // paragraph, which contains footnotes can cause the old upper + // frame to grow. In this case we explicitly allow a new check + // for MoveBwd. Robust: We also check the bMovedBwd flag again. + // If pOldUp was a footnote frame, it has been deleted inside MoveFwd. + // Therefore we only check for growing body frames. + bMovedFwd = !bCheckForGrownBody || bMovedBwd || pOldUp == GetUpper() || + aRectFnSet.GetHeight(pOldUp->getFrameArea()) <= nOldBodyHeight; + + bFormatted = false; + if ( bMoveOrFit && GetUpper() == pOldUp ) + { + // FME 2007-08-30 #i81146# new loop control + if ( nConsecutiveFormatsWithoutChange <= cnStopFormat ) + { + Prepare( PrepareHint::MustFit, nullptr, false ); + setFrameAreaSizeValid(false); + bMustFit = true; + continue; + } + +#if OSL_DEBUG_LEVEL > 0 + OSL_FAIL( "LoopControl in SwContentFrame::MakeAll" ); +#endif + } + if ( bMovedBwd && GetUpper() ) + { // Retire invalidations that have become useless. + GetUpper()->ResetCompletePaint(); + if( pPre && !pPre->IsSctFrame() ) + ::ValidateSz( pPre ); + } + + } //while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + + // NEW: Looping Louie (Light). Should not be applied in balanced sections. + // Should only be applied if there is no better solution! + LOOPING_LOUIE_LIGHT( bMovedFwd && bMovedBwd && !IsInBalancedSection() && + ( + + ( bFootnote && !FindFootnoteFrame()->GetRef()->IsInSct() ) || + + // #i33887# + ( IsInSct() && bKeep ) + + // ... add your conditions here ... + + ), + static_cast<SwTextFrame&>(*this) ); + + pSaveFootnote.reset(); + + UnlockJoin(); + oDeleteGuard.reset(); + if ( bMovedFwd || bMovedBwd ) + oNotify->SetInvaKeep(); + if ( bMovedFwd ) + { + oNotify->SetInvalidatePrevPrtArea(); + } + oNotify.reset(); + SetFlyLock( false ); +} + +void MakeNxt( SwFrame *pFrame, SwFrame *pNxt ) +{ + // fix(25455): Validate, otherwise this leads to a recursion. + // The first try, cancelling with pFrame = 0 if !Valid, leads to a problem, as + // the Keep may not be considered properly anymore (27417). + const bool bOldPos = pFrame->isFrameAreaPositionValid(); + const bool bOldSz = pFrame->isFrameAreaSizeValid(); + const bool bOldPrt = pFrame->isFramePrintAreaValid(); + pFrame->setFrameAreaPositionValid(true); + pFrame->setFrameAreaSizeValid(true); + pFrame->setFramePrintAreaValid(true); + + // fix(29272): Don't call MakeAll - there, pFrame might be invalidated again, and + // we recursively end up in here again. + if ( pNxt->IsContentFrame() ) + { + SwContentNotify aNotify( static_cast<SwContentFrame*>(pNxt) ); + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pNxt ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + if ( !pNxt->isFrameAreaSizeValid() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pNxt); + + if( pNxt->IsVertical() ) + { + aFrm.Height( pNxt->GetUpper()->getFramePrintArea().Height() ); + } + else + { + aFrm.Width( pNxt->GetUpper()->getFramePrintArea().Width() ); + } + } + static_cast<SwContentFrame*>(pNxt)->MakePrtArea( rAttrs ); + pNxt->Format( pNxt->getRootFrame()->GetCurrShell()->GetOut(), &rAttrs ); + } + else + { + SwLayNotify aNotify( static_cast<SwLayoutFrame*>(pNxt) ); + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pNxt ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + if ( !pNxt->isFrameAreaSizeValid() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pNxt); + + if( pNxt->IsVertical() ) + { + aFrm.Height( pNxt->GetUpper()->getFramePrintArea().Height() ); + } + else + { + aFrm.Width( pNxt->GetUpper()->getFramePrintArea().Width() ); + } + } + pNxt->Format( pNxt->getRootFrame()->GetCurrShell()->GetOut(), &rAttrs ); + } + + pFrame->setFrameAreaPositionValid(bOldPos); + pFrame->setFrameAreaSizeValid(bOldSz); + pFrame->setFramePrintAreaValid(bOldPrt); +} + +/// This routine checks whether there are no other FootnoteBosses +/// between the pFrame's FootnoteBoss and the pNxt's FootnoteBoss. +static bool lcl_IsNextFootnoteBoss( const SwFrame *pFrame, const SwFrame* pNxt ) +{ + assert(pFrame && pNxt && "lcl_IsNextFootnoteBoss: No Frames?"); + pFrame = pFrame->FindFootnoteBossFrame(); + pNxt = pNxt->FindFootnoteBossFrame(); + // If pFrame is a last column, we use the page instead. + while( pFrame && pFrame->IsColumnFrame() && !pFrame->GetNext() ) + pFrame = pFrame->GetUpper()->FindFootnoteBossFrame(); + // If pNxt is a first column, we use the page instead. + while( pNxt && pNxt->IsColumnFrame() && !pNxt->GetPrev() ) + pNxt = pNxt->GetUpper()->FindFootnoteBossFrame(); + // So... now pFrame and pNxt are either two adjacent pages or columns. + return pFrame && pNxt && pFrame->GetNext() == pNxt; +} + +bool SwContentFrame::WouldFit_( SwTwips nSpace, + SwLayoutFrame *pNewUpper, + bool bTstMove, + const bool bObjsInNewUpper ) +{ + // To have the footnote select its place carefully, it needs + // to be moved in any case if there is at least one page/column + // between the footnote and the new Upper. + SwFootnoteFrame* pFootnoteFrame = nullptr; + if ( IsInFootnote() ) + { + if( !lcl_IsNextFootnoteBoss( pNewUpper, this ) ) + return true; + pFootnoteFrame = FindFootnoteFrame(); + } + + bool bRet; + bool bSplit = !pNewUpper->Lower(); + SwContentFrame *pFrame = this; + const SwFrame *pTmpPrev = pNewUpper->Lower(); + if( pTmpPrev && pTmpPrev->IsFootnoteFrame() ) + pTmpPrev = static_cast<const SwFootnoteFrame*>(pTmpPrev)->Lower(); + while ( pTmpPrev && pTmpPrev->GetNext() ) + pTmpPrev = pTmpPrev->GetNext(); + do + { + // #i46181# + SwTwips nSecondCheck = 0; + SwTwips nOldSpace = nSpace; + bool bOldSplit = bSplit; + + if ( bTstMove || IsInFly() || ( IsInSct() && + ( pFrame->GetUpper()->IsColBodyFrame() || ( pFootnoteFrame && + pFootnoteFrame->GetUpper()->GetUpper()->IsColumnFrame() ) ) ) ) + { + // This is going to get a bit insidious now. If you're faint of heart, + // you'd better look away here. If a Fly contains columns, then the Contents + // are movable, except ones in the last column (see SwFrame::IsMoveable()). + // Of course they're allowed to float back. WouldFit() only returns a usable + // value if the Frame is movable. To fool WouldFit() into believing there's + // a movable Frame, I'm just going to hang it somewhere else for the time. + // The same procedure applies for column sections to make SwSectionFrame::Growable() + // return the proper value. + // Within footnotes, we may even need to put the SwFootnoteFrame somewhere else, if + // there's no SwFootnoteFrame there. + SwFrame* pTmpFrame = pFrame->IsInFootnote() && !pNewUpper->FindFootnoteFrame() ? + static_cast<SwFrame*>(pFrame->FindFootnoteFrame()) : pFrame; + SwLayoutFrame *pUp = pTmpFrame->GetUpper(); + SwFrame *pOldNext = pTmpFrame->GetNext(); + pTmpFrame->RemoveFromLayout(); + pTmpFrame->InsertBefore( pNewUpper, nullptr ); + // tdf#107126 for a section in a footnote, we have only inserted + // the SwTextFrame but no SwSectionFrame - reset mbInfSct flag + // to avoid crashing (but perhaps we should create a temp + // SwSectionFrame here because WidowsAndOrphans checks for that?) + pTmpFrame->InvalidateInfFlags(); + if ( pFrame->IsTextFrame() && + ( bTstMove || + static_cast<SwTextFrame*>(pFrame)->HasFollow() || + ( !static_cast<SwTextFrame*>(pFrame)->HasPara() && + !static_cast<SwTextFrame*>(pFrame)->IsEmpty() + ) + ) + ) + { + bTstMove = true; + bRet = static_cast<SwTextFrame*>(pFrame)->TestFormat( pTmpPrev, nSpace, bSplit ); + } + else + bRet = pFrame->WouldFit(nSpace, bSplit, false, true); + + pTmpFrame->RemoveFromLayout(); + pTmpFrame->InsertBefore( pUp, pOldNext ); + pTmpFrame->InvalidateInfFlags(); // restore flags + } + else + { + bRet = pFrame->WouldFit(nSpace, bSplit, false, true); + nSecondCheck = !bSplit ? 1 : 0; + } + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFrame ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + // Sad but true: We need to consider the spacing in our calculation. + // This already happened in TestFormat. + if ( bRet && !bTstMove ) + { + SwTwips nUpper; + + if ( pTmpPrev ) + { + nUpper = CalcUpperSpace( nullptr, pTmpPrev ); + + // in balanced columned section frames we do not want the + // common border + bool bCommonBorder = true; + if ( pFrame->IsInSct() && pFrame->GetUpper()->IsColBodyFrame() ) + { + const SwSectionFrame* pSct = pFrame->FindSctFrame(); + bCommonBorder = pSct->GetFormat()->GetBalancedColumns().GetValue(); + } + + // #i46181# + nSecondCheck = ( 1 == nSecondCheck && + pFrame == this && + IsTextFrame() && + bCommonBorder && + !static_cast<const SwTextFrame*>(this)->IsEmpty() ) ? + nUpper : + 0; + + nUpper += bCommonBorder ? + rAttrs.GetBottomLine( *pFrame ) : + rAttrs.CalcBottomLine(); + + } + else + { + // #i46181# + nSecondCheck = 0; + + if( pFrame->IsVertical() ) + nUpper = pFrame->getFrameArea().Width() - pFrame->getFramePrintArea().Width(); + else + nUpper = pFrame->getFrameArea().Height() - pFrame->getFramePrintArea().Height(); + } + + nSpace -= nUpper; + + if ( nSpace < 0 ) + { + bRet = false; + + // #i46181# + if ( nSecondCheck > 0 ) + { + // The following code is intended to solve a (rare) problem + // causing some frames not to move backward: + // SwTextFrame::WouldFit() claims that the whole paragraph + // fits into the given space and subtracts the height of + // all lines from nSpace. nSpace - nUpper is not a valid + // indicator if the frame should be allowed to move backward. + // We do a second check with the original remaining space + // reduced by the required upper space: + nOldSpace -= nSecondCheck; + const bool bSecondRet = nOldSpace >= 0 && pFrame->WouldFit(nOldSpace, bOldSplit, false, true); + if ( bSecondRet && bOldSplit && nOldSpace >= 0 ) + { + bRet = true; + bSplit = true; + } + } + } + } + + // Also consider lower spacing in table cells + IDocumentSettingAccess const& rIDSA(pNewUpper->GetFormat()->getIDocumentSettingAccess()); + if ( bRet && IsInTab() && + rIDSA.get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS)) + { + nSpace -= rAttrs.GetULSpace().GetLower(); + + if (rIDSA.get(DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS)) + { + nSpace -= rAttrs.CalcLineSpacing(); + } + if ( nSpace < 0 ) + { + bRet = false; + } + } + + if (bRet && !bSplit && pFrame->IsKeep(rAttrs.GetAttrSet().GetKeep(), GetBreakItem())) + { + if( bTstMove ) + { + while( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->HasFollow() ) + { + pFrame = static_cast<SwTextFrame*>(pFrame)->GetFollow(); + } + // If last follow frame of <this> text frame isn't valid, + // a formatting of the next content frame doesn't makes sense. + // Thus, return true. + if ( IsAnFollow( pFrame ) && !pFrame->isFrameAreaDefinitionValid() ) + { + OSL_FAIL( "Only a warning for task 108824:/n<SwContentFrame::WouldFit_(..) - follow not valid!" ); + return true; + } + } + SwFrame *pNxt; + if( nullptr != (pNxt = pFrame->FindNext()) && pNxt->IsContentFrame() && + ( !pFootnoteFrame || ( pNxt->IsInFootnote() && + pNxt->FindFootnoteFrame()->GetAttr() == pFootnoteFrame->GetAttr() ) ) ) + { + // TestFormat(?) does not like paragraph- or character anchored objects. + + // current solution for the test formatting doesn't work, if + // objects are present in the remaining area of the new upper + if ( bTstMove && + ( pNxt->GetDrawObjs() || bObjsInNewUpper ) ) + { + return true; + } + + if ( !pNxt->isFrameAreaDefinitionValid() ) + { + MakeNxt( pFrame, pNxt ); + } + + // Little trick: if the next has a predecessor, then the paragraph + // spacing has been calculated already, and we don't need to re-calculate + // it in an expensive way. + if( lcl_NotHiddenPrev( pNxt ) ) + pTmpPrev = nullptr; + else + { + if( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsHiddenNow() ) + pTmpPrev = lcl_NotHiddenPrev( pFrame ); + else + pTmpPrev = pFrame; + } + pFrame = static_cast<SwContentFrame*>(pNxt); + } + else + pFrame = nullptr; + } + else + pFrame = nullptr; + + } while ( bRet && pFrame ); + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/colfrm.cxx b/sw/source/core/layout/colfrm.cxx new file mode 100644 index 000000000..4b13576a7 --- /dev/null +++ b/sw/source/core/layout/colfrm.cxx @@ -0,0 +1,446 @@ +/* -*- 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 <editeng/ulspitem.hxx> +#include <osl/diagnose.h> +#include <fmtclds.hxx> +#include <fmtfordr.hxx> +#include <frmfmt.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <colfrm.hxx> +#include <pagefrm.hxx> +#include <bodyfrm.hxx> +#include <rootfrm.hxx> +#include <sectfrm.hxx> +#include <calbck.hxx> +#include <ftnfrm.hxx> +#include <IDocumentState.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentUndoRedo.hxx> + +SwColumnFrame::SwColumnFrame( SwFrameFormat *pFormat, SwFrame* pSib ): + SwFootnoteBossFrame( pFormat, pSib ) +{ + mnFrameType = SwFrameType::Column; + SwBodyFrame* pColBody = new SwBodyFrame( pFormat->GetDoc()->GetDfltFrameFormat(), pSib ); + pColBody->InsertBehind( this, nullptr ); // ColumnFrames now with BodyFrame + SetMaxFootnoteHeight( LONG_MAX ); +} + +void SwColumnFrame::DestroyImpl() +{ + SwFrameFormat *pFormat = GetFormat(); + SwDoc *pDoc; + if ( !(pDoc = pFormat->GetDoc())->IsInDtor() && pFormat->HasOnlyOneListener() ) + { + //I'm the only one, delete the format. + //Get default format before, so the base class can cope with it. + pDoc->GetDfltFrameFormat()->Add( this ); + // tdf#134009, like #i32968# avoid SwUndoFrameFormatDelete creation, + // the format is owned by the SwColumnFrame, see lcl_AddColumns() + ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo()); + pDoc->DelFrameFormat( pFormat ); + } + + SwFootnoteBossFrame::DestroyImpl(); +} + +SwColumnFrame::~SwColumnFrame() +{ +} + +static void lcl_RemoveColumns( SwLayoutFrame *pCont, sal_uInt16 nCnt ) +{ + OSL_ENSURE( pCont && pCont->Lower() && pCont->Lower()->IsColumnFrame(), + "no columns to remove." ); + + SwColumnFrame *pColumn = static_cast<SwColumnFrame*>(pCont->Lower()); + sw_RemoveFootnotes( pColumn, true, true ); + while ( pColumn->GetNext() ) + { + OSL_ENSURE( pColumn->GetNext()->IsColumnFrame(), + "neighbor of ColumnFrame is no ColumnFrame." ); + pColumn = static_cast<SwColumnFrame*>(pColumn->GetNext()); + } + for ( sal_uInt16 i = 0; i < nCnt; ++i ) + { + SwColumnFrame *pTmp = static_cast<SwColumnFrame*>(pColumn->GetPrev()); + pColumn->Cut(); + SwFrame::DestroyFrame(pColumn); //format is going to be destroyed in the DTor if needed. + pColumn = pTmp; + } +} + +static SwLayoutFrame * lcl_FindColumns( SwLayoutFrame *pLay, sal_uInt16 nCount ) +{ + SwFrame *pCol = pLay->Lower(); + if ( pLay->IsPageFrame() ) + pCol = static_cast<SwPageFrame*>(pLay)->FindBodyCont()->Lower(); + + if ( pCol && pCol->IsColumnFrame() ) + { + SwFrame *pTmp = pCol; + sal_uInt16 i; + for ( i = 0; pTmp; pTmp = pTmp->GetNext(), ++i ) + /* do nothing */; + return i == nCount ? static_cast<SwLayoutFrame*>(pCol) : nullptr; + } + return nullptr; +} + +static bool lcl_AddColumns( SwLayoutFrame *pCont, sal_uInt16 nCount ) +{ + SwDoc *pDoc = pCont->GetFormat()->GetDoc(); + const bool bMod = pDoc->getIDocumentState().IsModified(); + + //Formats should be shared whenever possible. If a neighbour already has + //the same column settings we can add them to the same format. + //The neighbour can be searched using the format, however the owner of the + //attribute depends on the frame type. + SwLayoutFrame *pAttrOwner = pCont; + if ( pCont->IsBodyFrame() ) + pAttrOwner = pCont->FindPageFrame(); + SwLayoutFrame *pNeighbourCol = nullptr; + SwIterator<SwLayoutFrame,SwFormat> aIter( *pAttrOwner->GetFormat() ); + SwLayoutFrame *pNeighbour = aIter.First(); + + sal_uInt16 nAdd = 0; + SwFrame *pCol = pCont->Lower(); + if ( pCol && pCol->IsColumnFrame() ) + for ( nAdd = 1; pCol; pCol = pCol->GetNext(), ++nAdd ) + /* do nothing */; + while ( pNeighbour ) + { + if ( nullptr != (pNeighbourCol = lcl_FindColumns( pNeighbour, nCount+nAdd )) && + pNeighbourCol != pCont ) + break; + pNeighbourCol = nullptr; + pNeighbour = aIter.Next(); + } + + bool bRet; + SwTwips nMax = pCont->IsPageBodyFrame() ? + pCont->FindPageFrame()->GetMaxFootnoteHeight() : LONG_MAX; + if ( pNeighbourCol ) + { + bRet = false; + SwFrame *pTmp = pCont->Lower(); + while ( pTmp ) + { + pTmp = pTmp->GetNext(); + pNeighbourCol = static_cast<SwLayoutFrame*>(pNeighbourCol->GetNext()); + } + for ( sal_uInt16 i = 0; i < nCount; ++i ) + { + SwColumnFrame *pTmpCol = new SwColumnFrame( pNeighbourCol->GetFormat(), pCont ); + pTmpCol->SetMaxFootnoteHeight( nMax ); + pTmpCol->InsertBefore( pCont, nullptr ); + pNeighbourCol = static_cast<SwLayoutFrame*>(pNeighbourCol->GetNext()); + } + } + else + { + bRet = true; + // tdf#103359, like #i32968# Inserting columns in the section causes MakeFrameFormat to put + // nCount objects of type SwUndoFrameFormat on the undo stack. We don't want them. + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + for ( sal_uInt16 i = 0; i < nCount; ++i ) + { + SwFrameFormat *pFormat = pDoc->MakeFrameFormat(OUString(), pDoc->GetDfltFrameFormat()); + SwColumnFrame *pTmp = new SwColumnFrame( pFormat, pCont ); + pTmp->SetMaxFootnoteHeight( nMax ); + pTmp->Paste( pCont ); + } + } + + if ( !bMod ) + pDoc->getIDocumentState().ResetModified(); + return bRet; +} + +/** add or remove columns from a layoutframe. + * + * Normally, a layoutframe with a column attribute of 1 or 0 columns contains + * no columnframe. However, a sectionframe with "footnotes at the end" needs + * a columnframe. + * + * @param rOld + * @param rNew + * @param bChgFootnote if true, the columnframe will be inserted or removed, if necessary. + */ +void SwLayoutFrame::ChgColumns( const SwFormatCol &rOld, const SwFormatCol &rNew, + const bool bChgFootnote ) +{ + if ( rOld.GetNumCols() <= 1 && rNew.GetNumCols() <= 1 && !bChgFootnote ) + return; + // #i97379# + // If current lower is a no text frame, then columns are not allowed + if ( Lower() && Lower()->IsNoTextFrame() && + rNew.GetNumCols() > 1 ) + { + return; + } + + sal_uInt16 nNewNum, nOldNum = 1; + if( Lower() && Lower()->IsColumnFrame() ) + { + SwFrame* pCol = Lower(); + while( nullptr != (pCol=pCol->GetNext()) ) + ++nOldNum; + } + nNewNum = rNew.GetNumCols(); + if( !nNewNum ) + ++nNewNum; + bool bAtEnd; + if( IsSctFrame() ) + bAtEnd = static_cast<SwSectionFrame*>(this)->IsAnyNoteAtEnd(); + else + bAtEnd = false; + + //Setting the column width is only needed for new formats. + bool bAdjustAttributes = nOldNum != rOld.GetNumCols(); + + //The content is saved and restored if the column count is different. + SwFrame *pSave = nullptr; + if( nOldNum != nNewNum || bChgFootnote ) + { + SwDoc *pDoc = GetFormat()->GetDoc(); + OSL_ENSURE( pDoc, "FrameFormat doesn't return a document." ); + // SaveContent would also suck up the content of the footnote container + // and store it within the normal text flow. + if( IsPageBodyFrame() ) + pDoc->getIDocumentLayoutAccess().GetCurrentLayout()->RemoveFootnotes( static_cast<SwPageFrame*>(GetUpper()) ); + pSave = ::SaveContent( this ); + + //If columns exist, they get deleted if a column count of 0 or 1 is requested. + if ( nNewNum == 1 && !bAtEnd ) + { + ::lcl_RemoveColumns( this, nOldNum ); + if ( IsBodyFrame() ) + SetFrameFormat( pDoc->GetDfltFrameFormat() ); + else + GetFormat()->SetFormatAttr( SwFormatFillOrder() ); + if ( pSave ) + ::RestoreContent( pSave, this, nullptr ); + return; + } + if ( nOldNum == 1 ) + { + if ( IsBodyFrame() ) + SetFrameFormat( pDoc->GetColumnContFormat() ); + else + GetFormat()->SetFormatAttr( SwFormatFillOrder( ATT_LEFT_TO_RIGHT ) ); + if( !Lower() || !Lower()->IsColumnFrame() ) + --nOldNum; + } + if ( nOldNum > nNewNum ) + { + ::lcl_RemoveColumns( this, nOldNum - nNewNum ); + bAdjustAttributes = true; + } + else if( nOldNum < nNewNum ) + { + sal_uInt16 nAdd = nNewNum - nOldNum; + bAdjustAttributes = lcl_AddColumns( this, nAdd ); + } + } + + if ( !bAdjustAttributes ) + { + if ( rOld.GetLineWidth() != rNew.GetLineWidth() || + rOld.GetWishWidth() != rNew.GetWishWidth() || + rOld.IsOrtho() != rNew.IsOrtho() ) + bAdjustAttributes = true; + else + { + const size_t nCount = std::min( rNew.GetColumns().size(), rOld.GetColumns().size() ); + for ( size_t i = 0; i < nCount; ++i ) + if ( !(rOld.GetColumns()[i] == rNew.GetColumns()[i]) ) + { + bAdjustAttributes = true; + break; + } + } + } + + //The columns can now be easily adjusted. + AdjustColumns( &rNew, bAdjustAttributes ); + + //Don't restore the content before. An earlier restore would trigger useless + //actions during setup. + if ( pSave ) + { + OSL_ENSURE( Lower() && Lower()->IsLayoutFrame() && + static_cast<SwLayoutFrame*>(Lower())->Lower() && + static_cast<SwLayoutFrame*>(Lower())->Lower()->IsLayoutFrame(), + "no column body." ); // ColumnFrames contain BodyFrames + ::RestoreContent( pSave, static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(Lower())->Lower()), nullptr ); + } +} + +void SwLayoutFrame::AdjustColumns( const SwFormatCol *pAttr, bool bAdjustAttributes ) +{ + if( !Lower()->GetNext() ) + { + Lower()->ChgSize( getFramePrintArea().SSize() ); + return; + } + + const bool bVert = IsVertical(); + + SwRectFn fnRect = bVert ? ( IsVertLR() ? (IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ) : fnRectHori; + + //If we have a pointer or we have to configure an attribute, we set the + //column widths in any case. Otherwise we check if a configuration is needed. + if ( !pAttr ) + { + pAttr = &GetFormat()->GetCol(); + if ( !bAdjustAttributes ) + { + tools::Long nAvail = (getFramePrintArea().*fnRect->fnGetWidth)(); + for ( SwLayoutFrame *pCol = static_cast<SwLayoutFrame*>(Lower()); + pCol; + pCol = static_cast<SwLayoutFrame*>(pCol->GetNext()) ) + nAvail -= (pCol->getFrameArea().*fnRect->fnGetWidth)(); + if ( !nAvail ) + return; + } + } + + //The columns can now be easily adjusted. + //The widths get counted so we can give the reminder to the last one. + SwTwips nAvail = (getFramePrintArea().*fnRect->fnGetWidth)(); + const bool bLine = pAttr->GetLineAdj() != COLADJ_NONE; + const sal_uInt16 nMin = bLine ? sal_uInt16( 20 + ( pAttr->GetLineWidth() / 2) ) : 0; + + const bool bR2L = IsRightToLeft(); + SwFrame *pCol = bR2L ? GetLastLower() : Lower(); + + // #i27399# + // bOrtho means we have to adjust the column frames manually. Otherwise + // we may use the values returned by CalcColWidth: + const bool bOrtho = pAttr->IsOrtho() && pAttr->GetNumCols() > 0; + tools::Long nGutter = 0; + + for ( sal_uInt16 i = 0; i < pAttr->GetNumCols() && pCol; ++i ) //i118878, value returned by GetNumCols() can't be trusted + { + if( !bOrtho ) + { + const SwTwips nWidth = i == (pAttr->GetNumCols() - 1) ? + nAvail : + pAttr->CalcColWidth( i, sal_uInt16( (getFramePrintArea().*fnRect->fnGetWidth)() ) ); + + const Size aColSz = bVert ? + Size( getFramePrintArea().Width(), nWidth ) : + Size( nWidth, getFramePrintArea().Height() ); + + pCol->ChgSize( aColSz ); + + // With this, the ColumnBodyFrames from page columns gets adjusted and + // their bFixHeight flag is set so they won't shrink/grow. + // Don't use the flag with frame columns because BodyFrames in frame + // columns can grow/shrink. + if( IsBodyFrame() ) + static_cast<SwLayoutFrame*>(pCol)->Lower()->ChgSize( aColSz ); + + nAvail -= nWidth; + } + + if ( bOrtho || bAdjustAttributes ) + { + const SwColumn *pC = &pAttr->GetColumns()[i]; + const SwAttrSet* pSet = pCol->GetAttrSet(); + SvxLRSpaceItem aLR( pSet->GetLRSpace() ); + + //In order to have enough space for the separation lines, we have to + //take them into account here. Every time two columns meet we + //calculate a clearance of 20 + half the pen width on the left or + //right side, respectively. + const sal_uInt16 nLeft = pC->GetLeft(); + const sal_uInt16 nRight = pC->GetRight(); + + aLR.SetLeft ( nLeft ); + aLR.SetRight( nRight ); + + if ( bLine ) + { + if ( i == 0 ) + { + aLR.SetRight( std::max( nRight, nMin ) ); + } + else if ( i == pAttr->GetNumCols() - 1 ) + { + aLR.SetLeft ( std::max( nLeft, nMin ) ); + } + else + { + aLR.SetLeft ( std::max( nLeft, nMin ) ); + aLR.SetRight( std::max( nRight, nMin ) ); + } + } + + if ( bAdjustAttributes ) + { + SvxULSpaceItem aUL( pSet->GetULSpace() ); + aUL.SetUpper(0); + aUL.SetLower(0); + + static_cast<SwLayoutFrame*>(pCol)->GetFormat()->SetFormatAttr( aLR ); + static_cast<SwLayoutFrame*>(pCol)->GetFormat()->SetFormatAttr( aUL ); + } + + nGutter += aLR.GetLeft() + aLR.GetRight(); + } + + pCol = bR2L ? pCol->GetPrev() : pCol->GetNext(); + } + + if( !bOrtho ) + return; + + tools::Long nInnerWidth = ( nAvail - nGutter ) / pAttr->GetNumCols(); + pCol = Lower(); + for( sal_uInt16 i = 0; i < pAttr->GetNumCols() && pCol; pCol = pCol->GetNext(), ++i ) //i118878, value returned by GetNumCols() can't be trusted + { + SwTwips nWidth; + if ( i == pAttr->GetNumCols() - 1 ) + nWidth = nAvail; + else + { + SvxLRSpaceItem aLR( pCol->GetAttrSet()->GetLRSpace() ); + nWidth = nInnerWidth + aLR.GetLeft() + aLR.GetRight(); + } + if( nWidth < 0 ) + nWidth = 0; + + const Size aColSz = bVert ? + Size( getFramePrintArea().Width(), nWidth ) : + Size( nWidth, getFramePrintArea().Height() ); + + pCol->ChgSize( aColSz ); + + if( IsBodyFrame() ) + static_cast<SwLayoutFrame*>(pCol)->Lower()->ChgSize( aColSz ); + + nAvail -= nWidth; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/dbg_lay.cxx b/sw/source/core/layout/dbg_lay.cxx new file mode 100644 index 000000000..d1d012c64 --- /dev/null +++ b/sw/source/core/layout/dbg_lay.cxx @@ -0,0 +1,953 @@ +/* -*- 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 . + */ + +#ifdef DBG_UTIL + +/* + * And here's the description: + * + * The PROTOCOL macros allow you to log events in frame methods. In places where + * logging is useful either one of the PROTOCOL(...) or PROTOCOL_ENTER(...) can + * be used. PROTOCOL_ENTER(...) additionally logs the leaving of a method. + * + * The PROTOCOL macros accept the following parameters: + * 1. A pointer to an SwFrame (usually "this" or "rThis") + * 2. The function group i.e. PROT::MakeAll. This is used to decide (inline) + * whether this event shall be logged at the current time. + * 3. The action, usually 0. For example DbgAction::Start indents output in the log + * file and DbgAction::End stops the indentation. This allows for example + * PROTOCOL_ENTER to indent at the beginning of a method and stop indenting + * when leaving the method. + * 4. The fourth parameter is a void pointer which allows to pass anything + * which can be used in the log. A good example is PROT::Grow: this requires + * a pointer to the value which defines how much to grow. + * + * The log file is called "dbg_lay.out", which is saved in the current (BIN-) + * directory. The file contains lines with FrameId, function group and additional + * information. + * + * What exactly is going to be logged, can be defined as follows: + * 1. The static variable SwProtocol::nRecord contains the function groups + * which shall be logged. + * A value of i.e. PROT::Grow causes calls to SwFrame::Grow to be + * logged; PROT::MakeAll logs the calls to xxx::MakeAll. + * The PROT_XY values can be combined using binary OR, the default value + * is null - no method calls are logged. + * 2. The SwImplProtocol class contains a filter for frame types, only method + * call of frame types which are defined there are logged. + * The member nTypes can be set to values like SwFrameType::Page or SwFrameType::Section and + * may be combined using binary OR. The default values is 0xFFFF - meaning + * all frame types. + * 3. The SwImplProtocol class contains an ArrayPointer to FrameIds which need to be + * tracked. If the pointer is null, all frames will be logged; otherwise + * only frames of linked from the array will be logged. + * + * Code changes are needed to start logging; either change the default of nRecord + * in SwProtocol::Init() or change the debugger. There are several possible + * places in the debugger: + * 1. Set a breakpoint in SwProtocol::Init() and manipulate nRecord there, set + * FrameIds accordingly then start logging during program start. + * 2. Set a breakpoint before any PROTOCOL or PROTOCOL_ENTER macro during + * program execution, then set the lowest bit (PROT::Init) of + * SwProtocol::nRecord. This activates the function group of the following + * macro and causes it to be logged in the future. + * 3. There's a special case for 2: If one uses 2. in SwRootFrame::PaintSwFrame(..), + * the log settings are taken from the file "dbg_lay.ini"! + * In this INI-file you can have comment lines starting with a '#'. + * The sections "[frmid]", "[frmtype]" and "[record]" are relevant. + * In the [frmid] section, you can put FrameIds of the Frames to be logged. + * If there are no entries in this section, all Frames will be logged. + * In the [frmtype] section, the frame types which should be logged are + * listed; default is USHRT_MAX which means that all types are logged. + * It's possible to remove types from the list using '!' in front of a + * value. The value !0xC000 would for example exclude SwContentFrames from + * logging. + * In the [record] section the functions group which should be logged are + * listed; default is 0 which means that none are logged. It's also + * possible to remove functions using '!'. + * An example INI file: + * #Functions: all(0x0007ffff), except PrintArea (0x200) + * [record] 524287 !512 + * [frmid] + * #the following FrameIds: + * 1 2 12 13 14 15 + * #no layout frames, except ColumnFrames + * [frmtype] !0x3FFF 0x4 + * + * As soon as the logging is in process, one can manipulate many things in + * SwImplProtocol::Record_(...) using a debugger, especially concerning + * frame types and FrameIds. + */ + +#include <dbg_lay.hxx> + +#include <txtfrm.hxx> +#include <fntcache.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <cellfrm.hxx> +#include <layfrm.hxx> +#include <frame.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> +#include <tools/stream.hxx> + +PROT SwProtocol::s_nRecord = PROT::FileInit; +SwImplProtocol* SwProtocol::s_pImpl = nullptr; + +static sal_uLong lcl_GetFrameId( const SwFrame* pFrame ) +{ +#if OSL_DEBUG_LEVEL > 1 + static bool bFrameId = false; + if( bFrameId ) + return pFrame->GetFrameId(); +#endif + if( pFrame ) + return pFrame->GetFrameId(); + return 0; +} + +class SwImplProtocol +{ + std::unique_ptr<SvFileStream> m_pStream; // output stream + std::unique_ptr<std::set<sal_uInt16>> + m_pFrameIds; // which FrameIds shall be logged ( NULL == all) + std::vector<tools::Long> m_aVars; // variables + OStringBuffer m_aLayer; // indentation of output (" " per start/end) + SwFrameType m_nTypes; // which types shall be logged + sal_uInt16 m_nLineCount; // printed lines + sal_uInt16 m_nMaxLines; // max lines to be printed + sal_uInt8 m_nInitFile; // range (FrameId,FrameType,Record) during reading of the INI file + sal_uInt8 + m_nTestMode; // special for test formatting, logging may only be done in test formatting. + void Record_( const SwFrame* pFrame, PROT nFunction, DbgAction nAct, void* pParam ); + bool NewStream(); + void CheckLine( OString& rLine ); + static void SectFunc( OStringBuffer& rOut, DbgAction nAct, void const * pParam ); +public: + SwImplProtocol(); + ~SwImplProtocol(); + // logging + void Record( const SwFrame* pFrame, PROT nFunction, DbgAction nAct, void* pParam ) + { + if (m_pStream) + Record_(pFrame, nFunction, nAct, pParam); + } + void InsertFrame( sal_uInt16 nFrameId ); // take FrameId for logging + void DeleteFrame( sal_uInt16 nFrameId ); // remove FrameId; don't log him anymore + void FileInit(); // read the INI file + void ChkStream() { + if (!m_pStream) + NewStream(); + } +}; + +/* Through the PROTOCOL_ENTER macro a SwEnterLeave object gets created. If the + * current function should be logged as SwImplEnterLeace object gets created. + * The funny thing here is, that the Ctor of the Impl object is automatically + * called at the beginning of the function and the Dtor is automatically called + * when leaving the function. In the base implementation the Ctor calls only + * PROTOCOL(..) with DbgAction::Start and in the Dtor a PROTOCOL(..) with DbgAction::End. + * It's possible to derive from this class, for example to be able to document + * frame resize while leaving a function. To do this, one only needs to add the + * desired SwImplEnterLeave class in SwEnterLeave::Ctor(). + */ + +class SwImplEnterLeave +{ +protected: + const SwFrame* m_pFrame; // the frame + PROT m_nFunction; // the function + DbgAction m_nAction; // the action if needed + void* m_pParam; // further parameter +public: + SwImplEnterLeave(const SwFrame* pF, PROT nFunct, DbgAction nAct, void* pPar) + : m_pFrame(pF) + , m_nFunction(nFunct) + , m_nAction(nAct) + , m_pParam(pPar) + { + } + virtual ~SwImplEnterLeave() {} + virtual void Enter(); // message when entering + virtual void Leave(); // message when leaving +}; + +namespace { + +class SwSizeEnterLeave : public SwImplEnterLeave +{ + tools::Long m_nFrameHeight; + +public: + SwSizeEnterLeave(const SwFrame* pF, PROT nFunct, DbgAction nAct, void* pPar) + : SwImplEnterLeave(pF, nFunct, nAct, pPar) + , m_nFrameHeight(pF->getFrameArea().Height()) + { + } + + virtual void Leave() override; // resize message +}; + +class SwUpperEnterLeave : public SwImplEnterLeave +{ + sal_uInt16 m_nFrameId; + +public: + SwUpperEnterLeave(const SwFrame* pF, PROT nFunct, DbgAction nAct, void* pPar) + : SwImplEnterLeave(pF, nFunct, nAct, pPar) + , m_nFrameId(0) + { + } + + virtual void Enter() override; // message + virtual void Leave() override; // message of FrameId from upper +}; + +class SwFrameChangesLeave : public SwImplEnterLeave +{ + SwRect m_aFrame; + +public: + SwFrameChangesLeave(const SwFrame* pF, PROT nFunct, DbgAction nAct, void* pPar) + : SwImplEnterLeave(pF, nFunct, nAct, pPar) + , m_aFrame(pF->getFrameArea()) + { + } + + virtual void Enter() override; // no message + virtual void Leave() override; // message when resizing the Frame area +}; + +} + +void SwProtocol::Record( const SwFrame* pFrame, PROT nFunction, DbgAction nAct, void* pParam ) +{ + if( Start() ) + { // We reach this point if SwProtocol::nRecord is binary OR'd with PROT::Init(0x1) using the debugger + bool bFinit = false; // This gives the possibility to stop logging of this action in the debugger + if( bFinit ) + { + s_nRecord &= ~nFunction; // Don't log this function any longer + s_nRecord &= ~PROT::Init; // Always reset PROT::Init + return; + } + s_nRecord |= nFunction; // Activate logging of this function + s_nRecord &= ~PROT::Init; // Always reset PROT::Init + if( s_pImpl ) + s_pImpl->ChkStream(); + } + if( !s_pImpl ) // Create Impl object if needed + s_pImpl = new SwImplProtocol(); + s_pImpl->Record( pFrame, nFunction, nAct, pParam ); // ...and start logging +} + +// The following function gets called when pulling in the writer DLL through +// TextInit(..) and gives the possibility to release functions +// and/or FrameIds to the debugger + +void SwProtocol::Init() +{ + s_nRecord = PROT::FileInit; + SvFileStream aStream( "dbg_lay.go", StreamMode::READ ); + if( aStream.IsOpen() ) + { + s_pImpl = new SwImplProtocol(); + s_pImpl->FileInit(); + } + aStream.Close(); +} + +// End of logging + +void SwProtocol::Stop() +{ + if( s_pImpl ) + { + delete s_pImpl; + s_pImpl = nullptr; + if( pFntCache ) + pFntCache->Flush(); + } + s_nRecord = PROT::FileInit; +} + +SwImplProtocol::SwImplProtocol() + : m_nTypes(FRM_ALL) + , m_nLineCount(0) + , m_nMaxLines(USHRT_MAX) + , m_nTestMode(0) +{ + NewStream(); +} + +bool SwImplProtocol::NewStream() +{ + m_nLineCount = 0; + m_pStream.reset(new SvFileStream("dbg_lay.out", StreamMode::WRITE | StreamMode::TRUNC)); + if (m_pStream->GetError()) + { + m_pStream.reset(); + } + return nullptr != m_pStream; +} + +SwImplProtocol::~SwImplProtocol() +{ + if (m_pStream) + { + m_pStream->Close(); + m_pStream.reset(); + } + m_pFrameIds.reset(); + m_aVars.clear(); +} + +/// analyze a line in the INI file +void SwImplProtocol::CheckLine( OString& rLine ) +{ + rLine = rLine.toAsciiLowerCase(); // upper/lower case is the same + rLine = rLine.replace( '\t', ' ' ); + if( '#' == rLine[0] ) // comments start with '#' + return; + if( '[' == rLine[0] ) // section: FrameIds, type or function + { + std::string_view aTmp = o3tl::getToken(rLine, 0, ']'); + if (aTmp == "[frmid") // section FrameIds + { + m_nInitFile = 1; + m_pFrameIds.reset(); // default: log all frames + } + else if (aTmp == "[frmtype")// section types + { + m_nInitFile = 2; + m_nTypes = FRM_ALL; // default: log all frame types + } + else if (aTmp == "[record")// section functions + { + m_nInitFile = 3; + SwProtocol::SetRecord( PROT::FileInit );// default: don't log any function + } + else if (aTmp == "[test")// section functions + { + m_nInitFile = 4; // default: + m_nTestMode = 0; // log outside of test formatting + } + else if (aTmp == "[max")// Max number of lines + { + m_nInitFile = 5; // default: + m_nMaxLines = USHRT_MAX; + } + else if (aTmp == "[var")// variables + { + m_nInitFile = 6; + } + else + m_nInitFile = 0; // oops: unknown section? + rLine = rLine.copy(aTmp.size() + 1); + } + + // spaces (or tabs) are the delimiter + sal_Int32 nIndex = 0; + do + { + std::string_view aTok = o3tl::getToken(rLine, 0, ' ', nIndex ); + bool bNo = false; + if( !aTok.empty() && '!' == aTok[0] ) + { + bNo = true; // remove this function/type + aTok = aTok.substr(1); + } + if( !aTok.empty() ) + { + sal_Int64 nVal = o3tl::toInt64(aTok); + switch (m_nInitFile) + { + case 1: InsertFrame( sal_uInt16( nVal ) ); // add FrameId + break; + case 2: { + SwFrameType nNew = static_cast<SwFrameType>(nVal); + if( bNo ) + m_nTypes &= ~nNew; // remove type + else + m_nTypes |= nNew; // add type + } + break; + case 3: { + PROT nOld = SwProtocol::Record(); + if( bNo ) + nOld &= ~PROT(nVal & o3tl::typed_flags<PROT>::mask); // remove function + else + nOld |= PROT(nVal & o3tl::typed_flags<PROT>::mask); // remove function + SwProtocol::SetRecord( nOld ); + } + break; + case 4: { + sal_uInt8 nNew = static_cast<sal_uInt8>(nVal); + if( bNo ) + m_nTestMode &= ~nNew; // reset test mode + else + m_nTestMode |= nNew; // set test mode + } + break; + case 5: + m_nMaxLines = o3tl::narrowing<sal_uInt16>(nVal); + break; + case 6: + m_aVars.push_back(nVal); + break; + } + } + } + while ( nIndex >= 0 ); +} + +/// read the file "dbg_lay.ini" in the current directory and evaluate it. +void SwImplProtocol::FileInit() +{ + SvFileStream aStream( "dbg_lay.ini", StreamMode::READ ); + if( aStream.IsOpen() ) + { + OString aLine; + m_nInitFile = 0; + while( aStream.good() ) + { + char c; + aStream.ReadChar( c ); + if( '\n' == c || '\r' == c ) // line ending + { + aLine = aLine.trim(); + if( !aLine.isEmpty() ) + CheckLine( aLine ); // evaluate line + aLine.clear(); + } + else + aLine += OStringChar(c); + } + if( !aLine.isEmpty() ) + CheckLine( aLine ); // evaluate last line + } + aStream.Close(); +} + +/// enable indentation by two spaces during DbgAction::Start and disable it again at DbgAction::End. +static void lcl_Start(OStringBuffer& rOut, OStringBuffer& rLay, DbgAction nAction) +{ + if( nAction == DbgAction::Start ) + { + rLay.append(" "); + rOut.append(" On"); + } + else if( nAction == DbgAction::End ) + { + if( rLay.getLength() > 1 ) + { + rLay.remove(rLay.getLength() - 2, rLay.getLength()); + rOut.remove(0, 2); + } + rOut.append(" Off"); + } +} + +/// output the ValidSize-, ValidPos- and ValidPrtArea-Flag ("Sz","Ps","PA") +/// of the frame; "+" stands for valid, "-" stands for invalid. +static void lcl_Flags(OStringBuffer& rOut, const SwFrame* pFrame) +{ + rOut.append(" ValidSize"); + rOut.append(pFrame->isFrameAreaSizeValid() ? '+' : '-'); + rOut.append(" ValidPos"); + rOut.append(pFrame->isFrameAreaPositionValid() ? '+' : '-'); + rOut.append(" ValidPrtArea"); + rOut.append(pFrame->isFramePrintAreaValid() ? '+' : '-'); +} + +static void lcl_Padded(OStringBuffer& rOut, const OString& s, size_t length) +{ + if (length < o3tl::make_unsigned(s.getLength())) + length = s.getLength(); + rOut.append(s); + for (size_t i = 0; i < length - s.getLength(); i++) + { + rOut.append(" "); + } +} + +static void lcl_Padded(OStringBuffer& rOut, const tools::Long n, size_t length = 5) +{ + char sz[RTL_STR_MAX_VALUEOFINT64]; + rtl_str_valueOfInt64(sz, n, 10); + OString s(sz); + lcl_Padded(rOut, s, length); +} + +/// output the frame as plain text. +static void lcl_FrameRect(OStringBuffer& rOut, const char* hint, const SwRect& rect) +{ + rOut.append("["); + rOut.append(hint); + rOut.append(":X:"); + lcl_Padded(rOut, rect.Pos().X()); + rOut.append(", Y:"); + lcl_Padded(rOut, rect.Pos().Y()); + rOut.append(", Width:"); + lcl_Padded(rOut, rect.SSize().Width()); + rOut.append(", Height:"); + lcl_Padded(rOut, rect.SSize().Height()); + rOut.append("] "); +} + +static OString lcl_TableInfo(const SwTabFrame* pTabFrame) +{ + const SwTable* pTable = pTabFrame->GetTable(); + const SwFormat* pFormat = static_cast<const SwFormat*>(pTable->GetRegisteredIn()); + const OUString& text = pFormat->GetName(); + return OUStringToOString(text, RTL_TEXTENCODING_ASCII_US); +} + +static OString lcl_RowInfo(const SwRowFrame* pFrame) +{ + // dummy, needs actual functionality... + if (pFrame == nullptr) + return ""; + const SwTableLine* pTabLine = pFrame->GetTabLine(); + if (pTabLine == nullptr) + return ""; + + return "RowInfo"; +} + +static OUString lcl_CellText(const SwCellFrame* pFrame) +{ + OUString result; + int n = 0; + + const SwStartNode* pStartNode = pFrame->GetTabBox()->GetSttNd(); + const SwEndNode* pEndNode = pStartNode->EndOfSectionNode(); + const SwNodes& nodes = pStartNode->GetNodes(); + + for (SwNodeOffset i = pStartNode->GetIndex(); i < nodes.Count(); i++) + { + SwNode* pNode = nodes[i]; + + if (pNode->IsEndNode()) + { + if (pNode->EndOfSectionNode() == pEndNode) + break; + } + else if (pNode->IsTextNode()) + { + n++; + result += "Para:" + OUString::number(10) + " " + + pNode->GetTextNode()->GetText(); + } + } + + return OUString::number(n) + " para(s):" + result; +} + +static OString lcl_CellInfo(const SwCellFrame* pFrame) +{ + const OUString text = "CellInfo: " + pFrame->GetTabBox()->GetName() + " Text: " + lcl_CellText(pFrame); + return OUStringToOString(text, RTL_TEXTENCODING_ASCII_US); +} + +/// output the type of the frame as plain text. +static void lcl_FrameType( OStringBuffer& rOut, const SwFrame* pFrame ) +{ + if( pFrame->IsTextFrame() ) + rOut.append("SwTextFrame "); + else if( pFrame->IsLayoutFrame() ) + { + if( pFrame->IsPageFrame() ) + rOut.append("SwPageFrame "); + else if( pFrame->IsColumnFrame() ) + rOut.append("SwColumnFrame "); + else if( pFrame->IsBodyFrame() ) + { + if( pFrame->GetUpper() && pFrame->IsColBodyFrame() ) + rOut.append("(Col)"); + rOut.append("SwBodyFrame "); + } + else if( pFrame->IsRootFrame() ) + rOut.append("SwRootFrame "); + else if( pFrame->IsCellFrame() ) + rOut.append("SwCellFrame "); + else if( pFrame->IsTabFrame() ) + rOut.append("SwTabFrame "); + else if( pFrame->IsRowFrame() ) + rOut.append("SwRowFrame "); + else if( pFrame->IsSctFrame() ) + rOut.append("SwSectionFrame "); + else if( pFrame->IsHeaderFrame() ) + rOut.append("SwHeaderFrame "); + else if( pFrame->IsFooterFrame() ) + rOut.append("SwFooterFrame "); + else if( pFrame->IsFootnoteFrame() ) + rOut.append("SwFootnoteFrame "); + else if( pFrame->IsFootnoteContFrame() ) + rOut.append("SwFootnoteContFrame "); + else if( pFrame->IsFlyFrame() ) + rOut.append("SwFlyFrame "); + else + rOut.append("SwLayoutFrame "); + } + else if( pFrame->IsNoTextFrame() ) + rOut.append("SwNoTextFrame"); + else + rOut.append("Not impl. "); +} + +/** + * Is only called if the PROTOCOL macro finds out, + * that this function should be recorded ( @see{SwProtocol::nRecord} ). + * + * In this method we also check if FrameId and frame type should be logged. + */ +void SwImplProtocol::Record_( const SwFrame* pFrame, PROT nFunction, DbgAction nAct, void* pParam ) +{ + sal_uInt16 nSpecial = 0; + if( nSpecial ) // the possible debugger manipulations + { + sal_uInt16 nId = sal_uInt16(lcl_GetFrameId( pFrame )); + switch ( nSpecial ) + { + case 1: InsertFrame( nId ); break; + case 2: DeleteFrame( nId ); break; + case 3: + m_pFrameIds.reset(); + break; + case 4: + m_pStream.reset(); + break; + } + return; + } + if (!m_pStream && !NewStream()) + return; // still no stream + + if (m_pFrameIds && !m_pFrameIds->count(sal_uInt16(lcl_GetFrameId(pFrame)))) + return; // doesn't belong to the wished FrameIds + + if (!(pFrame->GetType() & m_nTypes)) + return; // the type is unwanted + + if (1 == m_nTestMode && nFunction != PROT::TestFormat) + return; // we may only log inside a test formatting + bool bTmp = false; + OStringBuffer aOut(m_aLayer); + aOut.append(static_cast<sal_Int64>(lcl_GetFrameId(pFrame))); + aOut.append(' '); + lcl_FrameType( aOut, pFrame ); // then the frame type + switch ( nFunction ) // and the function + { + case PROT::MakeAll: aOut.append("SwFrame::MakeAll"); + lcl_Start(aOut, m_aLayer, nAct); + if (nAct == DbgAction::Start) + lcl_Flags(aOut, pFrame); + break; + case PROT::MoveFwd: bTmp = true; + [[fallthrough]]; + case PROT::MoveBack: + if (nFunction == (bTmp ? PROT::Init : PROT::FileInit)) + aOut.append("SwFlowFrame::MoveFwd"); + else + aOut.append("SwFlowFrame::MoveBwd"); + lcl_Start(aOut, m_aLayer, nAct); + if( pParam ) + { + aOut.append(' '); + aOut.append(static_cast<sal_Int32>(*static_cast<sal_uInt16*>(pParam))); + } + break; + case PROT::GrowTest: + aOut.append("SwFrame::Grow (test)"); + lcl_Start(aOut, m_aLayer, nAct); + break; + case PROT::ShrinkTest: + aOut.append("SwFrame::Shrink (test)"); + lcl_Start(aOut, m_aLayer, nAct); + break; + case PROT::AdjustN : + case PROT::Shrink: bTmp = true; + [[fallthrough]]; + case PROT::Grow: + if (!bTmp) + aOut.append("SwFrame::Grow"); + else + { + if (nFunction == PROT::Shrink) + aOut.append("SwFrame::Shrink"); + else + aOut.append("SwFrame::AdjustNeighbourhood"); + } + lcl_Start(aOut, m_aLayer, nAct); + if( pParam ) + { + aOut.append(' '); + aOut.append(static_cast<sal_Int64>(*static_cast<tools::Long*>(pParam))); + } + break; + case PROT::PrintArea: aOut.append("PROT::PrintArea"); + lcl_Start(aOut, m_aLayer, nAct); + break; + case PROT::Size: aOut.append("PROT::Size"); + lcl_Start(aOut, m_aLayer, nAct); + aOut.append(' '); + aOut.append(static_cast<sal_Int64>(pFrame->getFrameArea().Height())); + break; + case PROT::Leaf: aOut.append("SwFrame::GetPrev/NextSctLeaf"); + lcl_Start(aOut, m_aLayer, nAct); + aOut.append(' '); + if (pParam) + { + aOut.append(' '); + aOut.append(static_cast<sal_Int64>(lcl_GetFrameId(static_cast<SwFrame*>(pParam)))); + } + break; + case PROT::FileInit: FileInit(); + aOut.append("Initialize"); + break; + case PROT::Section: SectFunc(aOut, nAct, pParam); + break; + case PROT::Cut: bTmp = true; + [[fallthrough]]; + case PROT::Paste: + if (bTmp) + aOut.append("PROT::Cut from "); + else + aOut.append("PROT::Paste to "); + aOut.append(static_cast<sal_Int64>(lcl_GetFrameId(static_cast<SwFrame*>(pParam)))); + break; + case PROT::TestFormat: + aOut.append("SwTextFrame::TestFormat"); + lcl_Start(aOut, m_aLayer, nAct); + if( DbgAction::Start == nAct ) + m_nTestMode |= 2; + else + m_nTestMode &= ~2; + break; + case PROT::FrmChanges: + { + SwRect& rFrame = *static_cast<SwRect*>(pParam); + if( pFrame->getFrameArea().Pos() != rFrame.Pos() ) + { + aOut.append("PosChg: ("); + aOut.append(static_cast<sal_Int64>(rFrame.Left())); + aOut.append(", "); + aOut.append(static_cast<sal_Int64>(rFrame.Top())); + aOut.append(") -> ("); + aOut.append(static_cast<sal_Int64>(pFrame->getFrameArea().Left())); + aOut.append(", "); + aOut.append(static_cast<sal_Int64>(pFrame->getFrameArea().Top())); + aOut.append(") "); + } + if( pFrame->getFrameArea().Height() != rFrame.Height() ) + { + aOut.append("Height: "); + aOut.append(static_cast<sal_Int64>(rFrame.Height())); + aOut.append(" -> "); + aOut.append(static_cast<sal_Int64>(pFrame->getFrameArea().Height())); + aOut.append(" "); + } + if( pFrame->getFrameArea().Width() != rFrame.Width() ) + { + aOut.append("Width: "); + aOut.append(static_cast<sal_Int64>(rFrame.Width())); + aOut.append(" -> "); + aOut.append(static_cast<sal_Int64>(pFrame->getFrameArea().Width())); + aOut.append(' '); + } + break; + } + default: break; + } + + aOut.append(" "); + while (aOut.getLength() < 40) aOut.append(" "); + lcl_FrameRect(aOut, "SwFrame", pFrame->getFrameArea()); + + aOut.append(" "); + while (aOut.getLength() < 90) aOut.append(" "); + lcl_FrameRect(aOut, "SwPrint", pFrame->getFramePrintArea()); + + if (pFrame->IsTextFrame()) + { + aOut.append(" "); + while (aOut.getLength() < 140) aOut.append(" "); + const OUString& text = static_cast<const SwTextFrame*>(pFrame)->GetText(); + OString o = OUStringToOString(text, RTL_TEXTENCODING_ASCII_US); + aOut.append(o); + } + else if (pFrame->IsTabFrame()) + { + const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pFrame); + aOut.append(lcl_TableInfo(pTabFrame)); + } + else if (pFrame->IsRowFrame()) + { + const SwRowFrame* pRowFrame = static_cast<const SwRowFrame*>(pFrame); + aOut.append(lcl_RowInfo(pRowFrame)); + + } + else if (pFrame->IsCellFrame()) + { + const SwCellFrame* pCellFrame = static_cast<const SwCellFrame*>(pFrame); + aOut.append(lcl_CellInfo(pCellFrame)); + } + + SAL_INFO("sw.layout.debug", aOut.getStr()); + m_pStream->WriteOString(aOut.makeStringAndClear()); + (*m_pStream) << endl; // output + m_pStream->Flush(); // to the disk, so we can read it immediately + if (++m_nLineCount >= m_nMaxLines) // max number of lines reached? + { + SAL_WARN("sw.layout.debug", "max number of lines reached"); + SwProtocol::SetRecord( PROT::FileInit ); // => end f logging + } +} + +/// Handle the output of the SectionFrames. +void SwImplProtocol::SectFunc(OStringBuffer &rOut, DbgAction nAct, void const * pParam) +{ + bool bTmp = false; + switch( nAct ) + { + case DbgAction::Merge: rOut.append("Merge Section "); + rOut.append(static_cast<sal_Int64>(lcl_GetFrameId(static_cast<SwFrame const *>(pParam)))); + break; + case DbgAction::CreateMaster: bTmp = true; + [[fallthrough]]; + case DbgAction::CreateFollow: rOut.append("Create Section "); + if (bTmp) + rOut.append("Master to "); + else + rOut.append("Follow from "); + rOut.append(static_cast<sal_Int64>(lcl_GetFrameId(static_cast<SwFrame const *>(pParam)))); + break; + case DbgAction::DelMaster: bTmp = true; + [[fallthrough]]; + case DbgAction::DelFollow: rOut.append("Delete Section "); + if (bTmp) + rOut.append("Master to "); + else + rOut.append("Follow from "); + rOut.append(static_cast<sal_Int64>(lcl_GetFrameId(static_cast<SwFrame const *>(pParam)))); + break; + default: break; + } +} + +/** + * if pFrameIds==NULL all Frames will be logged. But as soon as pFrameIds are + * set, only the added FrameIds are being logged. + * + * @param nId new FrameId for logging + * @return TRUE if newly added, FALSE if FrameId is already under control + */ +void SwImplProtocol::InsertFrame( sal_uInt16 nId ) +{ + if (!m_pFrameIds) + m_pFrameIds.reset(new std::set<sal_uInt16>); + if (m_pFrameIds->count(nId)) + return; + m_pFrameIds->insert(nId); +} + +/// Removes a FrameId from the pFrameIds array, so that it won't be logged anymore. +void SwImplProtocol::DeleteFrame( sal_uInt16 nId ) +{ + if (!m_pFrameIds) + return; + m_pFrameIds->erase(nId); +} + +/* + * The task here is to find the right SwImplEnterLeave object based on the + * function; everything else is then done in his Ctor/Dtor. + */ +SwEnterLeave::SwEnterLeave( const SwFrame* pFrame, PROT nFunc, DbgAction nAct, void* pPar ) +{ + if( !SwProtocol::Record( nFunc ) ) + return; + switch( nFunc ) + { + case PROT::AdjustN : + case PROT::Grow: + case PROT::Shrink : pImpl.reset( new SwSizeEnterLeave( pFrame, nFunc, nAct, pPar ) ); break; + case PROT::MoveFwd: + case PROT::MoveBack : pImpl.reset( new SwUpperEnterLeave( pFrame, nFunc, nAct, pPar ) ); break; + case PROT::FrmChanges : pImpl.reset( new SwFrameChangesLeave( pFrame, nFunc, nAct, pPar ) ); break; + default: pImpl.reset( new SwImplEnterLeave( pFrame, nFunc, nAct, pPar ) ); break; + } + pImpl->Enter(); +} + +/* This is not inline because we don't want the SwImplEnterLeave definition inside + * dbg_lay.hxx. + */ +SwEnterLeave::~SwEnterLeave() +{ + if (pImpl) + pImpl->Leave(); +} + +void SwImplEnterLeave::Enter() +{ + SwProtocol::Record(m_pFrame, m_nFunction, DbgAction::Start, m_pParam); +} + +void SwImplEnterLeave::Leave() { + SwProtocol::Record(m_pFrame, m_nFunction, DbgAction::End, m_pParam); +} + +void SwSizeEnterLeave::Leave() +{ + m_nFrameHeight = m_pFrame->getFrameArea().Height() - m_nFrameHeight; + SwProtocol::Record(m_pFrame, m_nFunction, DbgAction::End, &m_nFrameHeight); +} + +void SwUpperEnterLeave::Enter() +{ + m_nFrameId = m_pFrame->GetUpper() ? sal_uInt16(lcl_GetFrameId(m_pFrame->GetUpper())) : 0; + SwProtocol::Record(m_pFrame, m_nFunction, DbgAction::Start, &m_nFrameId); +} + +void SwUpperEnterLeave::Leave() +{ + m_nFrameId = m_pFrame->GetUpper() ? sal_uInt16(lcl_GetFrameId(m_pFrame->GetUpper())) : 0; + SwProtocol::Record(m_pFrame, m_nFunction, DbgAction::End, &m_nFrameId); +} + +void SwFrameChangesLeave::Enter() +{ +} + +void SwFrameChangesLeave::Leave() +{ + if (m_pFrame->getFrameArea() != m_aFrame) + SwProtocol::Record(m_pFrame, PROT::FrmChanges, DbgAction::NONE, &m_aFrame); +} + +#endif // DBG_UTIL + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/dumpfilter.cxx b/sw/source/core/layout/dumpfilter.cxx new file mode 100644 index 000000000..21d1ec354 --- /dev/null +++ b/sw/source/core/layout/dumpfilter.cxx @@ -0,0 +1,164 @@ +/* -*- 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 <dumpfilter.hxx> + +#include <wrtsh.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <docsh.hxx> +#include <rootfrm.hxx> +#include <unotxdoc.hxx> + +#include <comphelper/servicehelper.hxx> +#include <unotools/mediadescriptor.hxx> + +#include <libxml/xmlwriter.h> + +using namespace ::com::sun::star; + +namespace +{ + int writeCallback( void* pContext, const char* sBuffer, int nLen ) + { + int written = nLen; + + // Actually write bytes to XOutputSream + try + { + uno::XInterface* pObj = static_cast<uno::XInterface*>(pContext); + uno::Reference< io::XOutputStream > xOut( pObj, uno::UNO_QUERY_THROW ); + + // Don't output the terminating \0 to the xml or the file will be invalid + uno::Sequence< sal_Int8 > seq( nLen ); + strncpy( reinterpret_cast<char *>(seq.getArray()), sBuffer, nLen ); + xOut->writeBytes( seq ); + } + catch (const uno::Exception&) + { + written = -1; + } + + return written; + } + + int closeCallback( void* pContext ) + { + int result = 0; + try + { + uno::XInterface* pObj = static_cast<uno::XInterface*>(pContext); + uno::Reference< io::XOutputStream > xOut( pObj, uno::UNO_QUERY_THROW ); + xOut->closeOutput( ); + } + catch (const uno::Exception&) + { + result = -1; + } + return result; + } +} + +namespace sw +{ + + LayoutDumpFilter::LayoutDumpFilter( ) + { + } + + LayoutDumpFilter::~LayoutDumpFilter( ) + { + } + + // XFilter + sal_Bool LayoutDumpFilter::filter( const uno::Sequence< beans::PropertyValue >& aDescriptor ) + { + bool bRet = false; + + utl::MediaDescriptor aMediaDesc = aDescriptor; + + // Get the output stream + uno::Reference< io::XOutputStream > xOut = aMediaDesc.getUnpackedValueOrDefault( + utl::MediaDescriptor::PROP_OUTPUTSTREAM, + uno::Reference< io::XOutputStream >() ); + + // Actually get the SwRootFrame to call dumpAsXml + auto pXDoc = comphelper::getFromUnoTunnel<SwXTextDocument>(m_xSrcDoc); + if ( pXDoc ) + { + SwRootFrame* pLayout = pXDoc->GetDocShell()->GetWrtShell()->GetLayout(); + + // Get sure that the whole layout is processed: set a visible area + // even though there isn't any need of it + pXDoc->GetDocShell()->GetWrtShell()->StartAction(); + tools::Rectangle aRect( 0, 0, 26000, 21000 ); + pXDoc->GetDocShell()->SetVisArea( aRect ); + pLayout->InvalidateAllContent( SwInvalidateFlags::Size ); + pXDoc->GetDocShell()->GetWrtShell()->EndAction(); + + // Dump the layout XML into the XOutputStream + xmlOutputBufferPtr outBuffer = xmlOutputBufferCreateIO( + writeCallback, closeCallback, static_cast<void*>(xOut.get()), nullptr ); + + xmlTextWriterPtr writer = xmlNewTextWriter( outBuffer ); + xmlTextWriterSetIndent(writer, 1); + (void)xmlTextWriterStartDocument( writer, nullptr, nullptr, nullptr ); + + // TODO This doesn't export the whole XML file, whereas dumpAsXML() does it nicely + pLayout->dumpAsXml( writer ); + + (void)xmlTextWriterEndDocument( writer ); + xmlFreeTextWriter( writer ); + + bRet = true; + } + + return bRet; + } + + void LayoutDumpFilter::cancel( ) + { + } + + // XExporter + void LayoutDumpFilter::setSourceDocument( const uno::Reference< lang::XComponent >& xDoc ) + { + m_xSrcDoc = xDoc; + } + + // XInitialization + void LayoutDumpFilter::initialize( const uno::Sequence< uno::Any >& ) + { + } + + // XServiceInfo + OUString LayoutDumpFilter::getImplementationName( ) + { + return "com.sun.star.comp.Writer.LayoutDump"; + } + + sal_Bool LayoutDumpFilter::supportsService( const OUString& rServiceName ) + { + return cppu::supportsService(this, rServiceName); + } + + uno::Sequence< OUString > LayoutDumpFilter::getSupportedServiceNames() + { + return { "com.sun.star.document.ExportFilter" }; + } + +} // Namespace sw + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_Writer_LayoutDump_get_implementation(css::uno::XComponentContext*, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new sw::LayoutDumpFilter()); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/findfrm.cxx b/sw/source/core/layout/findfrm.cxx new file mode 100644 index 000000000..3d92f2a9c --- /dev/null +++ b/sw/source/core/layout/findfrm.cxx @@ -0,0 +1,1905 @@ +/* -*- 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 <pagefrm.hxx> +#include <rootfrm.hxx> +#include <cellfrm.hxx> +#include <rowfrm.hxx> +#include <swtable.hxx> +#include <notxtfrm.hxx> +#include <tabfrm.hxx> +#include <sectfrm.hxx> +#include <frmatr.hxx> +#include <flyfrm.hxx> +#include <ftnfrm.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <fmtpdsc.hxx> +#include <fmtclbl.hxx> +#include <txtfrm.hxx> +#include <bodyfrm.hxx> +#include <calbck.hxx> +#include <viewopt.hxx> +#include <ndtxt.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <IDocumentSettingAccess.hxx> + + +/// Searches the first ContentFrame in BodyText below the page. +SwLayoutFrame *SwFootnoteBossFrame::FindBodyCont() +{ + SwFrame *pLay = Lower(); + while ( pLay && !pLay->IsBodyFrame() ) + pLay = pLay->GetNext(); + return static_cast<SwLayoutFrame*>(pLay); +} + +/// Searches the last ContentFrame in BodyText below the page. +SwContentFrame *SwPageFrame::FindLastBodyContent() +{ + SwContentFrame *pRet = FindFirstBodyContent(); + SwContentFrame *pNxt = pRet; + while ( pNxt && pNxt->IsInDocBody() && IsAnLower( pNxt ) ) + { pRet = pNxt; + pNxt = pNxt->FindNextCnt(); + } + return pRet; +} + +/** + * Checks if the frame contains one or more ContentFrame's anywhere in his + * subsidiary structure; if so the first found ContentFrame is returned. + */ +const SwContentFrame *SwLayoutFrame::ContainsContent() const +{ + //Search downwards the layout leaf and if there is no content, jump to the + //next leaf until content is found or we leave "this". + //Sections: Content next to sections would not be found this way (empty + //sections directly next to ContentFrame) therefore we need to recursively + //search for them even if it's more complex. + + const SwLayoutFrame *pLayLeaf = this; + do + { + while ( (!pLayLeaf->IsSctFrame() || pLayLeaf == this ) && + pLayLeaf->Lower() && pLayLeaf->Lower()->IsLayoutFrame() ) + pLayLeaf = static_cast<const SwLayoutFrame*>(pLayLeaf->Lower()); + + if( pLayLeaf->IsSctFrame() && pLayLeaf != this ) + { + const SwContentFrame *pCnt = pLayLeaf->ContainsContent(); + if( pCnt ) + return pCnt; + if( pLayLeaf->GetNext() ) + { + if( pLayLeaf->GetNext()->IsLayoutFrame() ) + { + pLayLeaf = static_cast<const SwLayoutFrame*>(pLayLeaf->GetNext()); + continue; + } + else + return static_cast<const SwContentFrame*>(pLayLeaf->GetNext()); + } + } + else if ( pLayLeaf->Lower() ) + return static_cast<const SwContentFrame*>(pLayLeaf->Lower()); + + pLayLeaf = pLayLeaf->GetNextLayoutLeaf(); + if( !IsAnLower( pLayLeaf) ) + return nullptr; + } while( pLayLeaf ); + return nullptr; +} + +/** + * Calls ContainsAny first to reach the innermost cell. From there we walk back + * up to the first SwCellFrame. Since we use SectionFrames, ContainsContent()->GetUpper() + * is not enough anymore. + */ +const SwCellFrame *SwLayoutFrame::FirstCell() const +{ + const SwFrame* pCnt = ContainsAny(); + while( pCnt && !pCnt->IsCellFrame() ) + pCnt = pCnt->GetUpper(); + return static_cast<const SwCellFrame*>(pCnt); +} + +/** return ContentFrames, sections, and tables. + * + * @param _bInvestigateFootnoteForSections controls investigation of content of footnotes for sections. + * @see ContainsContent + */ +const SwFrame *SwLayoutFrame::ContainsAny( const bool _bInvestigateFootnoteForSections ) const +{ + //Search downwards the layout leaf and if there is no content, jump to the + //next leaf until content is found, we leave "this" or until we found + //a SectionFrame or a TabFrame. + + const SwLayoutFrame *pLayLeaf = this; + const bool bNoFootnote = IsSctFrame() && !_bInvestigateFootnoteForSections; + do + { + while ( ( (!pLayLeaf->IsSctFrame() && !pLayLeaf->IsTabFrame()) + || pLayLeaf == this ) && + pLayLeaf->Lower() && pLayLeaf->Lower()->IsLayoutFrame() ) + pLayLeaf = static_cast<const SwLayoutFrame*>(pLayLeaf->Lower()); + + if( ( pLayLeaf->IsTabFrame() || pLayLeaf->IsSctFrame() ) + && pLayLeaf != this ) + { + // Now we also return "deleted" SectionFrames so they can be + // maintained on SaveContent and RestoreContent + return pLayLeaf; + } + else if ( pLayLeaf->Lower() ) + return static_cast<const SwContentFrame*>(pLayLeaf->Lower()); + + pLayLeaf = pLayLeaf->GetNextLayoutLeaf(); + if( bNoFootnote && pLayLeaf && pLayLeaf->IsInFootnote() ) + { + do + { + pLayLeaf = pLayLeaf->GetNextLayoutLeaf(); + } while( pLayLeaf && pLayLeaf->IsInFootnote() ); + } + if( !IsAnLower( pLayLeaf) ) + return nullptr; + } while( pLayLeaf ); + return nullptr; +} + +bool SwLayoutFrame::ContainsDeleteForbiddenLayFrame() const +{ + if (IsDeleteForbidden()) + { + return true; + } + for (SwFrame const* pFrame = Lower(); pFrame; pFrame = pFrame->GetNext()) + { + if (!pFrame->IsLayoutFrame()) + { + continue; + } + SwLayoutFrame const*const pLay(static_cast<SwLayoutFrame const*>(pFrame)); + if (pLay->ContainsDeleteForbiddenLayFrame()) + { + return true; + } + } + return false; +} + +const SwFrame* SwFrame::GetLower() const +{ + return IsLayoutFrame() ? static_cast<const SwLayoutFrame*>(this)->Lower() : nullptr; +} + +SwFrame* SwFrame::GetLower() +{ + return IsLayoutFrame() ? static_cast<SwLayoutFrame*>(this)->Lower() : nullptr; +} + +SwContentFrame* SwFrame::FindPrevCnt( ) +{ + if ( GetPrev() && GetPrev()->IsContentFrame() ) + return static_cast<SwContentFrame*>(GetPrev()); + else + return FindPrevCnt_(); +} + +const SwContentFrame* SwFrame::FindPrevCnt() const +{ + if ( GetPrev() && GetPrev()->IsContentFrame() ) + return static_cast<const SwContentFrame*>(GetPrev()); + else + return const_cast<SwFrame*>(this)->FindPrevCnt_(); +} + +SwContentFrame *SwFrame::FindNextCnt( const bool _bInSameFootnote ) +{ + if ( mpNext && mpNext->IsContentFrame() ) + return static_cast<SwContentFrame*>(mpNext); + else + return FindNextCnt_( _bInSameFootnote ); +} + +const SwContentFrame *SwFrame::FindNextCnt( const bool _bInSameFootnote ) const +{ + if ( mpNext && mpNext->IsContentFrame() ) + return static_cast<SwContentFrame*>(mpNext); + else + return const_cast<SwFrame*>(this)->FindNextCnt_( _bInSameFootnote ); +} + +bool SwLayoutFrame::IsAnLower( const SwFrame *pAssumed ) const +{ + const SwFrame *pUp = pAssumed; + while ( pUp ) + { + if ( pUp == this ) + return true; + if ( pUp->IsFlyFrame() ) + pUp = static_cast<const SwFlyFrame*>(pUp)->GetAnchorFrame(); + else + pUp = pUp->GetUpper(); + } + return false; +} + +/** method to check relative position of layout frame to + a given layout frame. + + OD 08.11.2002 - refactoring of pseudo-local method <lcl_Apres(..)> in + <txtftn.cxx> for #104840#. + + @param _aCheckRefLayFrame + constant reference of an instance of class <SwLayoutFrame> which + is used as the reference for the relative position check. + + @return true, if <this> is positioned before the layout frame <p> +*/ +bool SwLayoutFrame::IsBefore( const SwLayoutFrame* _pCheckRefLayFrame ) const +{ + OSL_ENSURE( !IsRootFrame() , "<IsBefore> called at a <SwRootFrame>."); + OSL_ENSURE( !_pCheckRefLayFrame->IsRootFrame() , "<IsBefore> called with a <SwRootFrame>."); + + bool bReturn; + + // check, if on different pages + const SwPageFrame *pMyPage = FindPageFrame(); + const SwPageFrame *pCheckRefPage = _pCheckRefLayFrame->FindPageFrame(); + if( pMyPage != pCheckRefPage ) + { + // being on different page as check reference + bReturn = pMyPage->GetPhyPageNum() < pCheckRefPage->GetPhyPageNum(); + } + else + { + // being on same page as check reference + // --> search my supreme parent <pUp>, which doesn't contain check reference. + const SwLayoutFrame* pUp = this; + while ( pUp->GetUpper() && + !pUp->GetUpper()->IsAnLower( _pCheckRefLayFrame ) + ) + pUp = pUp->GetUpper(); + if( !pUp->GetUpper() ) + { + // can occur, if <this> is a fly frm + bReturn = false; + } + else + { + // travel through the next's of <pUp> and check if one of these + // contain the check reference. + const SwLayoutFrame* pUpNext = static_cast<const SwLayoutFrame*>(pUp->GetNext()); + while ( pUpNext && + !pUpNext->IsAnLower( _pCheckRefLayFrame ) ) + { + pUpNext = static_cast<const SwLayoutFrame*>(pUpNext->GetNext()); + } + bReturn = pUpNext != nullptr; + } + } + + return bReturn; +} + +// Local helper functions for GetNextLayoutLeaf + +static const SwFrame* lcl_FindLayoutFrame( const SwFrame* pFrame, bool bNext ) +{ + const SwFrame* pRet = nullptr; + if ( pFrame->IsFlyFrame() ) + pRet = bNext ? static_cast<const SwFlyFrame*>(pFrame)->GetNextLink() : static_cast<const SwFlyFrame*>(pFrame)->GetPrevLink(); + else + pRet = bNext ? pFrame->GetNext() : pFrame->GetPrev(); + + return pRet; +} + +static const SwFrame* lcl_GetLower( const SwFrame* pFrame, bool bFwd ) +{ + if ( !pFrame->IsLayoutFrame() ) + return nullptr; + + return bFwd ? + static_cast<const SwLayoutFrame*>(pFrame)->Lower() : + static_cast<const SwLayoutFrame*>(pFrame)->GetLastLower(); +} + +/** + * Finds the next layout leaf. This is a layout frame, which does not + * have a lower which is a LayoutFrame. That means, pLower can be 0 or a + * content frame. + * + * However, pLower may be a TabFrame + */ +const SwLayoutFrame *SwFrame::ImplGetNextLayoutLeaf( bool bFwd ) const +{ + const SwFrame *pFrame = this; + const SwLayoutFrame *pLayoutFrame = nullptr; + const SwFrame *p = nullptr; + bool bGoingUp = !bFwd; // false for forward, true for backward + do { + + bool bGoingFwdOrBwd = false; + + bool bGoingDown = !bGoingUp; + if (bGoingDown) + { + p = lcl_GetLower( pFrame, bFwd ); + bGoingDown = nullptr != p; + } + if ( !bGoingDown ) + { + // I cannot go down, because either I'm currently going up or + // because the is no lower. + // I'll try to go forward: + p = lcl_FindLayoutFrame( pFrame, bFwd ); + bGoingFwdOrBwd = nullptr != p; + if ( !bGoingFwdOrBwd ) + { + // I cannot go forward, because there is no next frame. + // I'll try to go up: + p = pFrame->GetUpper(); + bGoingUp = nullptr != p; + if ( !bGoingUp ) + { + // I cannot go up, because there is no upper frame. + return nullptr; + } + } + } + + // If I could not go down or forward, I'll have to go up + bGoingUp = !bGoingFwdOrBwd && !bGoingDown; + + pFrame = p; + p = lcl_GetLower( pFrame, true ); + + } while( ( p && !p->IsFlowFrame() ) || + pFrame == this || + nullptr == ( pLayoutFrame = pFrame->IsLayoutFrame() ? static_cast<const SwLayoutFrame*>(pFrame) : nullptr ) || + pLayoutFrame->IsAnLower( this ) ); + + return pLayoutFrame; +} + +/** + * Walk back inside the tree: grab the subordinate Frame if one exists and the + * last step was not moving up a level (this would lead to an infinite up/down + * loop!). With this we ensure that during walking back we search through all + * sub trees. If we walked downwards we have to go to the end of the chain first + * because we go backwards from the last Frame inside another Frame. Walking + * forward works the same. + * + * @warning fixes here may also need to be applied to the @{lcl_NextFrame} method above + */ +const SwContentFrame* SwContentFrame::ImplGetNextContentFrame( bool bFwd ) const +{ + const SwFrame *pFrame = this; + const SwContentFrame *pContentFrame = nullptr; + bool bGoingUp = false; + do { + const SwFrame *p = nullptr; + bool bGoingFwdOrBwd = false; + + bool bGoingDown = !bGoingUp; + if (bGoingDown) + { + p = lcl_GetLower( pFrame, true ) ; + bGoingDown = nullptr != p; + } + if ( !bGoingDown ) + { + p = lcl_FindLayoutFrame( pFrame, bFwd ); + bGoingFwdOrBwd = nullptr != p; + if ( !bGoingFwdOrBwd ) + { + p = pFrame->GetUpper(); + bGoingUp = nullptr != p; + if ( !bGoingUp ) + { + return nullptr; + } + } + } + + bGoingUp = !(bGoingFwdOrBwd || bGoingDown); + assert(p); + if (!bFwd && bGoingDown) + { + while ( p->GetNext() ) + p = p->GetNext(); + } + + pFrame = p; + } while ( nullptr == (pContentFrame = (pFrame->IsContentFrame() ? static_cast<const SwContentFrame*>(pFrame) : nullptr) )); + + return pContentFrame; +} + +SwPageFrame* SwFrame::ImplFindPageFrame() +{ + SwFrame *pRet = this; + while ( pRet && !pRet->IsPageFrame() ) + { + if ( pRet->GetUpper() ) + pRet = pRet->GetUpper(); + else if ( pRet->IsFlyFrame() ) + { + // #i28701# - use new method <GetPageFrame()> + const auto pFly(static_cast<SwFlyFrame*>(pRet)); + pRet = pFly->GetPageFrame(); + if (pRet == nullptr) + pRet = pFly->AnchorFrame(); + } + else + return nullptr; + } + return static_cast<SwPageFrame*>(pRet); +} + +SwFootnoteBossFrame* SwFrame::FindFootnoteBossFrame( bool bFootnotes ) +{ + SwFrame *pRet = this; + // Footnote bosses can't exist inside a table; also sections with columns + // don't contain footnote texts there + if( pRet->IsInTab() ) + pRet = pRet->FindTabFrame(); + + // tdf139336: put the footnotes into the page frame (instead of a column frame) + // to avoid maximizing the section to the full page.... if: + // - it is in a section + // - collect footnotes at section end (FootnoteAtEnd) is not set + // - columns are evenly distributed (=balanced) is not set + // Note 1: at page end, the footnotes have no multi-column-capability, + // so this fix is used only where there is better chance to help + // Note 2: If balanced is not set, there is a higher chance that the 1. column will reach + // the end of the page... if that happens the section will be maximized anyway. + // Note 3: The user will be able to easily choose the old layout (with multi-column footnotes) + // with this setting. + // similar case can be reached with a page break + FootnoteAtEnd setting + SwSectionFrame* pSectframe = pRet->FindSctFrame(); + bool bMoveToPageFrame = false; + // tdf146704: only if it is really a footnote, not an endnote. + // tdf54465: compatibility flag to make old odt files keep these full page sections. + if (bFootnotes && pSectframe + && pSectframe->GetFormat()->getIDocumentSettingAccess().get( + DocumentSettingId::FOOTNOTE_IN_COLUMN_TO_PAGEEND)) + { + SwSection* pSect = pSectframe->GetSection(); + if (pSect) { + bool bNoBalance = pSect->GetFormat()->GetBalancedColumns().GetValue(); + bool bFAtEnd = pSectframe->IsFootnoteAtEnd(); + bMoveToPageFrame = !bFAtEnd && !bNoBalance; + } + } + while (pRet + && ((!bMoveToPageFrame && !pRet->IsFootnoteBossFrame()) + || (bMoveToPageFrame && !pRet->IsPageFrame()))) + { + if ( pRet->GetUpper() ) + pRet = pRet->GetUpper(); + else if ( pRet->IsFlyFrame() ) + { + // #i28701# - use new method <GetPageFrame()> + if ( static_cast<SwFlyFrame*>(pRet)->GetPageFrame() ) + pRet = static_cast<SwFlyFrame*>(pRet)->GetPageFrame(); + else + pRet = static_cast<SwFlyFrame*>(pRet)->AnchorFrame(); + } + else + return nullptr; + } + if( bFootnotes && pRet && pRet->IsColumnFrame() && + !pRet->GetNext() && !pRet->GetPrev() ) + { + SwSectionFrame* pSct = pRet->FindSctFrame(); + OSL_ENSURE( pSct, "FindFootnoteBossFrame: Single column outside section?" ); + if( !pSct->IsFootnoteAtEnd() ) + return pSct->FindFootnoteBossFrame( true ); + } + return static_cast<SwFootnoteBossFrame*>(pRet); +} + +SwTabFrame* SwFrame::ImplFindTabFrame() +{ + SwFrame *pRet = this; + while ( !pRet->IsTabFrame() ) + { + pRet = pRet->GetUpper(); + if ( !pRet ) + return nullptr; + } + return static_cast<SwTabFrame*>(pRet); +} + +SwSectionFrame* SwFrame::ImplFindSctFrame() +{ + SwFrame *pRet = this; + while ( !pRet->IsSctFrame() ) + { + pRet = pRet->GetUpper(); + if ( !pRet ) + return nullptr; + } + return static_cast<SwSectionFrame*>(pRet); +} + +const SwBodyFrame* SwFrame::ImplFindBodyFrame() const +{ + const SwFrame *pRet = this; + while ( !pRet->IsBodyFrame() ) + { + pRet = pRet->GetUpper(); + if ( !pRet ) + return nullptr; + } + return static_cast<const SwBodyFrame*>(pRet); +} + +SwFootnoteFrame *SwFrame::ImplFindFootnoteFrame() +{ + SwFrame *pRet = this; + while ( !pRet->IsFootnoteFrame() ) + { + pRet = pRet->GetUpper(); + if ( !pRet ) + return nullptr; + } + return static_cast<SwFootnoteFrame*>(pRet); +} + +SwFlyFrame *SwFrame::ImplFindFlyFrame() +{ + SwFrame *pRet = this; + do + { + if ( pRet->IsFlyFrame() ) + return static_cast<SwFlyFrame*>(pRet); + else + pRet = pRet->GetUpper(); + } while ( pRet ); + return nullptr; +} + +SwFrame *SwFrame::FindColFrame() +{ + SwFrame *pFrame = this; + do + { pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsColumnFrame() ); + return pFrame; +} + +SwRowFrame *SwFrame::FindRowFrame() +{ + SwFrame *pFrame = this; + do + { pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsRowFrame() ); + return dynamic_cast< SwRowFrame* >( pFrame ); +} + +SwFrame* SwFrame::FindFooterOrHeader() +{ + SwFrame* pRet = this; + do + { + if (pRet->GetType() & FRM_HEADFOOT) //header and footer + return pRet; + else if ( pRet->GetUpper() ) + pRet = pRet->GetUpper(); + else if ( pRet->IsFlyFrame() ) + pRet = static_cast<SwFlyFrame*>(pRet)->AnchorFrame(); + else + return nullptr; + } while ( pRet ); + return pRet; +} + +const SwFootnoteFrame* SwFootnoteContFrame::FindFootNote() const +{ + const SwFootnoteFrame* pRet = static_cast<const SwFootnoteFrame*>(Lower()); + if( pRet && !pRet->GetAttr()->GetFootnote().IsEndNote() ) + return pRet; + return nullptr; +} + +const SwPageFrame* SwRootFrame::GetPageAtPos( const Point& rPt, const Size* pSize, bool bExtend ) const +{ + const SwPageFrame* pRet = nullptr; + + SwRect aRect; + if ( pSize ) + { + aRect.Pos() = rPt; + aRect.SSize( *pSize ); + } + + const SwFrame* pPage = Lower(); + + if ( !bExtend ) + { + if( !getFrameArea().Contains( rPt ) ) + return nullptr; + + // skip pages above point: + while( pPage && rPt.Y() > pPage->getFrameArea().Bottom() ) + pPage = pPage->GetNext(); + } + + OSL_ENSURE( GetPageNum() <= maPageRects.size(), "number of pages differs from page rect array size" ); + size_t nPageIdx = 0; + + while ( pPage && !pRet ) + { + const SwRect& rBoundRect = bExtend ? maPageRects[ nPageIdx++ ] : pPage->getFrameArea(); + + if ( (!pSize && rBoundRect.Contains(rPt)) || + (pSize && rBoundRect.Overlaps(aRect)) ) + { + pRet = static_cast<const SwPageFrame*>(pPage); + } + + pPage = pPage->GetNext(); + } + + return pRet; +} + +bool SwRootFrame::IsBetweenPages(const Point& rPt) const +{ + if (!getFrameArea().Contains(rPt)) + return false; + + // top visible page + const SwFrame* pPage = Lower(); + if (pPage == nullptr) + return false; + + // skip pages above point: + while (pPage && rPt.Y() > pPage->getFrameArea().Bottom()) + pPage = pPage->GetNext(); + + if (pPage && + rPt.X() >= pPage->getFrameArea().Left() && + rPt.X() <= pPage->getFrameArea().Right()) + { + // Trivial case when we're right in between. + if (!pPage->getFrameArea().Contains(rPt)) + return true; + + // In normal mode the gap is large enough and + // header/footer mouse interaction competes with + // handling hide-whitespace within them. + // In hide-whitespace, however, the gap is too small + // for convenience and there are no headers/footers. + const SwViewShell *pSh = GetCurrShell(); + if (pSh && pSh->GetViewOptions()->IsWhitespaceHidden()) + { + constexpr SwTwips constMargin = o3tl::convert(tools::Long(2), o3tl::Length::mm, o3tl::Length::twip); + + // If we are really close to the bottom or top of a page. + const auto toEdge = std::min(std::abs(pPage->getFrameArea().Top() - rPt.Y()), + std::abs(pPage->getFrameArea().Bottom() - rPt.Y())); + return toEdge <= constMargin; + } + } + + return false; +} + +const SvxFormatBreakItem& SwFrame::GetBreakItem() const +{ + return GetAttrSet()->GetBreak(); +} + +const SwFormatPageDesc& SwFrame::GetPageDescItem() const +{ + return GetAttrSet()->GetPageDesc(); +} + +const SvxFormatBreakItem& SwTextFrame::GetBreakItem() const +{ + return GetTextNodeFirst()->GetSwAttrSet().GetBreak(); +} + +const SwFormatPageDesc& SwTextFrame::GetPageDescItem() const +{ + return GetTextNodeFirst()->GetSwAttrSet().GetPageDesc(); +} + +const SwAttrSet* SwFrame::GetAttrSet() const +{ + if (IsTextFrame()) + { + return &static_cast<const SwTextFrame*>(this)->GetTextNodeForParaProps()->GetSwAttrSet(); + } + else if (IsNoTextFrame()) + { + return &static_cast<const SwNoTextFrame*>(this)->GetNode()->GetSwAttrSet(); + } + else + { + assert(IsLayoutFrame()); + return &static_cast<const SwLayoutFrame*>(this)->GetFormat()->GetAttrSet(); + } +} + +drawinglayer::attribute::SdrAllFillAttributesHelperPtr SwFrame::getSdrAllFillAttributesHelper() const +{ + if (IsTextFrame()) + { + return static_cast<const SwTextFrame*>(this)->GetTextNodeForParaProps()->getSdrAllFillAttributesHelper(); + } + else if (IsNoTextFrame()) + { + return static_cast<const SwNoTextFrame*>(this)->GetNode()->getSdrAllFillAttributesHelper(); + } + else + { + return static_cast< const SwLayoutFrame* >(this)->GetFormat()->getSdrAllFillAttributesHelper(); + } +} + +bool SwFrame::supportsFullDrawingLayerFillAttributeSet() const +{ + if (IsContentFrame()) + { + return true; + } + else + { + return static_cast< const SwLayoutFrame* >(this)->GetFormat()->supportsFullDrawingLayerFillAttributeSet(); + } +} + +/* + * SwFrame::FindNext_(), FindPrev_(), InvalidateNextPos() + * FindNextCnt_() visits tables and sections and only returns SwContentFrames. + * + * Description Invalidates the position of the next frame. + * This is the direct successor or in case of ContentFrames the next + * ContentFrame which sits in the same flow as I do: + * - body, + * - footnote, + * - in headers/footers the notification only needs to be forwarded + * inside the section + * - same for Flys + * - Contents in tabs remain only inside their cell + * - in principle tables behave exactly like the Contents + * - sections also + */ +// This helper function is an equivalent to the ImplGetNextContentFrame() method, +// besides ContentFrames this function also returns TabFrames and SectionFrames. +static SwFrame* lcl_NextFrame( SwFrame* pFrame ) +{ + SwFrame *pRet = nullptr; + bool bGoingUp = false; + do { + SwFrame *p = nullptr; + + bool bGoingFwd = false; + bool bGoingDown = !bGoingUp && pFrame->IsLayoutFrame(); + if (bGoingDown) + { + p = static_cast<SwLayoutFrame*>(pFrame)->Lower(); + bGoingDown = nullptr != p; + } + if( !bGoingDown ) + { + p = pFrame->IsFlyFrame() ? static_cast<SwFlyFrame*>(pFrame)->GetNextLink() : pFrame->GetNext(); + bGoingFwd = nullptr != p; + if ( !bGoingFwd ) + { + p = pFrame->GetUpper(); + bGoingUp = nullptr != p; + if ( !bGoingUp ) + { + return nullptr; + } + } + } + bGoingUp = !(bGoingFwd || bGoingDown); + pFrame = p; + } while ( nullptr == (pRet = ( ( pFrame->IsContentFrame() || ( !bGoingUp && + ( pFrame->IsTabFrame() || pFrame->IsSctFrame() ) ) )? pFrame : nullptr ) ) ); + return pRet; +} + +SwFrame *SwFrame::FindNext_() +{ + bool bIgnoreTab = false; + SwFrame *pThis = this; + + if ( IsTabFrame() ) + { + //The last Content of the table gets picked up and his follower is + //returned. To be able to deactivate the special case for tables + //(see below) bIgnoreTab will be set. + if ( static_cast<SwTabFrame*>(this)->GetFollow() ) + return static_cast<SwTabFrame*>(this)->GetFollow(); + + pThis = static_cast<SwTabFrame*>(this)->FindLastContentOrTable(); + if ( !pThis ) + pThis = this; + bIgnoreTab = true; + } + else if ( IsSctFrame() ) + { + //The last Content of the section gets picked and his follower is returned. + if ( static_cast<SwSectionFrame*>(this)->GetFollow() ) + return static_cast<SwSectionFrame*>(this)->GetFollow(); + + pThis = static_cast<SwSectionFrame*>(this)->FindLastContent(); + if ( !pThis ) + pThis = this; + } + else if ( IsContentFrame() ) + { + if( static_cast<SwContentFrame*>(this)->GetFollow() ) + return static_cast<SwContentFrame*>(this)->GetFollow(); + } + else if ( IsRowFrame() ) + { + SwFrame* pMyUpper = GetUpper(); + if ( pMyUpper->IsTabFrame() && static_cast<SwTabFrame*>(pMyUpper)->GetFollow() ) + return static_cast<SwTabFrame*>(pMyUpper)->GetFollow()->GetLower(); + else return nullptr; + } + else + return nullptr; + + SwFrame* pRet = nullptr; + const bool bFootnote = pThis->IsInFootnote(); + if ( !bIgnoreTab && pThis->IsInTab() ) + { + SwLayoutFrame *pUp = pThis->GetUpper(); + while (pUp && !pUp->IsCellFrame()) + pUp = pUp->GetUpper(); + assert(pUp && "Content flag says it's in table but it's not in cell."); + SwFrame* pNxt = pUp ? static_cast<SwCellFrame*>(pUp)->GetFollowCell() : nullptr; + if ( pNxt ) + pNxt = static_cast<SwCellFrame*>(pNxt)->ContainsContent(); + if ( !pNxt ) + { + pNxt = lcl_NextFrame( pThis ); + if (pUp && pUp->IsAnLower(pNxt)) + pRet = pNxt; + } + else + pRet = pNxt; + } + else + { + const bool bBody = pThis->IsInDocBody(); + SwFrame *pNxtCnt = lcl_NextFrame( pThis ); + if ( pNxtCnt ) + { + if ( bBody || bFootnote ) + { + while ( pNxtCnt ) + { + // OD 02.04.2003 #108446# - check for endnote, only if found + // next content isn't contained in a section, that collect its + // endnotes at its end. + bool bEndn = IsInSct() && !IsSctFrame() && + ( !pNxtCnt->IsInSct() || + !pNxtCnt->FindSctFrame()->IsEndnAtEnd() + ); + if ( ( bBody && pNxtCnt->IsInDocBody() ) || + ( pNxtCnt->IsInFootnote() && + ( bFootnote || + ( bEndn && pNxtCnt->FindFootnoteFrame()->GetAttr()->GetFootnote().IsEndNote() ) + ) + ) + ) + { + pRet = pNxtCnt->IsInTab() ? pNxtCnt->FindTabFrame() + : pNxtCnt; + break; + } + pNxtCnt = lcl_NextFrame( pNxtCnt ); + } + } + else if ( pThis->IsInFly() ) + { + pRet = pNxtCnt->IsInTab() ? pNxtCnt->FindTabFrame() + : pNxtCnt; + } + else //footer-/or header section + { + const SwFrame *pUp = pThis->GetUpper(); + const SwFrame *pCntUp = pNxtCnt->GetUpper(); + while ( pUp && pUp->GetUpper() && + !pUp->IsHeaderFrame() && !pUp->IsFooterFrame() ) + pUp = pUp->GetUpper(); + while ( pCntUp && pCntUp->GetUpper() && + !pCntUp->IsHeaderFrame() && !pCntUp->IsFooterFrame() ) + pCntUp = pCntUp->GetUpper(); + if ( pCntUp == pUp ) + { + pRet = pNxtCnt->IsInTab() ? pNxtCnt->FindTabFrame() + : pNxtCnt; + } + } + } + } + if( pRet && pRet->IsInSct() ) + { + SwSectionFrame* pSct = pRet->FindSctFrame(); + //Footnotes in frames with columns must not return the section which + //contains the footnote + if( !pSct->IsAnLower( this ) && + (!bFootnote || pSct->IsInFootnote() ) ) + return pSct; + } + return pRet; +} + +// #i27138# - add parameter <_bInSameFootnote> +SwContentFrame *SwFrame::FindNextCnt_( const bool _bInSameFootnote ) +{ + SwFrame *pThis = this; + + if ( IsTabFrame() ) + { + if ( static_cast<SwTabFrame*>(this)->GetFollow() ) + { + pThis = static_cast<SwTabFrame*>(this)->GetFollow()->ContainsContent(); + if( pThis ) + return static_cast<SwContentFrame*>(pThis); + } + pThis = static_cast<SwTabFrame*>(this)->FindLastContentOrTable(); + if ( !pThis ) + return nullptr; + } + else if ( IsSctFrame() ) + { + if ( static_cast<SwSectionFrame*>(this)->GetFollow() ) + { + pThis = static_cast<SwSectionFrame*>(this)->GetFollow()->ContainsContent(); + if( pThis ) + return static_cast<SwContentFrame*>(pThis); + } + pThis = static_cast<SwSectionFrame*>(this)->FindLastContent(); + if ( !pThis ) + return nullptr; + } + else if ( IsContentFrame() && static_cast<SwContentFrame*>(this)->GetFollow() ) + return static_cast<SwContentFrame*>(this)->GetFollow(); + + if ( pThis->IsContentFrame() ) + { + const bool bBody = pThis->IsInDocBody(); + const bool bFootnote = pThis->IsInFootnote(); + SwContentFrame *pNxtCnt = static_cast<SwContentFrame*>(pThis)->GetNextContentFrame(); + if ( pNxtCnt ) + { + // #i27138# + if ( bBody || ( bFootnote && !_bInSameFootnote ) ) + { + // handling for environments 'footnotes' and 'document body frames': + while ( pNxtCnt ) + { + if ( (bBody && pNxtCnt->IsInDocBody()) || + (bFootnote && pNxtCnt->IsInFootnote()) ) + return pNxtCnt; + pNxtCnt = pNxtCnt->GetNextContentFrame(); + } + } + // #i27138# + else if ( bFootnote && _bInSameFootnote ) + { + // handling for environments 'each footnote': + // Assure that found next content frame belongs to the same footnotes + const SwFootnoteFrame* pFootnoteFrameOfNext( pNxtCnt->FindFootnoteFrame() ); + const SwFootnoteFrame* pFootnoteFrameOfCurr( pThis->FindFootnoteFrame() ); + OSL_ENSURE( pFootnoteFrameOfCurr, + "<SwFrame::FindNextCnt_() - unknown layout situation: current frame has to have an upper footnote frame." ); + if ( pFootnoteFrameOfNext == pFootnoteFrameOfCurr ) + { + return pNxtCnt; + } + else if ( pFootnoteFrameOfCurr->GetFollow() ) + { + // next content frame has to be the first content frame + // in the follow footnote, which contains a content frame. + SwFootnoteFrame* pFollowFootnoteFrameOfCurr( + const_cast<SwFootnoteFrame*>(pFootnoteFrameOfCurr) ); + pNxtCnt = nullptr; + do { + pFollowFootnoteFrameOfCurr = pFollowFootnoteFrameOfCurr->GetFollow(); + pNxtCnt = pFollowFootnoteFrameOfCurr->ContainsContent(); + } while ( !pNxtCnt && pFollowFootnoteFrameOfCurr->GetFollow() ); + return pNxtCnt; + } + else + { + // current content frame is the last content frame in the + // footnote - no next content frame exists. + return nullptr; + } + } + else if ( pThis->IsInFly() ) + // handling for environments 'unlinked fly frame' and + // 'group of linked fly frames': + return pNxtCnt; + else + { + // handling for environments 'page header' and 'page footer': + const SwFrame *pUp = pThis->GetUpper(); + const SwFrame *pCntUp = pNxtCnt->GetUpper(); + while ( pUp && pUp->GetUpper() && + !pUp->IsHeaderFrame() && !pUp->IsFooterFrame() ) + pUp = pUp->GetUpper(); + while ( pCntUp && pCntUp->GetUpper() && + !pCntUp->IsHeaderFrame() && !pCntUp->IsFooterFrame() ) + pCntUp = pCntUp->GetUpper(); + if ( pCntUp == pUp ) + return pNxtCnt; + } + } + } + return nullptr; +} + +/** method to determine previous content frame in the same environment + for a flow frame (content frame, table frame, section frame) + + OD 2005-11-30 #i27138# +*/ +SwContentFrame* SwFrame::FindPrevCnt_() +{ + if ( !IsFlowFrame() ) + { + // nothing to do, if current frame isn't a flow frame. + return nullptr; + } + + SwContentFrame* pPrevContentFrame( nullptr ); + + // Because method <SwContentFrame::GetPrevContentFrame()> is used to travel + // through the layout, a content frame, at which the travel starts, is needed. + SwContentFrame* pCurrContentFrame = dynamic_cast<SwContentFrame*>(this); + + // perform shortcut, if current frame is a follow, and + // determine <pCurrContentFrame>, if current frame is a table or section frame + if ( pCurrContentFrame && pCurrContentFrame->IsFollow() ) + { + // previous content frame is its master content frame + pPrevContentFrame = pCurrContentFrame->FindMaster(); + } + else if ( IsTabFrame() ) + { + SwTabFrame* pTabFrame( static_cast<SwTabFrame*>(this) ); + if ( pTabFrame->IsFollow() ) + { + // previous content frame is the last content of its master table frame + pPrevContentFrame = pTabFrame->FindMaster()->FindLastContent(); + } + else + { + // start content frame for the search is the first content frame of + // the table frame. + pCurrContentFrame = pTabFrame->ContainsContent(); + } + } + else if ( IsSctFrame() ) + { + SwSectionFrame* pSectFrame( static_cast<SwSectionFrame*>(this) ); + if ( pSectFrame->IsFollow() ) + { + // previous content frame is the last content of its master section frame + pPrevContentFrame = pSectFrame->FindMaster()->FindLastContent(); + } + else + { + // start content frame for the search is the first content frame of + // the section frame. + pCurrContentFrame = pSectFrame->ContainsContent(); + } + } + + // search for next content frame, depending on the environment, in which + // the current frame is in. + if ( !pPrevContentFrame && pCurrContentFrame ) + { + pPrevContentFrame = pCurrContentFrame->GetPrevContentFrame(); + if ( pPrevContentFrame ) + { + if ( pCurrContentFrame->IsInFly() ) + { + // handling for environments 'unlinked fly frame' and + // 'group of linked fly frames': + // Nothing to do, <pPrevContentFrame> is the one + } + else + { + const bool bInDocBody = pCurrContentFrame->IsInDocBody(); + const bool bInFootnote = pCurrContentFrame->IsInFootnote(); + if ( bInDocBody ) + { + // handling for environments 'footnotes' and 'document body frames': + // Assure that found previous frame is also in one of these + // environments. Otherwise, travel further + while ( pPrevContentFrame ) + { + if ( ( bInDocBody && pPrevContentFrame->IsInDocBody() ) || + ( bInFootnote && pPrevContentFrame->IsInFootnote() ) ) + { + break; + } + pPrevContentFrame = pPrevContentFrame->GetPrevContentFrame(); + } + } + else if ( bInFootnote ) + { + // handling for environments 'each footnote': + // Assure that found next content frame belongs to the same footnotes + const SwFootnoteFrame* pFootnoteFrameOfPrev( pPrevContentFrame->FindFootnoteFrame() ); + const SwFootnoteFrame* pFootnoteFrameOfCurr( pCurrContentFrame->FindFootnoteFrame() ); + if ( pFootnoteFrameOfPrev != pFootnoteFrameOfCurr ) + { + if ( pFootnoteFrameOfCurr->GetMaster() ) + { + SwFootnoteFrame* pMasterFootnoteFrameOfCurr( + const_cast<SwFootnoteFrame*>(pFootnoteFrameOfCurr) ); + pPrevContentFrame = nullptr; + // correct wrong loop-condition + do { + pMasterFootnoteFrameOfCurr = pMasterFootnoteFrameOfCurr->GetMaster(); + pPrevContentFrame = pMasterFootnoteFrameOfCurr->FindLastContent(); + } while ( !pPrevContentFrame && + pMasterFootnoteFrameOfCurr->GetMaster() ); + } + else + { + // current content frame is the first content in the + // footnote - no previous content exists. + pPrevContentFrame = nullptr; + } + } + } + else + { + // handling for environments 'page header' and 'page footer': + // Assure that found previous frame is also in the same + // page header respectively page footer as <pCurrContentFrame> + // Note: At this point it's clear that <pCurrContentFrame> has + // to be inside a page header or page footer and that + // neither <pCurrContentFrame> nor <pPrevContentFrame> are + // inside a fly frame. + // Thus, method <FindFooterOrHeader()> can be used. + OSL_ENSURE( pCurrContentFrame->FindFooterOrHeader(), + "<SwFrame::FindPrevCnt_()> - unknown layout situation: current frame should be in page header or page footer" ); + OSL_ENSURE( !pPrevContentFrame->IsInFly(), + "<SwFrame::FindPrevCnt_()> - unknown layout situation: found previous frame should *not* be inside a fly frame." ); + if ( pPrevContentFrame->FindFooterOrHeader() != + pCurrContentFrame->FindFooterOrHeader() ) + { + pPrevContentFrame = nullptr; + } + } + } + } + } + + return pPrevContentFrame; +} + +SwFrame *SwFrame::FindPrev_() +{ + bool bIgnoreTab = false; + SwFrame *pThis = this; + + if ( IsTabFrame() ) + { + //The first Content of the table gets picked up and his predecessor is + //returned. To be able to deactivate the special case for tables + //(see below) bIgnoreTab will be set. + if ( static_cast<SwTabFrame*>(this)->IsFollow() ) + return static_cast<SwTabFrame*>(this)->FindMaster(); + else + pThis = static_cast<SwTabFrame*>(this)->ContainsContent(); + bIgnoreTab = true; + } + + if ( pThis && pThis->IsContentFrame() ) + { + SwContentFrame *pPrvCnt = static_cast<SwContentFrame*>(pThis)->GetPrevContentFrame(); + if( !pPrvCnt ) + return nullptr; + if ( !bIgnoreTab && pThis->IsInTab() ) + { + SwLayoutFrame *pUp = pThis->GetUpper(); + while (pUp && !pUp->IsCellFrame()) + pUp = pUp->GetUpper(); + assert(pUp && "Content flag says it's in table but it's not in cell."); + if (pUp && pUp->IsAnLower(pPrvCnt)) + return pPrvCnt; + } + else + { + SwFrame* pRet; + const bool bBody = pThis->IsInDocBody(); + const bool bFootnote = !bBody && pThis->IsInFootnote(); + if ( bBody || bFootnote ) + { + while ( pPrvCnt ) + { + if ( (bBody && pPrvCnt->IsInDocBody()) || + (bFootnote && pPrvCnt->IsInFootnote()) ) + { + pRet = pPrvCnt->IsInTab() ? pPrvCnt->FindTabFrame() + : static_cast<SwFrame*>(pPrvCnt); + return pRet; + } + pPrvCnt = pPrvCnt->GetPrevContentFrame(); + } + } + else if ( pThis->IsInFly() ) + { + pRet = pPrvCnt->IsInTab() ? pPrvCnt->FindTabFrame() + : static_cast<SwFrame*>(pPrvCnt); + return pRet; + } + else // footer or header or Fly + { + const SwFrame *pUp = pThis->GetUpper(); + const SwFrame *pCntUp = pPrvCnt->GetUpper(); + while ( pUp && pUp->GetUpper() && + !pUp->IsHeaderFrame() && !pUp->IsFooterFrame() ) + pUp = pUp->GetUpper(); + while ( pCntUp && pCntUp->GetUpper() ) + pCntUp = pCntUp->GetUpper(); + if ( pCntUp == pUp ) + { + pRet = pPrvCnt->IsInTab() ? pPrvCnt->FindTabFrame() + : static_cast<SwFrame*>(pPrvCnt); + return pRet; + } + } + } + } + return nullptr; +} + +void SwFrame::ImplInvalidateNextPos( bool bNoFootnote ) +{ + SwFrame *pFrame = FindNext_(); + if ( nullptr == pFrame ) + return; + + if( pFrame->IsSctFrame() ) + { + while( pFrame && pFrame->IsSctFrame() ) + { + if( static_cast<SwSectionFrame*>(pFrame)->GetSection() ) + { + SwFrame* pTmp = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pTmp ) + pTmp->InvalidatePos(); + else if( !bNoFootnote ) + static_cast<SwSectionFrame*>(pFrame)->InvalidateFootnotePos(); + if( !IsInSct() || FindSctFrame()->GetFollow() != pFrame ) + pFrame->InvalidatePos(); + return; + } + pFrame = pFrame->FindNext(); + } + if( pFrame ) + { + if ( pFrame->IsSctFrame()) + { + // We need to invalidate the section's content so it gets + // the chance to flow to a different page. + SwFrame* pTmp = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pTmp ) + pTmp->InvalidatePos(); + if( !IsInSct() || FindSctFrame()->GetFollow() != pFrame ) + pFrame->InvalidatePos(); + } + else + pFrame->InvalidatePos(); + } + } + else + pFrame->InvalidatePos(); +} + +/** method to invalidate printing area of next frame + + OD 09.01.2004 #i11859# + + FME 2004-04-19 #i27145# Moved function from SwTextFrame to SwFrame +*/ +void SwFrame::InvalidateNextPrtArea() +{ + // determine next frame + SwFrame* pNextFrame = FindNext(); + // skip empty section frames and hidden text frames + { + while ( pNextFrame && + ( ( pNextFrame->IsSctFrame() && + !static_cast<SwSectionFrame*>(pNextFrame)->GetSection() ) || + ( pNextFrame->IsTextFrame() && + static_cast<SwTextFrame*>(pNextFrame)->IsHiddenNow() ) ) ) + { + pNextFrame = pNextFrame->FindNext(); + } + } + + // Invalidate printing area of found next frame + if ( !pNextFrame ) + return; + + if ( pNextFrame->IsSctFrame() ) + { + // Invalidate printing area of found section frame, if + // (1) this text frame isn't in a section OR + // (2) found section frame isn't a follow of the section frame this + // text frame is in. + if ( !IsInSct() || FindSctFrame()->GetFollow() != pNextFrame ) + { + pNextFrame->InvalidatePrt(); + } + + // Invalidate printing area of first content in found section. + SwFrame* pFstContentOfSctFrame = + static_cast<SwSectionFrame*>(pNextFrame)->ContainsAny(); + if ( pFstContentOfSctFrame ) + { + pFstContentOfSctFrame->InvalidatePrt(); + } + } + else + { + pNextFrame->InvalidatePrt(); + } +} + +/// @returns true if the frame _directly_ sits in a section +/// but not if it sits in a table which itself sits in a section. +static bool lcl_IsInSectionDirectly( const SwFrame *pUp ) +{ + bool bSeenColumn = false; + + while( pUp ) + { + if( pUp->IsColumnFrame() ) + bSeenColumn = true; + else if( pUp->IsSctFrame() ) + { + auto pSection = static_cast<const SwSectionFrame*>(pUp); + const SwFrame* pHeaderFooter = pSection->FindFooterOrHeader(); + // When the section frame is not in header/footer: + // Allow move of frame in case our only column is not growable. + // Also allow if there is a previous section frame (to move back). + bool bAllowOutsideHeaderFooter = !pSection->Growable() || pSection->GetPrecede(); + return bSeenColumn || (!pHeaderFooter && bAllowOutsideHeaderFooter); + } + else if( pUp->IsTabFrame() ) + return false; + pUp = pUp->GetUpper(); + } + return false; +} + +/** determine, if frame is moveable in given environment + + OD 08.08.2003 #110978# + method replaced 'old' method <sal_Bool IsMoveable() const>. + Determines, if frame is moveable in given environment. if no environment + is given (parameter _pLayoutFrame == 0), the movability in the actual + environment (<GetUpper()) is checked. +*/ +bool SwFrame::IsMoveable( const SwLayoutFrame* _pLayoutFrame ) const +{ + bool bRetVal = false; + + if ( !_pLayoutFrame ) + { + _pLayoutFrame = GetUpper(); + } + + if ( _pLayoutFrame && IsFlowFrame() ) + { + if ( _pLayoutFrame->IsInSct() && lcl_IsInSectionDirectly( _pLayoutFrame ) ) + { + bRetVal = true; + } + else if ( _pLayoutFrame->IsInFly() || + _pLayoutFrame->IsInDocBody() || + _pLayoutFrame->IsInFootnote() ) + { + // If IsMovable() is called before a MoveFwd() the method + // may return false if there is no NextCellLeaf. If + // IsMovable() is called before a MoveBwd() the method may + // return false if there is no PrevCellLeaf. + if ( _pLayoutFrame->IsInTab() && !IsTabFrame() && + ( !IsContentFrame() || (!const_cast<SwFrame*>(this)->GetNextCellLeaf() + && !const_cast<SwFrame*>(this)->GetPrevCellLeaf()) ) + ) + { + bRetVal = false; + } + else + { + if ( _pLayoutFrame->IsInFly() ) + { + // if fly frame has a follow (next linked fly frame), + // frame is moveable. + if ( const_cast<SwLayoutFrame*>(_pLayoutFrame)->FindFlyFrame()->GetNextLink() ) + { + bRetVal = true; + } + else + { + // if environment is columned, frame is moveable, if + // it isn't in last column. + // search for column frame + const SwFrame* pCol = _pLayoutFrame; + while ( pCol && !pCol->IsColumnFrame() ) + { + pCol = pCol->GetUpper(); + } + // frame is moveable, if found column frame isn't last one. + if ( pCol && pCol->GetNext() ) + { + bRetVal = true; + } + } + } + else if (!(_pLayoutFrame->IsInFootnote() && (IsTabFrame() || IsInTab()))) + { + bRetVal = true; + } + } + } + } + + return bRetVal; +} + +void SwFrame::SetInfFlags() +{ + if ( !IsFlyFrame() && !GetUpper() ) //not yet pasted, no information available + return; + + mbInfInvalid = mbInfBody = mbInfTab = mbInfFly = mbInfFootnote = mbInfSct = false; + + SwFrame *pFrame = this; + if( IsFootnoteContFrame() ) + mbInfFootnote = true; + do + { + // mbInfBody is only set in the page body, but not in the column body + if ( pFrame->IsBodyFrame() && !mbInfFootnote && pFrame->GetUpper() + && pFrame->GetUpper()->IsPageFrame() ) + mbInfBody = true; + else if ( pFrame->IsTabFrame() || pFrame->IsCellFrame() ) + { + mbInfTab = true; + } + else if ( pFrame->IsFlyFrame() ) + mbInfFly = true; + else if ( pFrame->IsSctFrame() ) + mbInfSct = true; + else if ( pFrame->IsFootnoteFrame() ) + mbInfFootnote = true; + + pFrame = pFrame->GetUpper(); + + } while ( pFrame && !pFrame->IsPageFrame() ); //there is nothing above the page +} + +/** Updates the vertical or the righttoleft-flags. + * + * If the property is derived, it's from the upper or (for fly frames) from + * the anchor. Otherwise we've to call a virtual method to check the property. + */ +void SwFrame::SetDirFlags( bool bVert ) +{ + if( bVert ) + { + // OD 2004-01-21 #114969# - if derived, valid vertical flag only if + // vertical flag of upper/anchor is valid. + if( mbDerivedVert ) + { + const SwFrame* pAsk = IsFlyFrame() ? + static_cast<SwFlyFrame*>(this)->GetAnchorFrame() : GetUpper(); + + OSL_ENSURE( pAsk != this, "Autsch! Stack overflow is about to happen" ); + + if( pAsk ) + { + mbVertical = pAsk->IsVertical(); + mbVertLR = pAsk->IsVertLR(); + mbVertLRBT = pAsk->IsVertLRBT(); + + if ( !pAsk->mbInvalidVert ) + mbInvalidVert = false; + + if ( IsCellFrame() ) + { + SwCellFrame* pPrv = static_cast<SwCellFrame*>(this)->GetPreviousCell(); + if ( pPrv && !mbVertical && pPrv->IsVertical() ) + { + mbVertical = pPrv->IsVertical(); + mbVertLR = pPrv->IsVertLR(); + mbVertLRBT = pPrv->IsVertLRBT(); + } + } + } + } + else + CheckDirection( bVert ); + } + else + { + bool bInv = false; + if( !mbDerivedR2L ) // CheckDirection is able to set bDerivedR2L! + CheckDirection( bVert ); + if( mbDerivedR2L ) + { + const SwFrame* pAsk = IsFlyFrame() ? + static_cast<SwFlyFrame*>(this)->GetAnchorFrame() : GetUpper(); + + OSL_ENSURE( pAsk != this, "Oops! Stack overflow is about to happen" ); + + if( pAsk ) + mbRightToLeft = pAsk->IsRightToLeft(); + if( !pAsk || pAsk->mbInvalidR2L ) + bInv = mbInvalidR2L; + } + mbInvalidR2L = bInv; + } +} + +SwLayoutFrame* SwFrame::GetNextCellLeaf() +{ + SwFrame* pTmpFrame = this; + while (pTmpFrame && !pTmpFrame->IsCellFrame()) + pTmpFrame = pTmpFrame->GetUpper(); + + SAL_WARN_IF(!pTmpFrame, "sw.core", "SwFrame::GetNextCellLeaf() without cell"); + return pTmpFrame ? static_cast<SwCellFrame*>(pTmpFrame)->GetFollowCell() : nullptr; +} + +SwLayoutFrame* SwFrame::GetPrevCellLeaf() +{ + SwFrame* pTmpFrame = this; + while (pTmpFrame && !pTmpFrame->IsCellFrame()) + pTmpFrame = pTmpFrame->GetUpper(); + + SAL_WARN_IF(!pTmpFrame, "sw.core", "SwFrame::GetNextPreviousLeaf() without cell"); + return pTmpFrame ? static_cast<SwCellFrame*>(pTmpFrame)->GetPreviousCell() : nullptr; +} + +static SwCellFrame* lcl_FindCorrespondingCellFrame( const SwRowFrame& rOrigRow, + const SwCellFrame& rOrigCell, + const SwRowFrame& rCorrRow, + bool bInFollow ) +{ + SwCellFrame* pRet = nullptr; + const SwCellFrame* pCell = static_cast<const SwCellFrame*>(rOrigRow.Lower()); + SwCellFrame* pCorrCell = const_cast<SwCellFrame*>(static_cast<const SwCellFrame*>(rCorrRow.Lower())); + + while ( pCell != &rOrigCell && !pCell->IsAnLower( &rOrigCell ) ) + { + pCell = static_cast<const SwCellFrame*>(pCell->GetNext()); + pCorrCell = static_cast<SwCellFrame*>(pCorrCell->GetNext()); + } + + assert(pCell && pCorrCell && "lcl_FindCorrespondingCellFrame does not work"); + + if ( pCell != &rOrigCell ) + { + // rOrigCell must be a lower of pCell. We need to recurse into the rows: + assert(pCell->Lower() && pCell->Lower()->IsRowFrame() && + "lcl_FindCorrespondingCellFrame does not work"); + + const SwRowFrame* pRow = static_cast<const SwRowFrame*>(pCell->Lower()); + while ( !pRow->IsAnLower( &rOrigCell ) ) + pRow = static_cast<const SwRowFrame*>(pRow->GetNext()); + + SwRowFrame* pCorrRow = nullptr; + if ( bInFollow ) + pCorrRow = pRow->GetFollowRow(); + else + { + SwRowFrame* pTmpRow = static_cast<SwRowFrame*>(pCorrCell->GetLastLower()); + + if ( pTmpRow && pTmpRow->GetFollowRow() == pRow ) + pCorrRow = pTmpRow; + } + + if ( pCorrRow ) + pRet = lcl_FindCorrespondingCellFrame( *pRow, rOrigCell, *pCorrRow, bInFollow ); + } + else + pRet = pCorrCell; + + return pRet; +} + +// VERSION OF GetFollowCell() that assumes that we always have a follow flow line: +SwCellFrame* SwCellFrame::GetFollowCell() const +{ + SwCellFrame* pRet = nullptr; + + // NEW TABLES + // Covered cells do not have follow cells! + const tools::Long nRowSpan = GetLayoutRowSpan(); + if ( nRowSpan < 1 ) + return nullptr; + + // find most upper row frame + const SwFrame* pRow = GetUpper(); + + while (pRow && (!pRow->IsRowFrame() || !pRow->GetUpper()->IsTabFrame())) + pRow = pRow->GetUpper(); + + if (!pRow) + return nullptr; + + const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pRow->GetUpper()); + if (!pTabFrame || !pTabFrame->GetFollow() || !pTabFrame->HasFollowFlowLine()) + return nullptr; + + const SwCellFrame* pThisCell = this; + + // Get last cell of the current table frame that belongs to the rowspan: + if ( nRowSpan > 1 ) + { + // optimization: Will end of row span be in last row or exceed row? + tools::Long nMax = 0; + while ( pRow->GetNext() && ++nMax < nRowSpan ) + pRow = pRow->GetNext(); + + if ( !pRow->GetNext() ) + { + pThisCell = &pThisCell->FindStartEndOfRowSpanCell( false ); + pRow = pThisCell->GetUpper(); + } + } + + const SwRowFrame* pFollowRow = nullptr; + if ( !pRow->GetNext() && + nullptr != ( pFollowRow = pRow->IsInSplitTableRow() ) && + ( !pFollowRow->IsRowSpanLine() || nRowSpan > 1 ) ) + pRet = lcl_FindCorrespondingCellFrame( *static_cast<const SwRowFrame*>(pRow), *pThisCell, *pFollowRow, true ); + + return pRet; +} + +// VERSION OF GetPreviousCell() THAT ASSUMES THAT WE ALWAYS HAVE A FFL +SwCellFrame* SwCellFrame::GetPreviousCell() const +{ + SwCellFrame* pRet = nullptr; + + // NEW TABLES + // Covered cells do not have previous cells! + if ( GetLayoutRowSpan() < 1 ) + return nullptr; + + // find most upper row frame + const SwFrame* pRow = GetUpper(); + while( !pRow->IsRowFrame() || (pRow->GetUpper() && !pRow->GetUpper()->IsTabFrame()) ) + pRow = pRow->GetUpper(); + + OSL_ENSURE( pRow->GetUpper() && pRow->GetUpper()->IsTabFrame(), "GetPreviousCell without Table" ); + + const SwTabFrame* pTab = static_cast<const SwTabFrame*>(pRow->GetUpper()); + + if ( pTab && pTab->IsFollow() ) + { + const SwFrame* pTmp = pTab->GetFirstNonHeadlineRow(); + const bool bIsInFirstLine = ( pTmp == pRow ); + + if ( bIsInFirstLine ) + { + SwTabFrame *pMaster = pTab->FindMaster(); + if ( pMaster && pMaster->HasFollowFlowLine() ) + { + SwRowFrame* pMasterRow = static_cast<SwRowFrame*>(pMaster->GetLastLower()); + if ( pMasterRow ) + pRet = lcl_FindCorrespondingCellFrame( *static_cast<const SwRowFrame*>(pRow), *this, *pMasterRow, false ); + if ( pRet && pRet->GetTabBox()->getRowSpan() < 1 ) + pRet = &const_cast<SwCellFrame&>(pRet->FindStartEndOfRowSpanCell( true )); + } + } + } + + return pRet; +} + +// --> NEW TABLES +const SwCellFrame& SwCellFrame::FindStartEndOfRowSpanCell( bool bStart ) const +{ + const SwTabFrame* pTableFrame = dynamic_cast<const SwTabFrame*>(GetUpper()->GetUpper()); + + if ( !bStart && pTableFrame && pTableFrame->IsFollow() && pTableFrame->IsInHeadline( *this ) ) + return *this; + + OSL_ENSURE( pTableFrame && + ( (bStart && GetTabBox()->getRowSpan() < 1) || + (!bStart && GetLayoutRowSpan() > 1) ), + "SwCellFrame::FindStartRowSpanCell: No rowspan, no table, no cookies" ); + + if ( pTableFrame ) + { + const SwTable* pTable = pTableFrame->GetTable(); + + sal_uInt16 nMax = USHRT_MAX; + const SwFrame* pCurrentRow = GetUpper(); + const bool bDoNotEnterHeadline = bStart && pTableFrame->IsFollow() && + !pTableFrame->IsInHeadline( *pCurrentRow ); + + // check how many rows we are allowed to go up or down until we reach the end of + // the current table frame: + nMax = 0; + while ( bStart ? pCurrentRow->GetPrev() : pCurrentRow->GetNext() ) + { + if ( bStart ) + { + // do not enter a repeated headline: + if ( bDoNotEnterHeadline && pTableFrame->IsFollow() && + pTableFrame->IsInHeadline( *pCurrentRow->GetPrev() ) ) + break; + + pCurrentRow = pCurrentRow->GetPrev(); + } + else + pCurrentRow = pCurrentRow->GetNext(); + + ++nMax; + } + + // By passing the nMax value for Find*OfRowSpan (in case of bCurrentTableOnly + // is set) we assure that we find a rMasterBox that has a SwCellFrame in + // the current table frame: + const SwTableBox& rMasterBox = bStart ? + GetTabBox()->FindStartOfRowSpan( *pTable, nMax ) : + GetTabBox()->FindEndOfRowSpan( *pTable, nMax ); + + SwIterator<SwCellFrame,SwFormat> aIter( *rMasterBox.GetFrameFormat() ); + + for ( SwCellFrame* pMasterCell = aIter.First(); pMasterCell; pMasterCell = aIter.Next() ) + { + if ( pMasterCell->GetTabBox() == &rMasterBox ) + { + const SwTabFrame* pMasterTable = static_cast<const SwTabFrame*>(pMasterCell->GetUpper()->GetUpper()); + + if ( pMasterTable == pTableFrame ) + { + return *pMasterCell; + } + } + } + } + + SAL_WARN("sw.core", "SwCellFrame::FindStartRowSpanCell: No result"); + + return *this; +} + +// <-- NEW TABLES + +const SwRowFrame* SwFrame::IsInSplitTableRow() const +{ + OSL_ENSURE( IsInTab(), "IsInSplitTableRow should only be called for frames in tables" ); + + const SwFrame* pRow = this; + + // find most upper row frame + while( pRow && ( !pRow->IsRowFrame() || !pRow->GetUpper()->IsTabFrame() ) ) + pRow = pRow->GetUpper(); + + if ( !pRow ) return nullptr; + + OSL_ENSURE( pRow->GetUpper()->IsTabFrame(), "Confusion in table layout" ); + + const SwTabFrame* pTab = static_cast<const SwTabFrame*>(pRow->GetUpper()); + + // If most upper row frame is a headline row, the current frame + // can't be in a split table row. Thus, add corresponding condition. + if ( pRow->GetNext() || + pTab->GetTable()->IsHeadline( + *(static_cast<const SwRowFrame*>(pRow)->GetTabLine()) ) || + !pTab->HasFollowFlowLine() || + !pTab->GetFollow() ) + return nullptr; + + // skip headline + const SwRowFrame* pFollowRow = pTab->GetFollow()->GetFirstNonHeadlineRow(); + + OSL_ENSURE( pFollowRow, "SwFrame::IsInSplitTableRow() does not work" ); + + return pFollowRow; +} + +const SwRowFrame* SwFrame::IsInFollowFlowRow() const +{ + OSL_ENSURE( IsInTab(), "IsInSplitTableRow should only be called for frames in tables" ); + + // find most upper row frame + const SwFrame* pRow = this; + while( pRow && ( !pRow->IsRowFrame() || !pRow->GetUpper()->IsTabFrame() ) ) + pRow = pRow->GetUpper(); + + if ( !pRow ) return nullptr; + + OSL_ENSURE( pRow->GetUpper()->IsTabFrame(), "Confusion in table layout" ); + + const SwTabFrame* pTab = static_cast<const SwTabFrame*>(pRow->GetUpper()); + + const SwTabFrame* pMaster = pTab->IsFollow() ? pTab->FindMaster() : nullptr; + + if ( !pMaster || !pMaster->HasFollowFlowLine() ) + return nullptr; + + const SwFrame* pTmp = pTab->GetFirstNonHeadlineRow(); + const bool bIsInFirstLine = ( pTmp == pRow ); + + if ( !bIsInFirstLine ) + return nullptr; + + const SwRowFrame* pMasterRow = static_cast<const SwRowFrame*>(pMaster->GetLastLower()); + return pMasterRow; +} + +bool SwFrame::IsInBalancedSection() const +{ + bool bRet = false; + + if ( IsInSct() ) + { + const SwSectionFrame* pSectionFrame = FindSctFrame(); + if ( pSectionFrame ) + bRet = pSectionFrame->IsBalancedSection(); + } + return bRet; +} + +const SwFrame* SwLayoutFrame::GetLastLower() const +{ + const SwFrame* pRet = Lower(); + if ( !pRet ) + return nullptr; + while ( pRet->GetNext() ) + pRet = pRet->GetNext(); + return pRet; +} + +SwTextFrame* SwFrame::DynCastTextFrame() +{ + return IsTextFrame() ? static_cast<SwTextFrame*>(this) : nullptr; +} + +const SwTextFrame* SwFrame::DynCastTextFrame() const +{ + return IsTextFrame() ? static_cast<const SwTextFrame*>(this) : nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/flowfrm.cxx b/sw/source/core/layout/flowfrm.cxx new file mode 100644 index 000000000..78c3a34ca --- /dev/null +++ b/sw/source/core/layout/flowfrm.cxx @@ -0,0 +1,2736 @@ +/* -*- 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 <sal/config.h> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <svx/svdobj.hxx> + +#include <anchoredobject.hxx> +#include <bodyfrm.hxx> +#include <swtable.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <viewimp.hxx> +#include <viewopt.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/keepitem.hxx> +#include <fmtanchr.hxx> +#include <fmtsrnd.hxx> +#include <fmtpdsc.hxx> +#include <editeng/ulspitem.hxx> +#include <tgrditem.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <editeng/pgrditem.hxx> +#include <paratr.hxx> +#include <ftnfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <pagedesc.hxx> +#include <layact.hxx> +#include <flyfrm.hxx> +#include <sectfrm.hxx> +#include <section.hxx> +#include <dbg_lay.hxx> +#include <lineinfo.hxx> +#include <fmtclbl.hxx> +#include <sortedobjs.hxx> +#include <layouter.hxx> +#include <fmtfollowtextflow.hxx> +#include <calbck.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> + +bool SwFlowFrame::s_bMoveBwdJump = false; + +SwFlowFrame::SwFlowFrame( SwFrame &rFrame ) : + m_rThis( rFrame ), + m_pFollow( nullptr ), + m_pPrecede( nullptr ), + m_bLockJoin( false ), + m_bUndersized( false ), + m_bFlyLock( false ) +{} + +SwFlowFrame::~SwFlowFrame() +{ + if (m_pFollow) + { + m_pFollow->m_pPrecede = nullptr; + } + if (m_pPrecede) + { + m_pPrecede->m_pFollow = nullptr; + } +} + +void SwFlowFrame::SetFollow(SwFlowFrame *const pFollow) +{ + if (m_pFollow) + { + assert(this == m_pFollow->m_pPrecede); + m_pFollow->m_pPrecede = nullptr; + } + m_pFollow = pFollow; + if (m_pFollow != nullptr) + { + if (m_pFollow->m_pPrecede) // re-chaining pFollow? + { + assert(m_pFollow == m_pFollow->m_pPrecede->m_pFollow); + m_pFollow->m_pPrecede->m_pFollow = nullptr; + } + m_pFollow->m_pPrecede = this; + } +} + +/// @return true if any follow has the JoinLocked flag +bool SwFlowFrame::HasLockedFollow() const +{ + const SwFlowFrame* pFrame = GetFollow(); + while( pFrame ) + { + if( pFrame->IsJoinLocked() ) + return true; + pFrame = pFrame->GetFollow(); + } + return false; +} + +bool SwFlowFrame::IsKeepFwdMoveAllowed( bool bIgnoreMyOwnKeepValue ) +{ + // If all the predecessors up to the first of the chain have + // the 'keep' attribute set, and the first of the chain's + // IsFwdMoveAllowed returns false, then we're not allowed to move. + SwFrame *pFrame = &m_rThis; + if ( !pFrame->IsInFootnote() ) { + if ( bIgnoreMyOwnKeepValue && pFrame->GetIndPrev() ) + pFrame = pFrame->GetIndPrev(); + do + { if ( pFrame->GetAttrSet()->GetKeep().GetValue() ) + pFrame = pFrame->GetIndPrev(); + else + return true; + } while ( pFrame ); + } + //See IsFwdMoveAllowed() + bool bRet = false; + if ( pFrame && pFrame->GetIndPrev() ) + bRet = true; + return bRet; +} + +void SwFlowFrame::CheckKeep() +{ + // Kick off the "last" predecessor with a 'keep' attribute, because + // it's possible for the whole troop to move back. + SwFrame *pPre = m_rThis.GetIndPrev(); + assert(pPre); + if( pPre->IsSctFrame() ) + { + SwFrame *pLast = static_cast<SwSectionFrame*>(pPre)->FindLastContent(); + if( pLast && pLast->FindSctFrame() == pPre ) + pPre = pLast; + else + return; + } + SwFrame* pTmp; + bool bKeep; + while ( (bKeep = pPre->GetAttrSet()->GetKeep().GetValue()) && + nullptr != ( pTmp = pPre->GetIndPrev() ) ) + { + if( pTmp->IsSctFrame() ) + { + SwFrame *pLast = static_cast<SwSectionFrame*>(pTmp)->FindLastContent(); + if( pLast && pLast->FindSctFrame() == pTmp ) + pTmp = pLast; + else + break; + } + pPre = pTmp; + } + if ( bKeep ) + pPre->InvalidatePos(); +} + +namespace +{ +/** + * Determines if the next content frame after rThis will require the full area of the parent body + * frame. + */ +bool IsNextContentFullPage(const SwFrame& rThis) +{ + const SwFrame* pNext = rThis.FindNextCnt(); + if (!pNext) + { + return false; + } + + const SwSortedObjs* pNextDrawObjs = pNext->GetDrawObjs(); + if (!pNextDrawObjs || !pNextDrawObjs->size()) + { + return false; + } + + for (const auto& pDrawObj : *pNextDrawObjs) + { + if (!pDrawObj) + { + continue; + } + + SwTwips nDrawObjHeight = pDrawObj->GetObjRectWithSpaces().Height(); + const SwPageFrame* pPageFrame = pDrawObj->GetPageFrame(); + if (!pPageFrame) + { + continue; + } + + SwTwips nBodyHeight = pPageFrame->GetLower()->getFrameArea().Height(); + if (nDrawObjHeight < nBodyHeight) + { + continue; + } + + const SwFormatSurround& rSurround = pDrawObj->GetFrameFormat().GetSurround(); + if (rSurround.GetSurround() != text::WrapTextMode_NONE) + { + continue; + } + + // At this point the height of the draw object will use all the vertical available space, + // and also no wrapping will be performed, so all horizontal space will be taken as well. + return true; + } + + return false; +} +} + +bool SwFlowFrame::IsKeep(SvxFormatKeepItem const& rKeep, + SvxFormatBreakItem const& rBreak, + bool const bCheckIfLastRowShouldKeep) const +{ + // 1. The keep attribute is ignored inside footnotes + // 2. For compatibility reasons, the keep attribute is + // ignored for frames inside table cells + // 3. If bBreakCheck is set to true, this function only checks + // if there are any break after attributes set at rAttrs + // or break before attributes set for the next content (or next table) + // 4. Keep is ignored if the next frame will require its own page. + bool bKeep = bCheckIfLastRowShouldKeep || + ( !m_rThis.IsInFootnote() && + ( !m_rThis.IsInTab() || m_rThis.IsTabFrame() ) && + rKeep.GetValue() && !IsNextContentFullPage(m_rThis)); + + OSL_ENSURE( !bCheckIfLastRowShouldKeep || m_rThis.IsTabFrame(), + "IsKeep with bCheckIfLastRowShouldKeep should only be used for tabfrms" ); + + // Ignore keep attribute if there are break situations: + if ( bKeep ) + { + switch (rBreak.GetBreak()) + { + case SvxBreak::ColumnAfter: + case SvxBreak::ColumnBoth: + case SvxBreak::PageAfter: + case SvxBreak::PageBoth: + { + bKeep = false; + break; + } + default: break; + } + if ( bKeep ) + { + SwFrame *pNxt; + if( nullptr != (pNxt = m_rThis.FindNextCnt()) && + (!m_pFollow || pNxt != &m_pFollow->GetFrame())) + { + // The last row of a table only keeps with the next content + // it they are in the same section: + if ( bCheckIfLastRowShouldKeep ) + { + const SwSection* pThisSection = nullptr; + const SwSection* pNextSection = nullptr; + const SwSectionFrame* pThisSectionFrame = m_rThis.FindSctFrame(); + const SwSectionFrame* pNextSectionFrame = pNxt->FindSctFrame(); + + if ( pThisSectionFrame ) + pThisSection = pThisSectionFrame->GetSection(); + + if ( pNextSectionFrame ) + pNextSection = pNextSectionFrame->GetSection(); + + if ( pThisSection != pNextSection ) + bKeep = false; + } + + if ( bKeep ) + { + SvxFormatBreakItem const* pBreak; + SwFormatPageDesc const* pPageDesc; + SwTabFrame* pTab = pNxt->IsInTab() ? pNxt->FindTabFrame() : nullptr; + if (pTab && (!m_rThis.IsInTab() || m_rThis.FindTabFrame() != pTab)) + { + const SwAttrSet *const pSet = &pTab->GetFormat()->GetAttrSet(); + pBreak = &pSet->GetBreak(); + pPageDesc = &pSet->GetPageDesc(); + } + else + { + pBreak = &pNxt->GetBreakItem(); + pPageDesc = &pNxt->GetPageDescItem(); + } + + if (pPageDesc->GetPageDesc()) + bKeep = false; + else switch (pBreak->GetBreak()) + { + case SvxBreak::ColumnBefore: + case SvxBreak::ColumnBoth: + case SvxBreak::PageBefore: + case SvxBreak::PageBoth: + bKeep = false; + break; + default: break; + } + } + } + } + } + return bKeep; +} + +sal_uInt8 SwFlowFrame::BwdMoveNecessary( const SwPageFrame *pPage, const SwRect &rRect ) +{ + // The return value helps deciding whether we need to flow back (3), + // or whether we can use the good old WouldFit (0, 1), or if + // it's reasonable to relocate and test-format (2). + + // Bit 1 in this case means that there are objects anchored to myself, + // bit 2 means that I have to evade other objects. + + // If a SurroundObj that desires to be wrapped around overlaps with the + // Rect, it's required to flow (because we can't guess the relationships). + // However it's possible for a test formatting to happen. + // If the SurroundObj is a Fly and I'm a Lower, or the Fly is a Lower of + // mine, then it doesn't matter. + // If the SurroundObj is anchored in a character bound Fly, and I'm not + // a Lower of that character bound Fly myself, then the Fly doesn't matter. + + // If the object is anchored with me, i can ignore it, because + // it's likely that it will follow me with the flow. A test formatting is + // not allowed in that case, however! + sal_uInt8 nRet = 0; + SwFlowFrame *pTmp = this; + do + { // If there are objects hanging either on me or on a follow, we can't + // do a test formatting, because paragraph bound objects wouldn't + // be properly considered, and character bound objects shouldn't + // be test formatted at all. + if( pTmp->GetFrame().GetDrawObjs() ) + nRet = 1; + pTmp = pTmp->GetFollow(); + } while ( !nRet && pTmp ); + const SwSortedObjs *pObjs = pPage ? pPage->GetSortedObjs() : nullptr; + if (pObjs) + { + + const SwSortedObjs &rObjs = *pObjs; + SwNodeOffset nIndex = NODE_OFFSET_MAX; + for ( size_t i = 0; nRet < 3 && i < rObjs.size(); ++i ) + { + + SwAnchoredObject* pObj = rObjs[i]; + const SwFrameFormat& rFormat = pObj->GetFrameFormat(); + const SwRect aRect( pObj->GetObjRect() ); + if ( aRect.Overlaps( rRect ) && + rFormat.GetSurround().GetSurround() != css::text::WrapTextMode_THROUGH ) + { + if( m_rThis.IsLayoutFrame() && //Fly Lower of This? + Is_Lower_Of( &m_rThis, pObj->GetDrawObj() ) ) + continue; + if( auto pFly = pObj->DynCastFlyFrame() ) + { + if ( pFly->IsAnLower( &m_rThis ) )//This Lower of Fly? + continue; + } + + const SwFrame* pAnchor = pObj->GetAnchorFrame(); + if ( pAnchor == &m_rThis ) + { + nRet |= 1; + continue; + } + + // Don't do this if the object is anchored behind me in the text + // flow, because then I wouldn't evade it. + if ( ::IsFrameInSameContext( pAnchor, &m_rThis ) ) + { + if ( rFormat.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PARA ) + { + // The index of the other one can be retrieved using the anchor attribute. + SwNodeOffset nTmpIndex = rFormat.GetAnchor().GetContentAnchor()->nNode.GetIndex(); + // Now we're going to check whether the current paragraph before + // the anchor of the displacing object sits in the text. If this + // is the case, we don't try to evade it. + // The index is being determined via SwFormatAnchor, because it's + // getting quite expensive otherwise. + if( NODE_OFFSET_MAX == nIndex ) + { + const SwNode *pNode; + if (m_rThis.IsTextFrame()) + pNode = static_cast<SwTextFrame&>(m_rThis).GetTextNodeFirst(); + else if (m_rThis.IsNoTextFrame()) + pNode = static_cast<SwNoTextFrame&>(m_rThis).GetNode(); + else if( m_rThis.IsSctFrame() ) + pNode = static_cast<SwSectionFormat*>(static_cast<SwSectionFrame&>(m_rThis). + GetFormat())->GetSectionNode(); + else + { + assert(!m_rThis.IsContentFrame()); + OSL_ENSURE( m_rThis.IsTabFrame(), "new FowFrame?" ); + pNode = static_cast<SwTabFrame&>(m_rThis).GetTable()-> + GetTabSortBoxes()[0]->GetSttNd()->FindTableNode(); + } + nIndex = pNode->GetIndex(); + } + if (nIndex < nTmpIndex && + (!m_rThis.IsTextFrame() || + !FrameContainsNode(static_cast<SwTextFrame&>(m_rThis), nTmpIndex))) + { + continue; + } + } + } + else + continue; + + nRet |= 2; + } + } + } + return nRet; +} + +/// A specialized form of Cut(), which relocates a whole chain (this and the following, +/// in particular). During this process, only the minimum operations and notifications are done. +SwLayoutFrame *SwFlowFrame::CutTree( SwFrame *pStart ) +{ + // Cut the Start and all the neighbours; they are chained together and + // a handle to the first one is returned. Residuals are invalidated + // as appropriate. + + SwLayoutFrame *pLay = pStart->GetUpper(); + if ( pLay->IsInFootnote() ) + pLay = pLay->FindFootnoteFrame(); + + // i#58846 + // <pPrepare( PrepareHint::QuoVadis )> only for frames in footnotes + if( pStart->IsInFootnote() ) + { + SwFrame* pTmp = pStart->GetIndPrev(); + if( pTmp ) + pTmp->Prepare( PrepareHint::QuoVadis ); + } + + // Just cut quickly and take care that we don't cause problems with the + // left-behinds. The pointers of the chain being cut can point who-knows where. + if ( pStart == pStart->GetUpper()->Lower() ) + pStart->GetUpper()->m_pLower = nullptr; + if ( pStart->GetPrev() ) + { + pStart->GetPrev()->mpNext = nullptr; + pStart->mpPrev = nullptr; + } + + if ( pLay->IsFootnoteFrame() ) + { + if ( !pLay->Lower() && !pLay->IsColLocked() && + !static_cast<SwFootnoteFrame*>(pLay)->IsBackMoveLocked() ) + { + // tdf#101821 don't delete it while iterating over it + if (!pLay->IsDeleteForbidden()) + { + pLay->Cut(); + SwFrame::DestroyFrame(pLay); + } + // else: assume there is code on the stack to clean up empty + // footnote frames + // (don't go into the else branch below, it produces a disconnected + // footnote with null upper that can be returned by + // SwFootnoteBossFrame::FindFootnote() causing null pointer deref + // in SwTextFrame::ConnectFootnote() + } + else + { + bool bUnlock = !static_cast<SwFootnoteFrame*>(pLay)->IsBackMoveLocked(); + static_cast<SwFootnoteFrame*>(pLay)->LockBackMove(); + pLay->InvalidateSize(); + pLay->Calc(pLay->getRootFrame()->GetCurrShell()->GetOut()); + SwContentFrame *pCnt = pLay->ContainsContent(); + while ( pCnt && pLay->IsAnLower( pCnt ) ) + { + // It's possible for the ContentFrame to be locked, and we don't want + // to end up in an endless page migration, so we're not even + // going to call Calc! + OSL_ENSURE( pCnt->IsTextFrame(), "The Graphic has landed." ); + if ( static_cast<SwTextFrame*>(pCnt)->IsLocked() || + static_cast<SwTextFrame*>(pCnt)->GetFollow() == pStart ) + break; + pCnt->Calc(pCnt->getRootFrame()->GetCurrShell()->GetOut()); + pCnt = pCnt->GetNextContentFrame(); + } + if( bUnlock ) + static_cast<SwFootnoteFrame*>(pLay)->UnlockBackMove(); + } + pLay = nullptr; + } + return pLay; +} + +/// A specialized form of Paste(), which relocates a whole chain (this and the following, +/// in particular). During this process, only the minimum operations and notifications are done. +bool SwFlowFrame::PasteTree( SwFrame *pStart, SwLayoutFrame *pParent, SwFrame *pSibling, + SwFrame *pOldParent ) +{ + // returns true if there's a LayoutFrame in the chain. + bool bRet = false; + + // The chain beginning with pStart is inserted before pSibling + // under the parent. We take care to invalidate as required. + + // I'm receiving a finished chain. We need to update the pointers for + // the beginning of the chain, then all the uppers and finally the end. + // On the way there, we invalidate as required. + if ( pSibling ) + { + pStart->mpPrev = pSibling->GetPrev(); + if ( nullptr != pStart->mpPrev ) + pStart->GetPrev()->mpNext = pStart; + else + pParent->m_pLower = pStart; + pSibling->InvalidatePos_(); + pSibling->InvalidatePrt_(); + } + else + { + pStart->mpPrev = pParent->Lower(); + if ( nullptr == pStart->mpPrev ) + pParent->m_pLower = pStart; + else + //i#100782 + //If the pParent has more than 1 child nodes, former design will + //ignore them directly without any collection work. It will make some + //dangling pointers. This lead the crash... + //The new design will find the last child of pParent in loop way, and + //add the pStart after the last child. + // pParent->Lower()->pNext = pStart; + { + SwFrame* pTemp = pParent->m_pLower; + while (pTemp) + { + if (pTemp->mpNext) + pTemp = pTemp->mpNext; + else + { + pStart->mpPrev = pTemp; + pTemp->mpNext = pStart; + break; + } + } + } + + + // i#27145 + if ( pParent->IsSctFrame() ) + { + // We have no sibling because pParent is a section frame and + // has just been created to contain some content. The printing + // area of the frame behind pParent has to be invalidated, so + // that the correct distance between pParent and the next frame + // can be calculated. + pParent->InvalidateNextPrtArea(); + } + } + SwFrame *pFloat = pStart; + SwFrame *pLst = nullptr; + SwRectFnSet aRectFnSet(pParent); + SwTwips nGrowVal = 0; + do + { pFloat->mpUpper = pParent; + pFloat->InvalidateAll_(); + pFloat->CheckDirChange(); + + // I'm a friend of the TextFrame and thus am allowed to do many things. + // The CacheIdx idea seems to be a bit risky! + if ( pFloat->IsTextFrame() ) + { + if ( static_cast<SwTextFrame*>(pFloat)->GetCacheIdx() != USHRT_MAX ) + static_cast<SwTextFrame*>(pFloat)->Init(); // I'm his friend. + } + else + bRet = true; + + nGrowVal += aRectFnSet.GetHeight(pFloat->getFrameArea()); + if ( pFloat->GetNext() ) + pFloat = pFloat->GetNext(); + else + { + pLst = pFloat; + pFloat = nullptr; + } + } while ( pFloat ); + + if ( pSibling ) + { + pLst->mpNext = pSibling; + pSibling->mpPrev = pLst; + if( pSibling->IsInFootnote() ) + { + if( pSibling->IsSctFrame() ) + pSibling = static_cast<SwSectionFrame*>(pSibling)->ContainsAny(); + if( pSibling ) + pSibling->Prepare( PrepareHint::ErgoSum ); + } + } + if ( nGrowVal ) + { + if ( pOldParent && pOldParent->IsBodyFrame() ) // For variable page height while browsing + pOldParent->Shrink( nGrowVal ); + pParent->Grow( nGrowVal ); + } + + if ( pParent->IsFootnoteFrame() ) + static_cast<SwFootnoteFrame*>(pParent)->InvalidateNxtFootnoteCnts( pParent->FindPageFrame() ); + return bRet; +} + +void SwFlowFrame::MoveSubTree( SwLayoutFrame* pParent, SwFrame* pSibling ) +{ + OSL_ENSURE( pParent, "No parent given." ); + OSL_ENSURE( m_rThis.GetUpper(), "Where are we coming from?" ); + + // Be economical with notifications if an action is running. + SwViewShell *pSh = m_rThis.getRootFrame()->GetCurrShell(); + const SwViewShellImp *pImp = pSh ? pSh->Imp() : nullptr; + const bool bComplete = pImp && pImp->IsAction() && pImp->GetLayAction().IsComplete(); + + if ( !bComplete ) + { + SwFrame *pPre = m_rThis.GetIndPrev(); + if ( pPre ) + { + pPre->SetRetouche(); + // follow-up of i#26250 + // invalidate printing area of previous frame, if it's in a table + if ( pPre->GetUpper()->IsInTab() ) + { + pPre->InvalidatePrt_(); + } + pPre->InvalidatePage(); + } + else + { + m_rThis.GetUpper()->SetCompletePaint(); + m_rThis.GetUpper()->InvalidatePage(); + } + } + + SwPageFrame *pOldPage = m_rThis.FindPageFrame(); + + SwLayoutFrame *pOldParent; + bool bInvaLay; + + { + //JoinLock pParent for the lifetime of the Cut/Paste call to avoid + //SwSectionFrame::MergeNext removing the pParent we're trying to reparent + //into + FlowFrameJoinLockGuard aJoinGuard(pParent); + SwFrameDeleteGuard aDeleteGuard(pParent); + pOldParent = CutTree( &m_rThis ); + bInvaLay = PasteTree( &m_rThis, pParent, pSibling, pOldParent ); + } + + // If, by cutting & pasting, an empty SectionFrame came into existence, it should + // disappear automatically. + SwSectionFrame *pSct; + + if ( pOldParent && !pOldParent->Lower() && + ( pOldParent->IsInSct() && + !(pSct = pOldParent->FindSctFrame())->ContainsContent() && + !pSct->ContainsAny( true ) ) ) + { + pSct->DelEmpty( false ); + } + + // If we're in a column section, we'd rather not call Calc "from below" + if( !m_rThis.IsInSct() && + ( !m_rThis.IsInTab() || ( m_rThis.IsTabFrame() && !m_rThis.GetUpper()->IsInTab() ) ) ) + m_rThis.GetUpper()->Calc(m_rThis.getRootFrame()->GetCurrShell()->GetOut()); + else if( m_rThis.GetUpper()->IsSctFrame() ) + { + SwSectionFrame* pTmpSct = static_cast<SwSectionFrame*>(m_rThis.GetUpper()); + bool bOld = pTmpSct->IsContentLocked(); + pTmpSct->SetContentLock( true ); + pTmpSct->Calc(m_rThis.getRootFrame()->GetCurrShell()->GetOut()); + if( !bOld ) + pTmpSct->SetContentLock( false ); + } + SwPageFrame *pPage = m_rThis.FindPageFrame(); + + if ( pOldPage != pPage ) + { + m_rThis.InvalidatePage( pPage ); + if ( m_rThis.IsLayoutFrame() ) + { + SwContentFrame *pCnt = static_cast<SwLayoutFrame*>(&m_rThis)->ContainsContent(); + if ( pCnt ) + pCnt->InvalidatePage( pPage ); + } + else if ( pSh && pSh->GetDoc()->GetLineNumberInfo().IsRestartEachPage() + && pPage->FindFirstBodyContent() == &m_rThis ) + { + m_rThis.InvalidateLineNum_(); + } + } + if ( bInvaLay || (pSibling && pSibling->IsLayoutFrame()) ) + m_rThis.GetUpper()->InvalidatePage( pPage ); +} + +bool SwFlowFrame::IsAnFollow( const SwFlowFrame *pAssumed ) const +{ + const SwFlowFrame *pFoll = this; + do + { if ( pAssumed == pFoll ) + return true; + pFoll = pFoll->GetFollow(); + } while ( pFoll ); + return false; +} + +SwTextFrame* SwContentFrame::FindMaster() const +{ + OSL_ENSURE( IsFollow(), "SwContentFrame::FindMaster(): !IsFollow" ); + + const SwContentFrame* pPrec = static_cast<const SwContentFrame*>(SwFlowFrame::GetPrecede()); + + if ( pPrec && pPrec->HasFollow() && pPrec->GetFollow() == this ) + { + OSL_ENSURE( pPrec->IsTextFrame(), "NoTextFrame with follow found" ); + return const_cast<SwTextFrame*>(static_cast< const SwTextFrame* >(pPrec)); + } + + OSL_FAIL( "Follow is lost in Space." ); + return nullptr; +} + +SwSectionFrame* SwSectionFrame::FindMaster() const +{ + OSL_ENSURE( IsFollow(), "SwSectionFrame::FindMaster(): !IsFollow" ); + + if (!m_pSection) + return nullptr; + + SwIterator<SwSectionFrame,SwFormat> aIter( *m_pSection->GetFormat() ); + SwSectionFrame* pSect = aIter.First(); + while ( pSect ) + { + if (pSect->GetFollow() == this) + return pSect; + pSect = aIter.Next(); + } + + OSL_FAIL( "Follow is lost in Space." ); + return nullptr; +} + +SwTabFrame* SwTabFrame::FindMaster( bool bFirstMaster ) const +{ + OSL_ENSURE( IsFollow(), "SwTabFrame::FindMaster(): !IsFollow" ); + + SwIterator<SwTabFrame,SwFormat> aIter( *GetTable()->GetFrameFormat() ); + SwTabFrame* pTab = aIter.First(); + while ( pTab ) + { + if ( bFirstMaster ) + { + // Optimization. This makes code like this obsolete: + // while ( pTab->IsFollow() ) + // pTab = pTab->FindMaster(); + + if ( !pTab->IsFollow() ) + { + SwTabFrame* pNxt = pTab; + while ( pNxt ) + { + if ( pNxt->GetFollow() == this ) + return pTab; + pNxt = pNxt->GetFollow(); + } + } + } + else + { + if ( pTab->GetFollow() == this ) + return pTab; + } + + pTab = aIter.Next(); + } + + OSL_FAIL( "Follow is lost in Space." ); + return nullptr; +} + +/** + * Returns the next/previous Layout leaf that's NOT below this (or even is this itself). + * Also, that leaf must be in the same text flow as the pAnch origin frame (Body, Footnote) + */ +const SwLayoutFrame *SwFrame::GetLeaf( MakePageType eMakePage, bool bFwd, + const SwFrame *pAnch ) const +{ + // No flow, no joy... + if ( !(IsInDocBody() || IsInFootnote() || IsInFly()) ) + return nullptr; + + const SwFrame *pLeaf = this; + bool bFound = false; + + do + { pLeaf = const_cast<SwFrame*>(pLeaf)->GetLeaf( eMakePage, bFwd ); + + if ( pLeaf && + (!IsLayoutFrame() || !static_cast<const SwLayoutFrame*>(this)->IsAnLower( pLeaf ))) + { + if ( pAnch->IsInDocBody() == pLeaf->IsInDocBody() && + pAnch->IsInFootnote() == pLeaf->IsInFootnote() ) + { + bFound = true; + } + } + } while ( !bFound && pLeaf ); + + return static_cast<const SwLayoutFrame*>(pLeaf); +} + +SwLayoutFrame *SwFrame::GetLeaf( MakePageType eMakePage, bool bFwd ) +{ + if ( IsInFootnote() ) + return bFwd ? GetNextFootnoteLeaf( eMakePage ) : GetPrevFootnoteLeaf( eMakePage ); + + // i#53323 + // A frame could be inside a table AND inside a section. + // Thus, it has to be determined, which is the first parent. + bool bInTab( IsInTab() ); + bool bInSct( IsInSct() ); + if ( bInTab && bInSct ) + { + const SwFrame* pUpperFrame( GetUpper() ); + while ( pUpperFrame ) + { + if ( pUpperFrame->IsTabFrame() ) + { + // the table is the first. + bInSct = false; + break; + } + else if ( pUpperFrame->IsSctFrame() ) + { + // the section is the first. + bInTab = false; + break; + } + + pUpperFrame = pUpperFrame->GetUpper(); + } + } + + if ( bInTab && ( !IsTabFrame() || GetUpper()->IsCellFrame() ) ) // TABLE IN TABLE + return bFwd ? GetNextCellLeaf() : GetPrevCellLeaf(); + + if ( bInSct ) + return bFwd ? GetNextSctLeaf( eMakePage ) : GetPrevSctLeaf(); + + return bFwd ? GetNextLeaf( eMakePage ) : GetPrevLeaf(); +} + +bool SwFrame::WrongPageDesc( SwPageFrame* pNew ) +{ + // Now it's getting a bit complicated: + + // Maybe I'm bringing a Pagedesc myself; in that case, + // the pagedesc of the next page needs to correspond. + // Otherwise, I'll have to dig a bit deeper to see where + // the following Pagedesc is coming from. + // If the following page itself tells me that it's pagedesc + // is wrong, I can happily exchange it. + // If the page however thinks that it's pagedesc is correct, + // this doesn't mean it's useful to me: + // If the first BodyContent asks for a PageDesc or a PageBreak, + // I'll have to insert a new page - except the desired page is + // the correct one. + // If I inserted a new page, the problems only get started: + // because then it's likely for the next page to have been + // wrong and having been swapped because of that. + // This in turn means that I have a new (and correct) page, + // but the conditions to swap still apply. + // Way out of the situation: Try to preliminarily insert a + // new page once (empty pages are already inserted by InsertPage() + // if required) + + //My Pagedesc doesn't count if I'm a follow! + const SwPageDesc *pDesc = nullptr; + std::optional<sal_uInt16> oTmp; + SwFlowFrame *pFlow = SwFlowFrame::CastFlowFrame( this ); + if ( !pFlow || !pFlow->IsFollow() ) + { + const SwFormatPageDesc &rFormatDesc = GetPageDescItem(); + pDesc = rFormatDesc.GetPageDesc(); + if( pDesc ) + { + if( !pDesc->GetRightFormat() ) + oTmp = 2; + else if( !pDesc->GetLeftFormat() ) + oTmp = 1; + else if( rFormatDesc.GetNumOffset() ) + oTmp = rFormatDesc.GetNumOffset(); + } + } + + // Does the Content bring a Pagedesc or do we need the + // virtual page number of the new layout leaf? + // PageDesc isn't allowed with Follows + const bool isRightPage = oTmp ? sw::IsRightPageByNumber(*mpRoot, *oTmp) : pNew->OnRightPage(); + if ( !pDesc ) + pDesc = pNew->FindPageDesc(); + + bool bFirst = pNew->OnFirstPage(); + + const SwFlowFrame *pNewFlow = pNew->FindFirstBodyContent(); + // Did we find ourselves? + if( pNewFlow == pFlow ) + pNewFlow = nullptr; + if ( pNewFlow && pNewFlow->GetFrame().IsInTab() ) + pNewFlow = pNewFlow->GetFrame().FindTabFrame(); + const SwPageDesc *pNewDesc= ( pNewFlow && !pNewFlow->IsFollow() ) + ? pNewFlow->GetFrame().GetPageDescItem().GetPageDesc() + : nullptr; + + SAL_INFO( "sw.pageframe", "WrongPageDesc p: " << pNew << " phys: " << pNew->GetPhyPageNum() ); + SAL_INFO( "sw.pageframe", "WrongPageDesc " << pNew->GetPageDesc() << " " << pDesc ); + SAL_INFO( "sw.pageframe", "WrongPageDesc right: " << isRightPage + << " first: " << bFirst << " " << pNew->GetFormat() << " == " + << (isRightPage ? pDesc->GetRightFormat(bFirst) : pDesc->GetLeftFormat(bFirst)) << " " + << (isRightPage ? pDesc->GetLeftFormat(bFirst) : pDesc->GetRightFormat(bFirst)) ); + + return (pNew->GetPageDesc() != pDesc) // own desc ? + || (pNew->GetFormat() != + (isRightPage ? pDesc->GetRightFormat(bFirst) : pDesc->GetLeftFormat(bFirst))) + || (pNewDesc && pNewDesc == pDesc); +} + +/// Returns the next layout leaf in which we can move the frame. +SwLayoutFrame *SwFrame::GetNextLeaf( MakePageType eMakePage ) +{ + OSL_ENSURE( !IsInFootnote(), "GetNextLeaf(), don't call me for Footnote." ); + OSL_ENSURE( !IsInSct(), "GetNextLeaf(), don't call me for Sections." ); + + const bool bBody = IsInDocBody(); // If I'm coming from the DocBody, + // I want to end up in the body. + + // It doesn't make sense to insert pages, as we only want to search the + // chain. + if( IsInFly() ) + eMakePage = MAKEPAGE_NONE; + + // For tables, we just take the big leap. A simple GetNext would + // iterate through the first cells and, in turn, all other cells. + SwLayoutFrame *pLayLeaf = nullptr; + if ( IsTabFrame() ) + { + SwFrame *const pTmp = static_cast<SwTabFrame*>(this)->FindLastContentOrTable(); + if ( pTmp ) + pLayLeaf = pTmp->GetUpper(); + } + if ( !pLayLeaf ) + pLayLeaf = GetNextLayoutLeaf(); + + SwLayoutFrame *pOldLayLeaf = nullptr; // Make sure that we don't have to + // start searching from top when we + // have a freshly created page. + bool bNewPg = false; // Only insert a new page once. + + while ( true ) + { + if ( pLayLeaf ) + { + // There's yet another LayoutFrame. Let's see if it's ready to host + // me as well. + // It only needs to be of the same kind like my starting point + // (DocBody or Footnote respectively) + if ( pLayLeaf->FindPageFrame()->IsFootnotePage() ) + { // If I ended up at the end note pages, we're done. + pLayLeaf = nullptr; + continue; + } + if ( (bBody && !pLayLeaf->IsInDocBody()) || pLayLeaf->IsInTab() + || pLayLeaf->IsInSct() ) + { + // They don't want me! Try again + pOldLayLeaf = pLayLeaf; + pLayLeaf = pLayLeaf->GetNextLayoutLeaf(); + continue; + } + + // I'm wanted, therefore I'm done. However, it may still be that, + // during a page break, the page type isn't the desired one. In that + // case we have to insert a page of the correct type. + + if( !IsFlowFrame() && ( eMakePage == MAKEPAGE_NONE || + eMakePage==MAKEPAGE_APPEND || eMakePage==MAKEPAGE_NOSECTION ) ) + return pLayLeaf; + + SwPageFrame *pNew = pLayLeaf->FindPageFrame(); + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + // The pagedesc check does not make sense for frames in fly frames + if ( pNew != FindPageFrame() && !bNewPg && !IsInFly() && + // i#46683 + // Do not consider page descriptions in browse mode (since + // MoveBwd ignored them) + !(pSh && pSh->GetViewOptions()->getBrowseMode() ) ) + { + if( WrongPageDesc( pNew ) ) + { + SwFootnoteContFrame *pCont = pNew->FindFootnoteCont(); + if( pCont ) + { + // If the reference of the first footnote of this page + // lies before the page, we'd rather not insert a new page. + + SwFootnoteFrame *pFootnote = static_cast<SwFootnoteFrame*>(pCont->Lower()); + if( pFootnote && pFootnote->GetRef() ) + { + const sal_uInt16 nRefNum = pNew->GetPhyPageNum(); + if( pFootnote->GetRef()->GetPhyPageNum() < nRefNum ) + break; + } + } + //Gotcha! The following page is wrong, therefore we need to + //insert a new one. + if ( eMakePage == MAKEPAGE_INSERT ) + { + bNewPg = true; + + SwPageFrame *pPg = pOldLayLeaf ? + pOldLayLeaf->FindPageFrame() : nullptr; + if ( pPg && pPg->IsEmptyPage() ) + // Don't insert behind. Insert before the EmptyPage. + pPg = static_cast<SwPageFrame*>(pPg->GetPrev()); + + if ( !pPg || pPg == pNew ) + pPg = FindPageFrame(); + + InsertPage( pPg, false ); + pLayLeaf = GetNextLayoutLeaf(); + pOldLayLeaf = nullptr; + continue; + } + else + pLayLeaf = nullptr; + } + } + break; + } + else + { + // There's no other matching LayoutFrame, so we have to insert + // a new page. + if ( eMakePage == MAKEPAGE_APPEND || eMakePage == MAKEPAGE_INSERT ) + { + InsertPage( + pOldLayLeaf ? pOldLayLeaf->FindPageFrame() : FindPageFrame(), + false ); + + // And again from the start. + pLayLeaf = pOldLayLeaf ? pOldLayLeaf : GetNextLayoutLeaf(); + } + else + break; + } + } + return pLayLeaf; +} + +/// Returns the previous layout leaf where we can move the frame. +SwLayoutFrame *SwFrame::GetPrevLeaf() +{ + OSL_ENSURE( !IsInFootnote(), "GetPrevLeaf(), don't call me for Footnote." ); + + const bool bBody = IsInDocBody(); // If I'm coming from the DocBody, + // I want to end up in the body. + const bool bFly = IsInFly(); + + SwLayoutFrame *pLayLeaf = GetPrevLayoutLeaf(); + SwLayoutFrame *pPrevLeaf = nullptr; + + while ( pLayLeaf ) + { + if ( pLayLeaf->IsInTab() || // Never go into tables. + pLayLeaf->IsInSct() ) // Same goes for sections! + pLayLeaf = pLayLeaf->GetPrevLayoutLeaf(); + else if ( bBody && pLayLeaf->IsInDocBody() ) + { + if ( pLayLeaf->Lower() ) + break; + pPrevLeaf = pLayLeaf; + pLayLeaf = pLayLeaf->GetPrevLayoutLeaf(); + if ( pLayLeaf ) + SwFlowFrame::SetMoveBwdJump( true ); + } + else if ( bFly ) + break; //Contents in Flys should accept any layout leaf. + else + pLayLeaf = pLayLeaf->GetPrevLayoutLeaf(); + } + return pLayLeaf ? pLayLeaf : pPrevLeaf; +} + +bool SwFlowFrame::IsPrevObjMove() const +{ + // true: The FlowFrame must respect the a border of the predecessor, also needs + // to insert a break if required. + + //!!!!!!!!!!!Hack!!!!!!!!!!! + const SwViewShell *pSh = m_rThis.getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + return false; + + SwFrame *pPre = m_rThis.FindPrev(); + + if ( pPre && pPre->GetDrawObjs() ) + { + OSL_ENSURE( SwFlowFrame::CastFlowFrame( pPre ), "new flowfrm?" ); + if( SwFlowFrame::CastFlowFrame( pPre )->IsAnFollow( this ) ) + return false; + if (SwFlowFrame::CastFlowFrame(pPre)->IsJoinLocked()) + { + SwBorderAttrAccess baa(SwFrame::GetCache(), pPre); + SwBorderAttrs const& rAttrs(*baa.Get()); + if (SwFlowFrame::CastFlowFrame(pPre)->IsKeep(rAttrs.GetAttrSet().GetKeep(), pPre->GetBreakItem())) + { // pPre is currently being formatted - maybe it moved back but + // its objects still have the old page's body as + // mpVertPosOrientFrame and SwContentFrame::MakeAll() is calling + // pNxt->Calc() in this case so allow this frame to move back + return false; // too, else pPre is forced to move forward again. + } + } + SwLayoutFrame* pPreUp = pPre->GetUpper(); + // If the upper is a SectionFrame, or a column of a SectionFrame, we're + // allowed to protrude out of it. However, we need to respect the + // Upper of the SectionFrame. + if( pPreUp->IsInSct() ) + { + if( pPreUp->IsSctFrame() ) + pPreUp = pPreUp->GetUpper(); + else if( pPreUp->IsColBodyFrame() && + pPreUp->GetUpper()->GetUpper()->IsSctFrame() ) + pPreUp = pPreUp->GetUpper()->GetUpper()->GetUpper(); + } + // i#26945 - re-factoring + // use <GetVertPosOrientFrame()> to determine, if object has followed the + // text flow to the next layout frame + for (SwAnchoredObject* pObj : *pPre->GetDrawObjs()) + { + + // Do not consider hidden objects + // i#26945 - do not consider object, which + // doesn't follow the text flow. + if ( pObj->GetFrameFormat().GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( + pObj->GetDrawObj()->GetLayer() ) && + pObj->GetFrameFormat().GetFollowTextFlow().GetValue() ) + { + const SwLayoutFrame* pVertPosOrientFrame = pObj->GetVertPosOrientFrame(); + if ( pVertPosOrientFrame && + pPreUp != pVertPosOrientFrame && + !pPreUp->IsAnLower( pVertPosOrientFrame ) ) + { + return true; + } + } + } + } + return false; +} + +/** +|* If there's a hard page break before the Frame AND there's a +|* predecessor on the same page, true is returned (we need to create a +|* new PageBreak). Otherwise, returns false. +|* If bAct is set to true, this function returns true if +|* there's a PageBreak. +|* Of course, we don't evaluate the hard page break for follows. +|* The page break is in its own FrameFormat (BEFORE) or in the FrameFormat of the +|* predecessor (AFTER). If there's no predecessor on the page, we don't +|* need to think further. +|* Also, a page break (or the need for one) is also present if +|* the FrameFormat contains a PageDesc. +|* The implementation works only on ContentFrames! - the definition +|* of the predecessor is not clear for LayoutFrames. +|*/ +bool SwFlowFrame::IsPageBreak( bool bAct ) const +{ + if ( !IsFollow() && m_rThis.IsInDocBody() && + ( !m_rThis.IsInTab() || ( m_rThis.IsTabFrame() && !m_rThis.GetUpper()->IsInTab() ) ) ) // i66968 + { + const SwViewShell *pSh = m_rThis.getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + return false; + + // Determine predecessor + const SwFrame *pPrev = m_rThis.FindPrev(); + while ( pPrev && ( !pPrev->IsInDocBody() || + ( pPrev->IsTextFrame() && static_cast<const SwTextFrame*>(pPrev)->IsHiddenNow() ) ) ) + pPrev = pPrev->FindPrev(); + + if ( pPrev ) + { + OSL_ENSURE( pPrev->IsInDocBody(), "IsPageBreak: Not in DocBody?" ); + if ( bAct ) + { if ( m_rThis.FindPageFrame() == pPrev->FindPageFrame() ) + return false; + } + else + { if ( m_rThis.FindPageFrame() != pPrev->FindPageFrame() ) + return false; + } + + //for compatibility, also break at column break if no columns exist + const IDocumentSettingAccess& rIDSA = m_rThis.GetUpper()->GetFormat()->getIDocumentSettingAccess(); + const bool bTreatSingleColumnBreakAsPageBreak = rIDSA.get(DocumentSettingId::TREAT_SINGLE_COLUMN_BREAK_AS_PAGE_BREAK); + const SvxBreak eBreak = m_rThis.GetBreakItem().GetBreak(); + if ( eBreak == SvxBreak::PageBefore || + eBreak == SvxBreak::PageBoth || + ( bTreatSingleColumnBreakAsPageBreak && eBreak == SvxBreak::ColumnBefore && !m_rThis.FindColFrame() )) + return true; + else + { + const SvxBreak &ePrB = pPrev->GetBreakItem().GetBreak(); + if ( ePrB == SvxBreak::PageAfter || + ePrB == SvxBreak::PageBoth || + m_rThis.GetPageDescItem().GetPageDesc()) + { + return true; + } + } + } + } + return false; +} + +/** +|* If there's a hard column break before the Frame AND there is +|* a predecessor in the same column, we return true (we need to create +|* a ColBreak). Otherwise, we return false. +|* If bAct is set to true, we return true if there's a ColBreak. +|* Of course, we don't evaluate the hard column break for follows. +|* +|* The column break is in its own FrameFormat (BEFORE) or in the FrameFormat of the +|* predecessor (AFTER). If there's no predecessor in the column, we don't +|* need to think further. +|* The implementation works only on ContentFrames! - the definition +|* of the predecessor is not clear for LayoutFrames. +|*/ +bool SwFlowFrame::IsColBreak( bool bAct ) const +{ + if ( !IsFollow() && (m_rThis.IsMoveable() || bAct) ) + { + const SwFrame *pCol = m_rThis.FindColFrame(); + if ( pCol ) + { + // Determine predecessor + const SwFrame *pPrev = m_rThis.FindPrev(); + while( pPrev && ( ( !pPrev->IsInDocBody() && !m_rThis.IsInFly() && !m_rThis.FindFooterOrHeader() ) || + ( pPrev->IsTextFrame() && static_cast<const SwTextFrame*>(pPrev)->IsHiddenNow() ) ) ) + pPrev = pPrev->FindPrev(); + + if ( pPrev ) + { + if ( bAct ) + { if ( pCol == pPrev->FindColFrame() ) + return false; + } + else + { if ( pCol != pPrev->FindColFrame() ) + return false; + } + + const SvxBreak eBreak = m_rThis.GetBreakItem().GetBreak(); + if ( eBreak == SvxBreak::ColumnBefore || + eBreak == SvxBreak::ColumnBoth ) + return true; + else + { + const SvxBreak &ePrB = pPrev->GetBreakItem().GetBreak(); + if ( ePrB == SvxBreak::ColumnAfter || + ePrB == SvxBreak::ColumnBoth ) + return true; + } + } + } + } + return false; +} + +bool SwFlowFrame::HasParaSpaceAtPages( bool bSct ) const +{ + if( m_rThis.IsInSct() ) + { + const SwFrame* pTmp = m_rThis.GetUpper(); + while( pTmp ) + { + if( pTmp->IsCellFrame() || pTmp->IsFlyFrame() || + pTmp->IsFooterFrame() || pTmp->IsHeaderFrame() || + ( pTmp->IsFootnoteFrame() && !static_cast<const SwFootnoteFrame*>(pTmp)->GetMaster() ) ) + return true; + if( pTmp->IsPageFrame() ) + return !pTmp->GetPrev() || IsPageBreak(true); + if( pTmp->IsColumnFrame() && pTmp->GetPrev() ) + return IsColBreak( true ); + if( pTmp->IsSctFrame() && ( !bSct || pTmp->GetPrev() ) ) + return false; + pTmp = pTmp->GetUpper(); + } + OSL_FAIL( "HasParaSpaceAtPages: Where's my page?" ); + return false; + } + if( !m_rThis.IsInDocBody() || ( m_rThis.IsInTab() && !m_rThis.IsTabFrame()) || + IsPageBreak( true ) || ( m_rThis.FindColFrame() && IsColBreak( true ) ) ) + return true; + const SwFrame* pTmp = m_rThis.FindColFrame(); + if( pTmp ) + { + if( pTmp->GetPrev() ) + return false; + } + else + pTmp = &m_rThis; + pTmp = pTmp->FindPageFrame(); + return pTmp && !pTmp->GetPrev(); +} + +/** helper method to determine previous frame for calculation of the + upper space + + i#11860 +*/ +const SwFrame* SwFlowFrame::GetPrevFrameForUpperSpaceCalc_( const SwFrame* _pProposedPrevFrame ) const +{ + const SwFrame* pPrevFrame = _pProposedPrevFrame + ? _pProposedPrevFrame + : m_rThis.GetPrev(); + + // Skip hidden paragraphs and empty sections + while ( pPrevFrame && + ( ( pPrevFrame->IsTextFrame() && + static_cast<const SwTextFrame*>(pPrevFrame)->IsHiddenNow() ) || + ( pPrevFrame->IsSctFrame() && + !static_cast<const SwSectionFrame*>(pPrevFrame)->GetSection() ) ) ) + { + pPrevFrame = pPrevFrame->GetPrev(); + } + + // Special case: no direct previous frame is found but frame is in footnote + // Search for a previous frame in previous footnote, + // if frame isn't in a section, which is also in the footnote + if ( !pPrevFrame && m_rThis.IsInFootnote() && + ( m_rThis.IsSctFrame() || + !m_rThis.IsInSct() || !m_rThis.FindSctFrame()->IsInFootnote() ) ) + { + const SwFootnoteFrame* pPrevFootnoteFrame = + static_cast<const SwFootnoteFrame*>(m_rThis.FindFootnoteFrame()->GetPrev()); + if ( pPrevFootnoteFrame ) + { + pPrevFrame = pPrevFootnoteFrame->GetLastLower(); + + // Skip hidden paragraphs and empty sections + while ( pPrevFrame && + ( ( pPrevFrame->IsTextFrame() && + static_cast<const SwTextFrame*>(pPrevFrame)->IsHiddenNow() ) || + ( pPrevFrame->IsSctFrame() && + !static_cast<const SwSectionFrame*>(pPrevFrame)->GetSection() ) ) ) + { + pPrevFrame = pPrevFrame->GetPrev(); + } + } + } + // Special case: found previous frame is a section + // Search for the last content in the section + if( pPrevFrame && pPrevFrame->IsSctFrame() ) + { + const SwSectionFrame* pPrevSectFrame = + static_cast<const SwSectionFrame*>(pPrevFrame); + pPrevFrame = pPrevSectFrame->FindLastContent(); + // If the last content is in a table _inside_ the section, + // take the table herself. + // Correction: Check directly, if table is inside table, instead of indirectly + // by checking, if section isn't inside a table + if ( pPrevFrame && pPrevFrame->IsInTab() ) + { + const SwTabFrame* pTableFrame = pPrevFrame->FindTabFrame(); + if ( pPrevSectFrame->IsAnLower( pTableFrame ) ) + { + pPrevFrame = pTableFrame; + } + } + // Correction: skip hidden text frames + while ( pPrevFrame && + pPrevFrame->IsTextFrame() && + static_cast<const SwTextFrame*>(pPrevFrame)->IsHiddenNow() ) + { + pPrevFrame = pPrevFrame->GetPrev(); + } + } + + return pPrevFrame; +} + +/// Compare styles attached to these text frames. +static bool lcl_IdenticalStyles(const SwFrame* pPrevFrame, const SwFrame* pFrame) +{ + SwTextFormatColl *pPrevFormatColl = nullptr; + if (pPrevFrame && pPrevFrame->IsTextFrame()) + { + const SwTextFrame *pTextFrame = static_cast< const SwTextFrame * >( pPrevFrame ); + pPrevFormatColl = dynamic_cast<SwTextFormatColl*>( + pTextFrame->GetTextNodeForParaProps()->GetFormatColl()); + } + + bool bIdenticalStyles = false; + if (pFrame && pFrame->IsTextFrame()) + { + const SwTextFrame *pTextFrame = static_cast< const SwTextFrame * >( pFrame ); + SwTextFormatColl *const pFormatColl = dynamic_cast<SwTextFormatColl*>( + pTextFrame->GetTextNodeForParaProps()->GetFormatColl()); + bIdenticalStyles = pPrevFormatColl == pFormatColl; + } + return bIdenticalStyles; +} + +static bool lcl_getContextualSpacing(const SwFrame* pPrevFrame) +{ + bool bRet; + SwBorderAttrAccess aAccess(SwFrame::GetCache(), pPrevFrame); + const SwBorderAttrs *pAttrs = aAccess.Get(); + + bRet = pAttrs->GetULSpace().GetContext(); + + return bRet; +} + + +SwTwips SwFlowFrame::CalcUpperSpace( const SwBorderAttrs *pAttrs, + const SwFrame* pPr, + const bool _bConsiderGrid ) const +{ + const SwFrame* pPrevFrame = GetPrevFrameForUpperSpaceCalc_( pPr ); + + std::optional<SwBorderAttrAccess> oAccess; + SwFrame* pOwn; + if( !pAttrs ) + { + if( m_rThis.IsSctFrame() ) + { + SwSectionFrame* pFoll = &static_cast<SwSectionFrame&>(m_rThis); + do + pOwn = pFoll->ContainsAny(); + while( !pOwn && nullptr != ( pFoll = pFoll->GetFollow() ) ); + if( !pOwn ) + return 0; + } + else + pOwn = &m_rThis; + oAccess.emplace(SwFrame::GetCache(), pOwn); + pAttrs = oAccess->Get(); + } + else + { + pOwn = &m_rThis; + } + SwTwips nUpper = 0; + + { + const IDocumentSettingAccess& rIDSA = m_rThis.GetUpper()->GetFormat()->getIDocumentSettingAccess(); + if( pPrevFrame ) + { + const bool bUseFormerLineSpacing = rIDSA.get(DocumentSettingId::OLD_LINE_SPACING); + const bool bContextualSpacingThis = pAttrs->GetULSpace().GetContext(); + const bool bContextualSpacingPrev = lcl_getContextualSpacing(pPrevFrame); + bool bIdenticalStyles = lcl_IdenticalStyles(pPrevFrame, &m_rThis); + + const bool bContextualSpacing = bContextualSpacingThis + && bContextualSpacingPrev + && bIdenticalStyles; + + // tdf#125893 always ignore own top margin setting of the actual paragraph + // with contextual spacing, if the previous paragraph is identical + const bool bHalfContextualSpacing = !bContextualSpacing + && bContextualSpacingThis + && !bContextualSpacingPrev + && bIdenticalStyles; + + // tdf#134463 always ignore own bottom margin setting of the previous paragraph + // with contextual spacing, if the actual paragraph is identical + const bool bHalfContextualSpacingPrev = !bContextualSpacing + && !bContextualSpacingThis + && bContextualSpacingPrev + && bIdenticalStyles; + + // i#11860 - use new method to determine needed spacing + // values of found previous frame and use these values. + SwTwips nPrevLowerSpace = 0; + SwTwips nPrevLineSpacing = 0; + // i#102458 + bool bPrevLineSpacingProportional = false; + GetSpacingValuesOfFrame( (*pPrevFrame), + nPrevLowerSpace, nPrevLineSpacing, + bPrevLineSpacingProportional, + bIdenticalStyles); + if( rIDSA.get(DocumentSettingId::PARA_SPACE_MAX) ) + { + // FIXME: apply bHalfContextualSpacing for better portability? + nUpper = bContextualSpacing ? 0 : nPrevLowerSpace + pAttrs->GetULSpace().GetUpper(); + SwTwips nAdd = nPrevLineSpacing; + // i#11859 - consideration of the line spacing + // for the upper spacing of a text frame + if ( bUseFormerLineSpacing ) + { + // former consideration + if ( pOwn->IsTextFrame() ) + { + nAdd = std::max( nAdd, SwTwips(static_cast<SwTextFrame*>(pOwn)->GetLineSpace()) ); + } + nUpper += nAdd; + } + else + { + // new consideration: + // Only the proportional line spacing of the previous + // text frame is considered for the upper spacing and + // the line spacing values are add up instead of + // building its maximum. + if ( pOwn->IsTextFrame() ) + { + // i#102458 + // Correction: + // A proportional line spacing of the previous text frame + // is added up to an own leading line spacing. + // Otherwise, the maximum of the leading line spacing + // of the previous text frame and the own leading line + // spacing is built. + if ( bPrevLineSpacingProportional ) + { + nAdd += static_cast<SwTextFrame*>(pOwn)->GetLineSpace( true ); + } + else + { + nAdd = std::max( nAdd, SwTwips(static_cast<SwTextFrame*>(pOwn)->GetLineSpace( true )) ); + } + } + nUpper += nAdd; + } + } + else + { + nUpper = bContextualSpacing ? 0 : std::max( + bHalfContextualSpacingPrev ? 0 : static_cast<tools::Long>(nPrevLowerSpace), + bHalfContextualSpacing ? 0 : static_cast<tools::Long>(pAttrs->GetULSpace().GetUpper()) ); + + // i#11859 - consideration of the line spacing + // for the upper spacing of a text frame + if ( bUseFormerLineSpacing ) + { + // former consideration + if ( pOwn->IsTextFrame() ) + nUpper = std::max( nUpper, SwTwips(static_cast<SwTextFrame*>(pOwn)->GetLineSpace()) ); + if ( nPrevLineSpacing != 0 ) + { + nUpper = std::max( nUpper, nPrevLineSpacing ); + } + } + else + { + // new consideration: + // Only the proportional line spacing of the previous + // text frame is considered for the upper spacing and + // the line spacing values are add up and added to + // the paragraph spacing instead of building the + // maximum of the line spacings and the paragraph spacing. + SwTwips nAdd = nPrevLineSpacing; + if ( pOwn->IsTextFrame() ) + { + // i#102458 + // Correction: + // A proportional line spacing of the previous text frame + // is added up to an own leading line spacing. + // Otherwise, the maximum of the leading line spacing + // of the previous text frame and the own leading line + // spacing is built. + if ( bPrevLineSpacingProportional ) + { + nAdd += static_cast<SwTextFrame*>(pOwn)->GetLineSpace( true ); + } + else + { + nAdd = std::max( nAdd, SwTwips(static_cast<SwTextFrame*>(pOwn)->GetLineSpace( true )) ); + } + } + nUpper += nAdd; + } + } + } + else if ( rIDSA.get(DocumentSettingId::PARA_SPACE_MAX_AT_PAGES) && + CastFlowFrame( pOwn )->HasParaSpaceAtPages( m_rThis.IsSctFrame() ) ) + { + nUpper = pAttrs->GetULSpace().GetUpper(); + } + } + + // i#25029 - pass previous frame <pPrevFrame> + // to method <GetTopLine(..)>, if parameter <pPr> is set. + // Note: parameter <pPr> is set, if method is called from <SwTextFrame::WouldFit(..)> + nUpper += pAttrs->GetTopLine( m_rThis, (pPr ? pPrevFrame : nullptr) ); + + // i#11860 - consider value of new parameter <_bConsiderGrid> + // and use new method <GetUpperSpaceAmountConsideredForPageGrid(..)> + + //consider grid in square page mode + if ( _bConsiderGrid && m_rThis.GetUpper()->GetFormat()->GetDoc()->IsSquaredPageMode() ) + { + nUpper += GetUpperSpaceAmountConsideredForPageGrid_( nUpper ); + } + return nUpper; +} + +/** method to determine the upper space amount, which is considered for + the page grid + + i#11860 + Precondition: Position of frame is valid. +*/ +SwTwips SwFlowFrame::GetUpperSpaceAmountConsideredForPageGrid_( + const SwTwips _nUpperSpaceWithoutGrid ) const +{ + SwTwips nUpperSpaceAmountConsideredForPageGrid = 0; + + if ( m_rThis.IsInDocBody() && m_rThis.GetAttrSet()->GetParaGrid().GetValue() ) + { + const SwPageFrame* pPageFrame = m_rThis.FindPageFrame(); + SwTextGridItem const*const pGrid(GetGridItem(pPageFrame)); + if( pGrid ) + { + const SwFrame* pBodyFrame = pPageFrame->FindBodyCont(); + if ( pBodyFrame ) + { + const tools::Long nGridLineHeight = + pGrid->GetBaseHeight() + pGrid->GetRubyHeight(); + + SwRectFnSet aRectFnSet(&m_rThis); + const SwTwips nBodyPrtTop = aRectFnSet.GetPrtTop(*pBodyFrame); + const SwTwips nProposedPrtTop = + aRectFnSet.YInc( aRectFnSet.GetTop(m_rThis.getFrameArea()), + _nUpperSpaceWithoutGrid ); + + const SwTwips nSpaceAbovePrtTop = + aRectFnSet.YDiff( nProposedPrtTop, nBodyPrtTop ); + const SwTwips nSpaceOfCompleteLinesAbove = + nGridLineHeight * ( nSpaceAbovePrtTop / nGridLineHeight ); + SwTwips nNewPrtTop = + aRectFnSet.YInc( nBodyPrtTop, nSpaceOfCompleteLinesAbove ); + if ( aRectFnSet.YDiff( nProposedPrtTop, nNewPrtTop ) > 0 ) + { + nNewPrtTop = aRectFnSet.YInc( nNewPrtTop, nGridLineHeight ); + } + + const SwTwips nNewUpperSpace = + aRectFnSet.YDiff( nNewPrtTop, + aRectFnSet.GetTop(m_rThis.getFrameArea()) ); + + nUpperSpaceAmountConsideredForPageGrid = + nNewUpperSpace - _nUpperSpaceWithoutGrid; + + OSL_ENSURE( nUpperSpaceAmountConsideredForPageGrid >= 0, + "<SwFlowFrame::GetUpperSpaceAmountConsideredForPageGrid(..)> - negative space considered for page grid!" ); + } + } + } + return nUpperSpaceAmountConsideredForPageGrid; +} + +/** method to determine the upper space amount, which is considered for + the previous frame + + i#11860 +*/ +SwTwips SwFlowFrame::GetUpperSpaceAmountConsideredForPrevFrame() const +{ + SwTwips nUpperSpaceAmountOfPrevFrame = 0; + + const SwFrame* pPrevFrame = GetPrevFrameForUpperSpaceCalc_(); + if ( pPrevFrame ) + { + SwTwips nPrevLowerSpace = 0; + SwTwips nPrevLineSpacing = 0; + // i#102458 + bool bDummy = false; + GetSpacingValuesOfFrame( (*pPrevFrame), nPrevLowerSpace, nPrevLineSpacing, bDummy, lcl_IdenticalStyles(pPrevFrame, &m_rThis)); + if ( nPrevLowerSpace > 0 || nPrevLineSpacing > 0 ) + { + const IDocumentSettingAccess& rIDSA = m_rThis.GetUpper()->GetFormat()->getIDocumentSettingAccess(); + if ( rIDSA.get(DocumentSettingId::PARA_SPACE_MAX) || + !rIDSA.get(DocumentSettingId::OLD_LINE_SPACING) ) + { + nUpperSpaceAmountOfPrevFrame = nPrevLowerSpace + nPrevLineSpacing; + } + else + { + nUpperSpaceAmountOfPrevFrame = std::max( nPrevLowerSpace, nPrevLineSpacing ); + } + } + } + + return nUpperSpaceAmountOfPrevFrame; +} + +/** method to determine the upper space amount, which is considered for + the previous frame and the page grid, if option 'Use former object + positioning' is OFF + + i#11860 +*/ +SwTwips SwFlowFrame::GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid() const +{ + SwTwips nUpperSpaceAmountConsideredForPrevFrameAndPageGrid = 0; + + if (!m_rThis.GetUpper() || !m_rThis.GetUpper()->GetFormat()) + { + return nUpperSpaceAmountConsideredForPrevFrameAndPageGrid; + } + + if ( !m_rThis.GetUpper()->GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::USE_FORMER_OBJECT_POS) ) + { + nUpperSpaceAmountConsideredForPrevFrameAndPageGrid = + GetUpperSpaceAmountConsideredForPrevFrame() + + ( m_rThis.GetUpper()->GetFormat()->GetDoc()->IsSquaredPageMode() + ? GetUpperSpaceAmountConsideredForPageGrid_( CalcUpperSpace( nullptr, nullptr, false ) ) + : 0 ); + } + + return nUpperSpaceAmountConsideredForPrevFrameAndPageGrid; +} + +// Calculation of lower space + +SwTwips SwFlowFrame::CalcLowerSpace( const SwBorderAttrs* _pAttrs ) const +{ + SwTwips nLowerSpace = 0; + + std::optional<SwBorderAttrAccess> oAttrAccess; + if ( !_pAttrs ) + { + oAttrAccess.emplace(SwFrame::GetCache(), &m_rThis); + _pAttrs = oAttrAccess->Get(); + } + + bool bCommonBorder = true; + if ( m_rThis.IsInSct() && m_rThis.GetUpper()->IsColBodyFrame() ) + { + const SwSectionFrame* pSectFrame = m_rThis.FindSctFrame(); + bCommonBorder = pSectFrame->GetFormat()->GetBalancedColumns().GetValue(); + } + nLowerSpace = bCommonBorder ? + _pAttrs->GetBottomLine( m_rThis ) : + _pAttrs->CalcBottomLine(); + + // i#26250 + // - correct consideration of table frames + // - use new method <CalcAddLowerSpaceAsLastInTableCell(..)> + if ( ( ( m_rThis.IsTabFrame() && m_rThis.GetUpper()->IsInTab() ) || + // No lower spacing, if frame has a follow + ( m_rThis.IsInTab() && !GetFollow() ) ) && + !m_rThis.GetIndNext() ) + { + nLowerSpace += CalcAddLowerSpaceAsLastInTableCell( _pAttrs ); + } + + // tdf#128195 Consider para spacing below last paragraph in header + bool bHasSpacingBelowPara = m_rThis.GetUpper()->GetFormat()->getIDocumentSettingAccess().get( + DocumentSettingId::HEADER_SPACING_BELOW_LAST_PARA); + if (bHasSpacingBelowPara && !m_rThis.IsInFly() && m_rThis.FindFooterOrHeader() && !GetFollow() + && !m_rThis.GetIndNext()) + nLowerSpace += _pAttrs->GetULSpace().GetLower() + _pAttrs->CalcLineSpacing(); + + return nLowerSpace; +} + +/** calculation of the additional space to be considered, if flow frame + is the last inside a table cell + + i#26250 +*/ +SwTwips SwFlowFrame::CalcAddLowerSpaceAsLastInTableCell( + const SwBorderAttrs* _pAttrs ) const +{ + SwTwips nAdditionalLowerSpace = 0; + + IDocumentSettingAccess const& rIDSA(m_rThis.GetUpper()->GetFormat()->getIDocumentSettingAccess()); + if (rIDSA.get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS)) + { + const SwFrame* pFrame = &m_rThis; + if ( pFrame->IsSctFrame() ) + { + const SwSectionFrame* pSectFrame = static_cast<const SwSectionFrame*>(pFrame); + pFrame = pSectFrame->FindLastContent(); + if ( pFrame && pFrame->IsInTab() ) + { + const SwTabFrame* pTableFrame = pFrame->FindTabFrame(); + if ( pSectFrame->IsAnLower( pTableFrame ) ) + { + pFrame = pTableFrame; + } + } + } + + std::optional<SwBorderAttrAccess> oAttrAccess; + if (pFrame && (!_pAttrs || pFrame != &m_rThis)) + { + oAttrAccess.emplace(SwFrame::GetCache(), pFrame); + _pAttrs = oAttrAccess->Get(); + } + + if (_pAttrs) + { + nAdditionalLowerSpace += _pAttrs->GetULSpace().GetLower(); + + if (rIDSA.get(DocumentSettingId::ADD_PARA_LINE_SPACING_TO_TABLE_CELLS)) + { + nAdditionalLowerSpace += _pAttrs->CalcLineSpacing(); + } + } + } + + return nAdditionalLowerSpace; +} + +/// Moves the Frame forward if it seems necessary regarding the current conditions and attributes. +bool SwFlowFrame::CheckMoveFwd( bool& rbMakePage, bool bKeep, bool bIgnoreMyOwnKeepValue ) +{ + const SwFrame* pNxt = m_rThis.GetIndNext(); + + if ( bKeep && //!bMovedBwd && + ( !pNxt || ( pNxt->IsTextFrame() && static_cast<const SwTextFrame*>(pNxt)->IsEmptyMaster() ) ) && + ( nullptr != (pNxt = m_rThis.FindNext()) ) && IsKeepFwdMoveAllowed(bIgnoreMyOwnKeepValue) ) + { + if( pNxt->IsSctFrame() ) + { // Don't get fooled by empty SectionFrames + const SwFrame* pTmp = nullptr; + while( pNxt && pNxt->IsSctFrame() && + ( !static_cast<const SwSectionFrame*>(pNxt)->GetSection() || + nullptr == ( pTmp = static_cast<const SwSectionFrame*>(pNxt)->ContainsAny() ) ) ) + { + pNxt = pNxt->FindNext(); + pTmp = nullptr; + } + if( pTmp ) + pNxt = pTmp; // the content of the next notempty sectionfrm + } + if( pNxt && pNxt->isFrameAreaPositionValid() ) + { + bool bMove = false; + const SwSectionFrame *pSct = m_rThis.FindSctFrame(); + if( pSct && !pSct->isFrameAreaSizeValid() ) + { + const SwSectionFrame* pNxtSct = pNxt->FindSctFrame(); + if( pNxtSct && pSct->IsAnFollow( pNxtSct ) ) + bMove = true; + } + else + bMove = true; + if( bMove ) + { + //Keep together with the following frame + MoveFwd( rbMakePage, false ); + return true; + } + } + } + + bool bMovedFwd = false; + + if ( m_rThis.GetIndPrev() ) + { + if ( IsPrevObjMove() ) // Should we care about objects of the Prev? + { + bMovedFwd = true; + if ( !MoveFwd( rbMakePage, false ) ) + rbMakePage = false; + } + else + { + if ( IsPageBreak( false ) ) + { + while ( MoveFwd( rbMakePage, true ) ) + /* do nothing */; + rbMakePage = false; + bMovedFwd = true; + } + else if ( IsColBreak ( false ) ) + { + const SwPageFrame *pPage = m_rThis.FindPageFrame(); + SwFrame *pCol = m_rThis.FindColFrame(); + do + { MoveFwd( rbMakePage, false ); + SwFrame *pTmp = m_rThis.FindColFrame(); + if( pTmp != pCol ) + { + bMovedFwd = true; + pCol = pTmp; + } + else + break; + } while ( IsColBreak( false ) ); + if ( pPage != m_rThis.FindPageFrame() ) + rbMakePage = false; + } + } + } + return bMovedFwd; +} + +bool SwFlowFrame::ForbiddenForFootnoteCntFwd() const +{ + return m_rThis.IsTabFrame() || m_rThis.IsInTab(); +} + +/// Return value guarantees that a new page was not created, +/// although false does not NECESSARILY indicate that a new page was created. +/// Either false or true(MoveFootnoteCntFwd) can be returned if no changes were made +bool SwFlowFrame::MoveFwd( bool bMakePage, bool bPageBreak, bool bMoveAlways ) +{ +//!!!!MoveFootnoteCntFwd might need to be updated as well. + SwFootnoteBossFrame *pOldBoss = m_rThis.FindFootnoteBossFrame(); + if (m_rThis.IsInFootnote()) + { + assert(!ForbiddenForFootnoteCntFwd()); // prevented by IsMoveable() + if (!m_rThis.IsContentFrame() || !pOldBoss) + { + SAL_WARN("sw.core", "Tables in footnotes are not truly supported"); + return false; + } + return static_cast<SwContentFrame&>(m_rThis).MoveFootnoteCntFwd( bMakePage, pOldBoss ); + } + + if( !IsFwdMoveAllowed() && !bMoveAlways ) + { + bool bNoFwd = true; + if( m_rThis.IsInSct() ) + { + SwFootnoteBossFrame* pBoss = m_rThis.FindFootnoteBossFrame(); + bNoFwd = !pBoss->IsInSct() || ( !pBoss->Lower()->GetNext() && + !pBoss->GetPrev() ); + } + + // Allow the MoveFwd even if we do not have an IndPrev in these cases: + if ( m_rThis.IsInTab() && + ( !m_rThis.IsTabFrame() || + m_rThis.GetUpper()->IsInTab() ) && + nullptr != m_rThis.GetNextCellLeaf() ) + { + bNoFwd = false; + } + + if( bNoFwd ) + { + // It's allowed to move PageBreaks if the Frame isn't the first + // one on the page. + if ( !bPageBreak ) + return false; + + const SwFrame *pCol = m_rThis.FindColFrame(); + if ( !pCol || !pCol->GetPrev() ) + return false; + } + } + + std::optional<SwFrameDeleteGuard> oDeleteGuard; + if (bMakePage) + oDeleteGuard.emplace(pOldBoss); + + bool bSamePage = true; + SwLayoutFrame *pNewUpper = + m_rThis.GetLeaf( bMakePage ? MAKEPAGE_INSERT : MAKEPAGE_NONE, true ); + + if ( pNewUpper ) + { + PROTOCOL_ENTER( &m_rThis, PROT::MoveFwd, DbgAction::NONE, nullptr ); + SwPageFrame *pOldPage = pOldBoss->FindPageFrame(); + // We move ourself and all the direct successors before the + // first ContentFrame below the new Upper. + + // If our NewUpper lies in a SectionFrame, we need to make sure + // that it won't destroy itself in Calc. + SwSectionFrame* pSect = pNewUpper->FindSctFrame(); + if( pSect ) + { + // If we only switch column within our SectionFrame, we better don't + // call Calc, as this would format the SectionFrame, which in turn would + // call us again, etc. + if( pSect != m_rThis.FindSctFrame() ) + { + bool bUnlock = !pSect->IsColLocked(); + pSect->ColLock(); + pNewUpper->Calc(m_rThis.getRootFrame()->GetCurrShell()->GetOut()); + if( bUnlock ) + pSect->ColUnlock(); + } + } + // Do not calculate split cell frames. + else if ( !pNewUpper->IsCellFrame() || pNewUpper->Lower() ) + pNewUpper->Calc(m_rThis.getRootFrame()->GetCurrShell()->GetOut()); + + SwFootnoteBossFrame *pNewBoss = pNewUpper->FindFootnoteBossFrame(); + bool bBossChg = pNewBoss != pOldBoss; + pNewBoss = pNewBoss->FindFootnoteBossFrame( true ); + pOldBoss = pOldBoss->FindFootnoteBossFrame( true ); + SwPageFrame* pNewPage = pOldPage; + + oDeleteGuard.reset(); + + // First, we move the footnotes. + bool bFootnoteMoved = false; + + // i#26831 + // If pSect has just been created, the printing area of pSect has + // been calculated based on the first content of its follow. + // In this case we prefer to call a SimpleFormat for this new + // section after we inserted the contents. Otherwise the section + // frame will invalidate its lowers, if its printing area changes + // in SwSectionFrame::Format, which can cause loops. + const bool bForceSimpleFormat = pSect && pSect->HasFollow() && + !pSect->ContainsAny(); + + if ( pNewBoss != pOldBoss ) + { + pNewPage = pNewBoss->FindPageFrame(); + bSamePage = pNewPage == pOldPage; + // Set deadline, so the footnotes don't think up + // silly things... + SwRectFnSet aRectFnSet(pOldBoss); + SwSaveFootnoteHeight aHeight( pOldBoss, + aRectFnSet.GetBottom(pOldBoss->getFrameArea()) ); + SwContentFrame* pStart = m_rThis.IsContentFrame() ? + static_cast<SwContentFrame*>(&m_rThis) : static_cast<SwLayoutFrame&>(m_rThis).ContainsContent(); + OSL_ENSURE( pStart || ( m_rThis.IsTabFrame() && !static_cast<SwTabFrame&>(m_rThis).Lower() ), + "MoveFwd: Missing Content" ); + SwLayoutFrame* pBody = pStart ? ( pStart->IsTextFrame() ? + const_cast<SwBodyFrame *>(static_cast<SwTextFrame*>(pStart)->FindBodyFrame()) : nullptr ) : nullptr; + if( pBody ) + bFootnoteMoved = pBody->MoveLowerFootnotes( pStart, pOldBoss, pNewBoss, + false); + } + // It's possible when dealing with SectionFrames that we have been moved + // by pNewUpper->Calc(), for instance into the pNewUpper. + // MoveSubTree or PasteTree respectively is not prepared to handle such a + // situation. + if( pNewUpper != m_rThis.GetUpper() ) + { + // i#27145 + SwSectionFrame* pOldSct = nullptr; + if ( m_rThis.GetUpper()->IsSctFrame() ) + { + pOldSct = static_cast<SwSectionFrame*>(m_rThis.GetUpper()); + } + + MoveSubTree( pNewUpper, pNewUpper->Lower() ); + + // i#27145 + if ( pOldSct && pOldSct->GetSection() ) + { + // Prevent loops by setting the new height at + // the section frame if footnotes have been moved. + // Otherwise the call of SwLayNotify::~SwLayNotify() for + // the (invalid) section frame will invalidate the first + // lower of its follow, because it grows due to the removed + // footnotes. + // Note: If pOldSct has become empty during MoveSubTree, it + // has already been scheduled for removal. No SimpleFormat + // for these. + pOldSct->SimpleFormat(); + } + + // i#26831 + if ( bForceSimpleFormat ) + { + pSect->SimpleFormat(); + } + + if ( bFootnoteMoved && !bSamePage ) + { + pOldPage->UpdateFootnoteNum(); + pNewPage->UpdateFootnoteNum(); + } + + if( bBossChg ) + { + m_rThis.Prepare( PrepareHint::BossChanged, nullptr, false ); + if( !bSamePage ) + { + SwViewShell *pSh = m_rThis.getRootFrame()->GetCurrShell(); + if ( pSh && !pSh->Imp()->IsUpdateExpFields() ) + pSh->GetDoc()->getIDocumentFieldsAccess().SetNewFieldLst(true); // Will be done by CalcLayout() later on! + + pNewPage->InvalidateSpelling(); + pNewPage->InvalidateSmartTags(); + pNewPage->InvalidateAutoCompleteWords(); + pNewPage->InvalidateWordCount(); + } + } + } + // No <CheckPageDesc(..)> in online layout + const SwViewShell *pSh = m_rThis.getRootFrame()->GetCurrShell(); + + if ( !( pSh && pSh->GetViewOptions()->getBrowseMode() ) ) + { + // i#106452 + // check page description not only in situation with sections. + if ( !bSamePage && + ( m_rThis.GetPageDescItem().GetPageDesc() || + pOldPage->GetPageDesc()->GetFollow() != pNewPage->GetPageDesc() ) ) + { + SwFrame::CheckPageDescs( pNewPage, false ); + } + } + } + return bSamePage; +} + +/** Return value tells whether any changes have been made. + * If true, the frame has moved backwards to an earlier column/section/frame/page etc. + * + * @note This should be called by derived classes. + * @note The actual moving must be implemented in the subclasses via Cut()/Paste(). + */ +bool SwFlowFrame::MoveBwd( bool &rbReformat ) +{ + SwFlowFrame::SetMoveBwdJump( false ); + + SwFootnoteFrame* pFootnote = m_rThis.FindFootnoteFrame(); + if ( pFootnote && pFootnote->IsBackMoveLocked() ) + return false; + + // Text frames, which are directly inside + // tables aren't allowed to move backward. + if ( m_rThis.IsTextFrame() && m_rThis.IsInTab() ) + { + const SwLayoutFrame* pUpperFrame = m_rThis.GetUpper(); + while ( pUpperFrame ) + { + if ( pUpperFrame->IsTabFrame() || pUpperFrame->IsRowFrame() ) + { + return false; + } + // If the text frame is a follow-section-in-table, that can move + // backward as well. + bool bIsFollowSection = pUpperFrame->IsSctFrame() && static_cast<const SwSectionFrame*>(pUpperFrame)->GetPrecede(); + + // If the text frame is a follow-in-table, that can move + // backward as well. + bool bIsFollow = const_cast<SwLayoutFrame*>(pUpperFrame)->GetPrevCellLeaf(); + + if ( ( pUpperFrame->IsColumnFrame() && pUpperFrame->IsInSct() ) || bIsFollowSection || bIsFollow ) + { + break; + } + pUpperFrame = pUpperFrame->GetUpper(); + } + } + + SwFootnoteBossFrame * pOldBoss = m_rThis.FindFootnoteBossFrame(); + if (!pOldBoss) + return false; + + SwPageFrame * const pOldPage = pOldBoss->FindPageFrame(); + SwLayoutFrame *pNewUpper = nullptr; + bool bCheckPageDescs = false; + bool bCheckPageDescOfNextPage = false; + + if ( pFootnote ) + { + // If the footnote already sits on the same page/column as the reference, + // we can't flow back. The breaks don't need to be checked for footnotes. + + // i#37084 FindLastContent does not necessarily + // have to have a result != 0 + SwFrame* pRef = nullptr; + const bool bEndnote = pFootnote->GetAttr()->GetFootnote().IsEndNote(); + const IDocumentSettingAccess& rSettings + = pFootnote->GetAttrSet()->GetDoc()->getIDocumentSettingAccess(); + if( bEndnote && pFootnote->IsInSct() ) + { + SwSectionFrame* pSect = pFootnote->FindSctFrame(); + if( pSect->IsEndnAtEnd() ) + // Endnotes at the end of the section. + pRef = pSect->FindLastContent( SwFindMode::LastCnt ); + } + else if (bEndnote && rSettings.get(DocumentSettingId::CONTINUOUS_ENDNOTES)) + { + // Endnotes at the end of the document. + SwPageFrame* pPage = m_rThis.getRootFrame()->GetLastPage(); + pRef = pPage->FindLastBodyContent(); + } + if( !pRef ) + // Endnotes on a separate page. + pRef = pFootnote->GetRef(); + + OSL_ENSURE( pRef, "MoveBwd: Endnote for an empty section?" ); + + if( !bEndnote ) + pOldBoss = pOldBoss->FindFootnoteBossFrame( true ); + SwFootnoteBossFrame *pRefBoss = pRef->FindFootnoteBossFrame( !bEndnote ); + if ( pOldBoss != pRefBoss && + + ( !bEndnote || + pRefBoss->IsBefore( pOldBoss ) ) + ) + pNewUpper = m_rThis.GetLeaf( MAKEPAGE_FTN, false ); + } + else if ( IsPageBreak( true ) ) // Do we have to respect a PageBreak? + { + // If the previous page doesn't have a Frame in the body, + // flowing back makes sense despite the PageBreak (otherwise, + // we'd get an empty page). + // Of course we need to overlook empty pages! + const SwFrame *pFlow = &m_rThis; + do + { + pFlow = pFlow->FindPrev(); + } while ( pFlow && + ( pFlow->FindPageFrame() == pOldPage || + !pFlow->IsInDocBody() ) ); + if ( pFlow ) + { + tools::Long nDiff = pOldPage->GetPhyPageNum() - pFlow->GetPhyPageNum(); + if ( nDiff > 1 ) + { + if ( static_cast<SwPageFrame*>(pOldPage->GetPrev())->IsEmptyPage() ) + nDiff -= 1; + if ( nDiff > 1 ) + { + pNewUpper = m_rThis.GetLeaf( MAKEPAGE_NONE, false ); + // i#53139 + // Now <pNewUpper> is a previous layout frame, which contains + // content. But the new upper layout frame has to be the next one. + // Thus, hack for issue i14206 no longer needed, but fix for issue 114442 + // Correct fix for i53139 + // Check for wrong page description before using next new upper. + // i#66051 - further correction of fix for i53139 + // Check for correct type of new next upper layout frame + // Another correction of fix for i53139 + // Assumption, that in all cases <pNewUpper> is a previous + // layout frame, which contains content, is wrong. + // Another correction of fix for i53139 + // Beside type check, check also, if proposed new next upper + // frame is inside the same frame types. + // i#73194 - and yet another correction + // of fix for i53139: + // Assure that the new next upper layout frame doesn't + // equal the current one. + // E.g.: content is on page 3, on page 2 is only a 'ghost' + // section and on page 1 is normal content. Method <FindPrev(..)> + // will find the last content of page 1, but <GetLeaf(..)> + // returns new upper on page 2. + if (pNewUpper && pNewUpper->Lower()) + { + SwLayoutFrame* pNewNextUpper = pNewUpper->GetLeaf( MAKEPAGE_NONE, true ); + if ( pNewNextUpper && + pNewNextUpper != m_rThis.GetUpper() && + pNewNextUpper->GetType() == pNewUpper->GetType() && + pNewNextUpper->IsInDocBody() == pNewUpper->IsInDocBody() && + pNewNextUpper->IsInFootnote() == pNewUpper->IsInFootnote() && + pNewNextUpper->IsInTab() == pNewUpper->IsInTab() && + pNewNextUpper->IsInSct() == pNewUpper->IsInSct() && + !m_rThis.WrongPageDesc( pNewNextUpper->FindPageFrame() ) ) + { + pNewUpper = pNewNextUpper; + bCheckPageDescOfNextPage = true; + } + } + + bCheckPageDescs = true; + } + } + } + } + else if ( IsColBreak( true ) ) + { + // If the previous column doesn't contain a ContentFrame, flowing back + // makes sense despite the ColumnBreak, as otherwise we'd get + // an empty column. + if( m_rThis.IsInSct() ) + { + pNewUpper = m_rThis.GetLeaf( MAKEPAGE_NONE, false ); + if( pNewUpper && !SwFlowFrame::IsMoveBwdJump() && + ( pNewUpper->ContainsContent() || + ( ( !pNewUpper->IsColBodyFrame() || + !pNewUpper->GetUpper()->GetPrev() ) && + !pNewUpper->FindSctFrame()->GetPrev() ) ) ) + { + pNewUpper = nullptr; + } + // i#53139 + // i#69409 - check <pNewUpper> + // i#71065 - check <SwFlowFrame::IsMoveBwdJump()> + else if ( pNewUpper && !SwFlowFrame::IsMoveBwdJump() ) + { + // Now <pNewUpper> is a previous layout frame, which + // contains content. But the new upper layout frame + // has to be the next one. + // Correct fix for i53139 + // Check for wrong page description before using next new upper. + // i#66051 - further correction of fix for i53139 + // Check for correct type of new next upper layout frame + // Another correction of fix for i53139 + // Beside type check, check also, if proposed new next upper + // frame is inside the same frame types. + SwLayoutFrame* pNewNextUpper = pNewUpper->GetLeaf( MAKEPAGE_NOSECTION, true ); + if ( pNewNextUpper && + pNewNextUpper->GetType() == pNewUpper->GetType() && + pNewNextUpper->IsInDocBody() == pNewUpper->IsInDocBody() && + pNewNextUpper->IsInFootnote() == pNewUpper->IsInFootnote() && + pNewNextUpper->IsInTab() == pNewUpper->IsInTab() && + pNewNextUpper->IsInSct() == pNewUpper->IsInSct() && + !m_rThis.WrongPageDesc( pNewNextUpper->FindPageFrame() ) ) + { + pNewUpper = pNewNextUpper; + } + } + } + else + { + const SwFrame *pCol = m_rThis.FindColFrame(); + assert(pCol); + bool bGoOn = true; + bool bJump = false; + do + { + if ( pCol->GetPrev() ) + pCol = pCol->GetPrev(); + else + { + bGoOn = false; + pCol = m_rThis.GetLeaf( MAKEPAGE_NONE, false ); + } + if ( pCol ) + { + // ColumnFrames now with BodyFrame + SwLayoutFrame* pColBody = pCol->IsColumnFrame() ? + const_cast<SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(pCol)->Lower())) : + const_cast<SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(pCol)); + if ( pColBody->ContainsContent() ) + { + bGoOn = false; // We have content here! we accept this + // only if GetLeaf() has set the MoveBwdJump. + if( SwFlowFrame::IsMoveBwdJump() ) + { + pNewUpper = pColBody; + // i#53139 + // Now <pNewUpper> is a previous layout frame, which + // contains content. But the new upper layout frame + // has to be the next one. + // Correct fix for i53139 + // Check for wrong page description before using next new upper. + // i#66051 - further correction of fix for i53139 + // Check for correct type of new next upper layout frame + // Another correction of fix for i53139 + // Beside type check, check also, if proposed new next upper + // frame is inside the same frame types. + // i#71065 + // Check that the proposed new next upper layout + // frame isn't the current one. + SwLayoutFrame* pNewNextUpper = pNewUpper->GetLeaf( MAKEPAGE_NONE, true ); + if ( pNewNextUpper && + pNewNextUpper != m_rThis.GetUpper() && + pNewNextUpper->GetType() == pNewUpper->GetType() && + pNewNextUpper->IsInDocBody() == pNewUpper->IsInDocBody() && + pNewNextUpper->IsInFootnote() == pNewUpper->IsInFootnote() && + pNewNextUpper->IsInTab() == pNewUpper->IsInTab() && + pNewNextUpper->IsInSct() == pNewUpper->IsInSct() && + !m_rThis.WrongPageDesc( pNewNextUpper->FindPageFrame() ) ) + { + pNewUpper = pNewNextUpper; + } + } + } + else + { + if( pNewUpper ) // We already had an empty column, in other + bJump = true; // words we skipped one. + pNewUpper = pColBody; // this empty column could be considered, + // but we continue searching nevertheless. + } + } + } while( bGoOn ); + if( bJump ) + SwFlowFrame::SetMoveBwdJump( true ); + } + } + else // No breaks - we can flow back. + pNewUpper = m_rThis.GetLeaf( MAKEPAGE_NONE, false ); + + // i#27801 - no move backward of 'master' text frame, + // if - due to its object positioning - it isn't allowed to be on the new page frame + // i#44049 - add another condition for not moving backward + // If one of its objects has restarted the layout process, moving backward + // isn't sensible either. + // i#47697 - refine condition made for issue i44049 + // - allow move backward as long as the anchored object is only temporarily + // positions considering its wrapping style. + if ( pNewUpper && + m_rThis.IsTextFrame() && !IsFollow() ) + { + sal_uInt32 nToPageNum( 0 ); + const bool bMoveFwdByObjPos = SwLayouter::FrameMovedFwdByObjPos( + *(pOldPage->GetFormat()->GetDoc()), + static_cast<SwTextFrame&>(m_rThis), + nToPageNum ); + if ( bMoveFwdByObjPos && + pNewUpper->FindPageFrame()->GetPhyPageNum() < nToPageNum ) + { + pNewUpper = nullptr; + } + // i#44049 - check, if one of its anchored objects + // has restarted the layout process. + else if ( m_rThis.GetDrawObjs() ) + { + for (SwAnchoredObject* pAnchoredObj : *m_rThis.GetDrawObjs()) + { + // i#47697 - refine condition - see above + if ( pAnchoredObj->RestartLayoutProcess() && + !pAnchoredObj->IsTmpConsiderWrapInfluence() ) + { + pNewUpper = nullptr; + break; + } + } + } + } + + // With Follows, it's only allowed to flow back if there's no neighbor + // in the new environment (because that would be the Master). + // (6677) If however we skipped empty pages, we still have to move. + if ( pNewUpper && IsFollow() && pNewUpper->Lower() ) + { + // i#79774 + // neglect empty sections in proposed new upper frame + bool bProposedNewUpperContainsOnlyEmptySections( true ); + { + const SwFrame* pLower( pNewUpper->Lower() ); + while ( pLower ) + { + if ( pLower->IsSctFrame() && + !dynamic_cast<const SwSectionFrame&>(*pLower).GetSection() ) + { + pLower = pLower->GetNext(); + continue; + } + else + { + bProposedNewUpperContainsOnlyEmptySections = false; + break; + } + } + } + if ( !bProposedNewUpperContainsOnlyEmptySections ) + { + if ( SwFlowFrame::IsMoveBwdJump() ) + { + // Don't move after the Master, but into the next empty page. + SwFrame *pFrame = pNewUpper->Lower(); + while ( pFrame->GetNext() ) + pFrame = pFrame->GetNext(); + pNewUpper = pFrame->GetLeaf( MAKEPAGE_INSERT, true ); + if( pNewUpper == m_rThis.GetUpper() ) // Did we end up in the same place? + pNewUpper = nullptr; // If so, moving is not needed. + } + else + pNewUpper = nullptr; + } + } + if ( pNewUpper && !ShouldBwdMoved( pNewUpper, rbReformat ) ) + { + if( !pNewUpper->Lower() ) + { + if( pNewUpper->IsFootnoteContFrame() ) + { + pNewUpper->Cut(); + SwFrame::DestroyFrame(pNewUpper); + } + else + { + SwSectionFrame* pSectFrame = pNewUpper->FindSctFrame(); + + if ( pSectFrame && !pSectFrame->IsColLocked() && + !pSectFrame->ContainsContent() && !pSectFrame->ContainsAny( true ) ) + { + pSectFrame->DelEmpty( true ); + SwFrame::DestroyFrame(pSectFrame); + m_rThis.setFrameAreaPositionValid(true); + } + } + } + pNewUpper = nullptr; + } + + // i#21478 - don't move backward, if flow frame wants to + // keep with next frame and next frame is locked. + // i#38232 - If next frame is a table, do *not* check, + // if it's locked. + if ( pNewUpper && !IsFollow() && + m_rThis.GetAttrSet()->GetKeep().GetValue() && m_rThis.GetIndNext() ) + { + SwFrame* pIndNext = m_rThis.GetIndNext(); + // i#38232 + if ( !pIndNext->IsTabFrame() ) + { + // get first content of section, while empty sections are skipped + while ( pIndNext && pIndNext->IsSctFrame() ) + { + if( static_cast<SwSectionFrame*>(pIndNext)->GetSection() ) + { + SwFrame* pTmp = static_cast<SwSectionFrame*>(pIndNext)->ContainsAny(); + if ( pTmp ) + { + pIndNext = pTmp; + break; + } + } + pIndNext = pIndNext->GetIndNext(); + } + OSL_ENSURE( !pIndNext || dynamic_cast<const SwTextFrame*>( pIndNext) != nullptr, + "<SwFlowFrame::MovedBwd(..)> - incorrect next found." ); + if ( pIndNext && pIndNext->IsFlowFrame() && + SwFlowFrame::CastFlowFrame(pIndNext)->IsJoinLocked() ) + { + pNewUpper = nullptr; + } + } + } + + // i#65250 + // layout loop control for flowing content again and again moving + // backward under the same layout condition. + if ( pNewUpper && !IsFollow() && + pNewUpper != m_rThis.GetUpper() && + SwLayouter::MoveBwdSuppressed( *(pOldPage->GetFormat()->GetDoc()), + *this, *pNewUpper ) ) + { + SwLayoutFrame* pNextNewUpper = pNewUpper->GetLeaf( + ( !m_rThis.IsSctFrame() && m_rThis.IsInSct() ) + ? MAKEPAGE_NOSECTION + : MAKEPAGE_NONE, + true ); + // i#73194 - make code robust + OSL_ENSURE( pNextNewUpper, "<SwFlowFrame::MoveBwd(..)> - missing next new upper" ); + if ( pNextNewUpper && + ( pNextNewUpper == m_rThis.GetUpper() || + pNextNewUpper->GetType() != m_rThis.GetUpper()->GetType() ) ) + { + // tdf#107398 do not leave empty footnote container around + if (!pNewUpper->Lower() && pNewUpper->IsFootnoteContFrame()) + { + pNewUpper->Cut(); + SwFrame::DestroyFrame(pNewUpper); + } + pNewUpper = nullptr; + OSL_FAIL( "<SwFlowFrame::MoveBwd(..)> - layout loop control for layout action <Move Backward> applied!" ); + } + } + + OSL_ENSURE( pNewUpper != m_rThis.GetUpper(), + "<SwFlowFrame::MoveBwd(..)> - moving backward to the current upper frame!?" ); + if ( pNewUpper ) + { + PROTOCOL_ENTER( &m_rThis, PROT::MoveBack, DbgAction::NONE, nullptr ); + if ( pNewUpper->IsFootnoteContFrame() ) + { + // I may have gotten a Container + SwFootnoteFrame *pNew = SwFootnoteContFrame::PrependChained(&m_rThis, false); + pNew->Paste( pNewUpper ); + pNewUpper = pNew; + } + if( pNewUpper->IsFootnoteFrame() && m_rThis.IsInSct() ) + { + SwSectionFrame* pSct = m_rThis.FindSctFrame(); + // If we're in a section of a footnote, we may need to create + // a SwSectionFrame in the new upper + if( pSct->IsInFootnote() ) + { + SwFrame* pTmp = pNewUpper->Lower(); + if( pTmp ) + { + while( pTmp->GetNext() ) + pTmp = pTmp->GetNext(); + if( !pTmp->IsSctFrame() || + static_cast<SwSectionFrame*>(pTmp)->GetFollow() != pSct ) + pTmp = nullptr; + } + if( pTmp ) + pNewUpper = static_cast<SwSectionFrame*>(pTmp); + else + { + pSct = new SwSectionFrame( *pSct, true ); + pSct->Paste( pNewUpper ); + pSct->Init(); + pNewUpper = pSct; + pSct->SimpleFormat(); + } + } + } + bool bUnlock = false; + bool bFollow = false; + // Lock section. Otherwise, it could get destroyed if the only Content + // moves e.g. from the second into the first column. + SwSectionFrame* pSect = pNewUpper->FindSctFrame(); + if( pSect ) + { + bUnlock = !pSect->IsColLocked(); + pSect->ColLock(); + bFollow = pSect->HasFollow(); + } + + { + auto const pOld = m_rThis.GetUpper(); + ::std::optional<SwFrameDeleteGuard> g; + if (m_rThis.GetUpper()->IsCellFrame()) + { + // note: IsFollowFlowRow() is never set for new-style tables + SwTabFrame const*const pTabFrame(m_rThis.FindTabFrame()); + if ( pTabFrame->IsFollow() + && static_cast<SwTabFrame const*>(pTabFrame->GetPrecede())->HasFollowFlowLine() + && pTabFrame->GetFirstNonHeadlineRow() == m_rThis.GetUpper()->GetUpper()) + { + // lock follow-flow-row (similar to sections above) + g.emplace(m_rThis.GetUpper()->GetUpper()); + assert(m_rThis.GetUpper()->GetUpper()->IsDeleteForbidden()); + } + } + pNewUpper->Calc(m_rThis.getRootFrame()->GetCurrShell()->GetOut()); + SAL_WARN_IF(pOld != m_rThis.GetUpper(), "sw.core", + "MoveBwd(): pNewUpper->Calc() moved this frame?"); + } + + m_rThis.Cut(); + + // optimization: format section, if its size is invalidated and if it's + // the new parent of moved backward frame. + bool bFormatSect( false ); + if( bUnlock ) + { + pSect->ColUnlock(); + if( pSect->HasFollow() != bFollow ) + { + pSect->InvalidateSize(); + // - optimization + if ( pSect == pNewUpper ) + bFormatSect = true; + } + } + + m_rThis.Paste( pNewUpper ); + // - optimization + if ( bFormatSect ) + pSect->Calc(m_rThis.getRootFrame()->GetCurrShell()->GetOut()); + + SwPageFrame *pNewPage = m_rThis.FindPageFrame(); + if( pNewPage != pOldPage ) + { + m_rThis.Prepare( PrepareHint::BossChanged, static_cast<const void*>(pOldPage), false ); + SwViewShell *pSh = m_rThis.getRootFrame()->GetCurrShell(); + if ( pSh && !pSh->Imp()->IsUpdateExpFields() ) + pSh->GetDoc()->getIDocumentFieldsAccess().SetNewFieldLst(true); // Will be done by CalcLayout() later on + + pNewPage->InvalidateSpelling(); + pNewPage->InvalidateSmartTags(); + pNewPage->InvalidateAutoCompleteWords(); + pNewPage->InvalidateWordCount(); + + // No <CheckPageDesc(..)> in online layout + if ( !( pSh && pSh->GetViewOptions()->getBrowseMode() ) ) + { + if ( bCheckPageDescs && pNewPage->GetNext() ) + { + SwPageFrame* pStartPage = bCheckPageDescOfNextPage ? + pNewPage : + static_cast<SwPageFrame*>(pNewPage->GetNext()); + SwFrame::CheckPageDescs( pStartPage, false); + } + else if (m_rThis.GetPageDescItem().GetPageDesc()) + { + // First page could get empty for example by disabling + // a section + SwFrame::CheckPageDescs( pNewPage, false); + } + } + } + } + return pNewUpper != nullptr; +} + +SwFlowFrame *SwFlowFrame::CastFlowFrame( SwFrame *pFrame ) +{ + if ( pFrame->IsContentFrame() ) + return static_cast<SwContentFrame*>(pFrame); + if ( pFrame->IsTabFrame() ) + return static_cast<SwTabFrame*>(pFrame); + if ( pFrame->IsSctFrame() ) + return static_cast<SwSectionFrame*>(pFrame); + return nullptr; +} + +const SwFlowFrame *SwFlowFrame::CastFlowFrame( const SwFrame *pFrame ) +{ + if ( pFrame->IsContentFrame() ) + return static_cast<const SwContentFrame*>(pFrame); + if ( pFrame->IsTabFrame() ) + return static_cast<const SwTabFrame*>(pFrame); + if ( pFrame->IsSctFrame() ) + return static_cast<const SwSectionFrame*>(pFrame); + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/fly.cxx b/sw/source/core/layout/fly.cxx new file mode 100644 index 000000000..99f976eab --- /dev/null +++ b/sw/source/core/layout/fly.cxx @@ -0,0 +1,2997 @@ +/* -*- 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 <config_wasm_strip.h> + +#include <svl/itemiter.hxx> +#include <vcl/imap.hxx> +#include <tools/helpers.hxx> +#include <editeng/protitem.hxx> +#include <editeng/opaqitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <fmtfsize.hxx> +#include <fmtclds.hxx> +#include <fmtcntnt.hxx> +#include <fmturl.hxx> +#include <fmtsrnd.hxx> +#include <fmtornt.hxx> +#include <fmtcnct.hxx> +#include <ndgrf.hxx> +#include <tolayoutanchoredobjectposition.hxx> +#include <fmtfollowtextflow.hxx> +#include <sortedobjs.hxx> +#include <objectformatter.hxx> +#include <ndole.hxx> +#include <swtable.hxx> +#include <svx/svdoashp.hxx> +#include <layouter.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <viewimp.hxx> +#include <viewopt.hxx> +#include <dcontact.hxx> +#include <dflyobj.hxx> +#include <dview.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <hints.hxx> +#include <tabfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <flyfrms.hxx> +#include <sectfrm.hxx> +#include <vcl/svapp.hxx> +#include <calbck.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <textboxhelper.hxx> +#include <txtfly.hxx> +#include <ndindex.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <osl/diagnose.h> + +#include <wrtsh.hxx> +#include <view.hxx> +#include <edtwin.hxx> +#include <bodyfrm.hxx> +#include <FrameControlsManager.hxx> +#include <ndtxt.hxx> + +using namespace ::com::sun::star; + +static SwTwips lcl_CalcAutoWidth( const SwLayoutFrame& rFrame ); + +SwFlyFrame::SwFlyFrame( SwFlyFrameFormat *pFormat, SwFrame* pSib, SwFrame *pAnch ) : + SwLayoutFrame( pFormat, pSib ), + // #i26791# + m_pPrevLink( nullptr ), + m_pNextLink( nullptr ), + m_bInCnt( false ), + m_bAtCnt( false ), + m_bLayout( false ), + m_bAutoPosition( false ), + m_bDeleted( false ), + m_nAuthor( std::string::npos ), + m_bValidContentPos( false ) +{ + mnFrameType = SwFrameType::Fly; + + m_bInvalid = m_bNotifyBack = true; + m_bLocked = m_bMinHeight = + m_bHeightClipped = m_bWidthClipped = m_bFormatHeightOnly = false; + + // Size setting: Fixed size is always the width + const SwFormatFrameSize &rFrameSize = pFormat->GetFrameSize(); + const SvxFrameDirection nDir = pFormat->GetFormatAttr( RES_FRAMEDIR ).GetValue(); + if( SvxFrameDirection::Environment == nDir ) + { + mbDerivedVert = true; + mbDerivedR2L = true; + } + else + { + mbInvalidVert = false; + mbDerivedVert = false; + mbDerivedR2L = false; + if( SvxFrameDirection::Horizontal_LR_TB == nDir || SvxFrameDirection::Horizontal_RL_TB == nDir ) + { + mbVertLR = false; + mbVertical = false; + } + else + { + const SwViewShell *pSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + { + mbVertLR = false; + mbVertical = false; + } + else + { + mbVertical = true; + + if ( SvxFrameDirection::Vertical_LR_TB == nDir ) + mbVertLR = true; + else if (nDir == SvxFrameDirection::Vertical_LR_BT) + { + mbVertLR = true; + mbVertLRBT = true; + } + else + mbVertLR = false; + } + } + + mbInvalidR2L = false; + if( SvxFrameDirection::Horizontal_RL_TB == nDir ) + mbRightToLeft = true; + else + mbRightToLeft = false; + } + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( rFrameSize.GetWidth() ); + aFrm.Height( rFrameSize.GetHeightSizeType() == SwFrameSize::Variable ? MINFLY : rFrameSize.GetHeight() ); + } + + // Fixed or variable Height? + if ( rFrameSize.GetHeightSizeType() == SwFrameSize::Minimum ) + m_bMinHeight = true; + else if ( rFrameSize.GetHeightSizeType() == SwFrameSize::Fixed ) + mbFixSize = true; + + // insert columns, if necessary + InsertColumns(); + + // First the Init, then the Content: + // This is due to the fact that the Content may have Objects/Frames, + // which are then registered + InitDrawObj(*pAnch); + + Chain( pAnch ); + + InsertCnt(); + + // Put it somewhere outside so that out document is not formatted unnecessarily often + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX(FAR_AWAY); + aFrm.Pos().setY(FAR_AWAY); +} + +void SwFlyFrame::Chain( SwFrame* _pAnch ) +{ + // Connect to chain neighbours. + // No problem, if a neighbor doesn't exist - the construction of the + // neighbor will make the connection + const SwFormatChain& rChain = GetFormat()->GetChain(); + if ( !(rChain.GetPrev() || rChain.GetNext()) ) + return; + + if ( rChain.GetNext() ) + { + SwFlyFrame* pFollow = FindChainNeighbour( *rChain.GetNext(), _pAnch ); + if ( pFollow ) + { + OSL_ENSURE( !pFollow->GetPrevLink(), "wrong chain detected" ); + if ( !pFollow->GetPrevLink() ) + SwFlyFrame::ChainFrames( this, pFollow ); + } + } + if ( rChain.GetPrev() ) + { + SwFlyFrame *pMaster = FindChainNeighbour( *rChain.GetPrev(), _pAnch ); + if ( pMaster ) + { + OSL_ENSURE( !pMaster->GetNextLink(), "wrong chain detected" ); + if ( !pMaster->GetNextLink() ) + SwFlyFrame::ChainFrames( pMaster, this ); + } + } +} + +void SwFlyFrame::InsertCnt() +{ + if ( GetPrevLink() ) + return; + + const SwFormatContent& rContent = GetFormat()->GetContent(); + OSL_ENSURE( rContent.GetContentIdx(), ":-( no content prepared." ); + SwNodeOffset nIndex = rContent.GetContentIdx()->GetIndex(); + // Lower() means SwColumnFrame; the Content then needs to be inserted into the (Column)BodyFrame + ::InsertCnt_( Lower() ? static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(Lower())->Lower()) : static_cast<SwLayoutFrame*>(this), + GetFormat()->GetDoc(), nIndex ); + + // NoText always have a fixed height. + if ( Lower() && Lower()->IsNoTextFrame() ) + { + mbFixSize = true; + m_bMinHeight = false; + } +} + +void SwFlyFrame::InsertColumns() +{ + // #i97379# + // Check, if column are allowed. + // Columns are not allowed for fly frames, which represent graphics or embedded objects. + const SwFormatContent& rContent = GetFormat()->GetContent(); + OSL_ENSURE( rContent.GetContentIdx(), "<SwFlyFrame::InsertColumns()> - no content prepared." ); + SwNodeIndex aFirstContent( *(rContent.GetContentIdx()), 1 ); + if ( aFirstContent.GetNode().IsNoTextNode() ) + { + return; + } + + const SwFormatCol &rCol = GetFormat()->GetCol(); + if ( rCol.GetNumCols() <= 1 ) + return; + + // Start off PrtArea to be as large as Frame, so that we can put in the columns + // properly. It'll adjust later on. + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( getFrameArea().Width() ); + aPrt.Height( getFrameArea().Height() ); + } + + const SwFormatCol aOld; // ChgColumns() also needs an old value passed + ChgColumns( aOld, rCol ); +} + +void SwFlyFrame::DestroyImpl() +{ + // Accessible objects for fly frames will be destroyed in this destructor. + // For frames bound as char or frames that don't have an anchor we have + // to do that ourselves. For any other frame the call RemoveFly at the + // anchor will do that. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if( IsAccessibleFrame() && GetFormat() && (IsFlyInContentFrame() || !GetAnchorFrame()) ) + { + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() ) + { + SwViewShell *pVSh = pRootFrame->GetCurrShell(); + if( pVSh && pVSh->Imp() ) + { + // Lowers aren't disposed already, so we have to do a recursive + // dispose + pVSh->Imp()->DisposeAccessibleFrame( this, true ); + } + } + } +#endif + + if( GetFormat() && !GetFormat()->GetDoc()->IsInDtor() ) + { + ClearTmpConsiderWrapInfluence(); // remove this from SwLayouter + + Unchain(); + + DeleteCnt(); + + if ( GetAnchorFrame() ) + AnchorFrame()->RemoveFly( this ); + } + + FinitDrawObj(); + + SwLayoutFrame::DestroyImpl(); + + SwWrtShell* pWrtSh = dynamic_cast<SwWrtShell*>(getRootFrame()->GetCurrShell()); + UpdateUnfloatButton(pWrtSh, false); +} + +SwFlyFrame::~SwFlyFrame() +{ +} + +const IDocumentDrawModelAccess& SwFlyFrame::getIDocumentDrawModelAccess() +{ + return GetFormat()->getIDocumentDrawModelAccess(); +} + +void SwFlyFrame::Unchain() +{ + if ( GetPrevLink() ) + UnchainFrames( GetPrevLink(), this ); + if ( GetNextLink() ) + UnchainFrames( this, GetNextLink() ); +} + +void SwFlyFrame::DeleteCnt() +{ + SwFrame* pFrame = m_pLower; + while ( pFrame ) + { + while ( pFrame->GetDrawObjs() && pFrame->GetDrawObjs()->size() ) + { + SwAnchoredObject *pAnchoredObj = (*pFrame->GetDrawObjs())[0]; + if ( auto pFlyFrame = pAnchoredObj->DynCastFlyFrame() ) + { + SwFrame::DestroyFrame(pFlyFrame); + } + else if ( dynamic_cast<const SwAnchoredDrawObject*>( pAnchoredObj) != nullptr ) + { + // consider 'virtual' drawing objects + SdrObject* pObj = pAnchoredObj->DrawObj(); + if ( auto pDrawVirtObj = dynamic_cast<SwDrawVirtObj*>( pObj) ) + { + pDrawVirtObj->RemoveFromWriterLayout(); + pDrawVirtObj->RemoveFromDrawingPage(); + } + else + { + SwDrawContact* pContact = + static_cast<SwDrawContact*>(::GetUserCall( pObj )); + if ( pContact ) + { + pContact->DisconnectFromLayout(); + } + } + } + } + + pFrame->RemoveFromLayout(); + SwFrame::DestroyFrame(pFrame); + pFrame = m_pLower; + } + + InvalidatePage(); +} + +void SwFlyFrame::InitDrawObj(SwFrame const& rAnchorFrame) +{ + SetDrawObj(*SwFlyDrawContact::CreateNewRef(this, GetFormat(), rAnchorFrame)); + + // Set the right Layer + IDocumentDrawModelAccess& rIDDMA = GetFormat()->getIDocumentDrawModelAccess(); + SdrLayerID nHeavenId = rIDDMA.GetHeavenId(); + SdrLayerID nHellId = rIDDMA.GetHellId(); + GetVirtDrawObj()->SetLayer( GetFormat()->GetOpaque().GetValue() + ? nHeavenId + : nHellId ); +} + +static SwPosition ResolveFlyAnchor(SwFrameFormat const& rFlyFrame) +{ + SwFormatAnchor const& rAnch(rFlyFrame.GetAnchor()); + if (rAnch.GetAnchorId() == RndStdIds::FLY_AT_PAGE) + { // arbitrarily pick last node + return SwPosition(SwNodeIndex(rFlyFrame.GetDoc()->GetNodes().GetEndOfContent(), -1)); + } + else + { + SwPosition const*const pPos(rAnch.GetContentAnchor()); + assert(pPos); + if (SwFrameFormat const*const pParent = pPos->nNode.GetNode().GetFlyFormat()) + { + return ResolveFlyAnchor(*pParent); + } + else if (pPos->nContent.GetIdxReg()) + { + return *pPos; + } + else + { + return SwPosition(*pPos->nNode.GetNode().GetContentNode(), 0); + } + } +} + +void SwFlyFrame::FinitDrawObj() +{ + if(!GetVirtDrawObj() ) + return; + SwFormat* pFormat = GetFormat(); + // Deregister from SdrPageViews if the Objects is still selected there. + if(!pFormat->GetDoc()->IsInDtor()) + { + SwViewShell* p1St = getRootFrame()->GetCurrShell(); + if(p1St) + { + for(SwViewShell& rCurrentShell : p1St->GetRingContainer()) + { // At the moment the Drawing can do just do an Unmark on everything, + // as the Object was already removed + if (rCurrentShell.HasDrawView() && + rCurrentShell.Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount()) + { + SwFlyFrame const*const pOldSelFly = ::GetFlyFromMarked(nullptr, &rCurrentShell); + if (pOldSelFly == this) + { + assert(rCurrentShell.Imp()->GetDrawView()->GetMarkedObjectList().GetMarkCount() == 1); + if (SwFEShell *const pFEShell = dynamic_cast<SwFEShell*>(&rCurrentShell)) + { // tdf#131679 move any cursor out of fly + rCurrentShell.Imp()->GetDrawView()->UnmarkAll(); + if (pOldSelFly) + { + SwPosition const pos(ResolveFlyAnchor(*pOldSelFly->GetFormat())); + SwPaM const temp(pos); + pFEShell->SetSelection(temp); + // could also call SetCursor() like SwFEShell::SelectObj() + // does, but that would access layout a bit much... + } + } + else + { + rCurrentShell.Imp()->GetDrawView()->UnmarkAll(); + } + } + } + } + } + } + + // Else calls delete of the ContactObj + GetVirtDrawObj()->SetUserCall(nullptr); + + // Deregisters itself at the Master + // always use SdrObject::Free(...) for SdrObjects (!) + SdrObject* pTemp(GetVirtDrawObj()); + SdrObject::Free(pTemp); +} + +void SwFlyFrame::ChainFrames( SwFlyFrame *pMaster, SwFlyFrame *pFollow ) +{ + OSL_ENSURE( pMaster && pFollow, "incomplete chain" ); + OSL_ENSURE( !pMaster->GetNextLink(), "link can not be changed" ); + OSL_ENSURE( !pFollow->GetPrevLink(), "link can not be changed" ); + + pMaster->m_pNextLink = pFollow; + pFollow->m_pPrevLink = pMaster; + + if ( pMaster->ContainsContent() ) + { + // To get a text flow we need to invalidate + SwFrame *pInva = pMaster->FindLastLower(); + SwRectFnSet aRectFnSet(pMaster); + const tools::Long nBottom = aRectFnSet.GetPrtBottom(*pMaster); + while ( pInva ) + { + if( aRectFnSet.BottomDist( pInva->getFrameArea(), nBottom ) <= 0 ) + { + pInva->InvalidateSize(); + pInva->Prepare(); + pInva = pInva->FindPrev(); + } + else + pInva = nullptr; + } + } + + if ( pFollow->ContainsContent() ) + { + // There's only the content from the Masters left; the content from the Follow + // does not have any Frames left (should always be exactly one empty TextNode). + SwFrame *pFrame = pFollow->ContainsContent(); + OSL_ENSURE( !pFrame->IsTabFrame() && !pFrame->FindNext(), "follow in chain contains content" ); + pFrame->Cut(); + SwFrame::DestroyFrame(pFrame); + } + + // invalidate accessible relation set (accessibility wrapper) +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + SwViewShell* pSh = pMaster->getRootFrame()->GetCurrShell(); + if( pSh ) + { + SwRootFrame* pLayout = pMaster->getRootFrame(); + if( pLayout && pLayout->IsAnyShellAccessible() ) + pSh->Imp()->InvalidateAccessibleRelationSet( pMaster, pFollow ); + } +#endif +} + +void SwFlyFrame::UnchainFrames( SwFlyFrame *pMaster, SwFlyFrame *pFollow ) +{ + pMaster->m_pNextLink = nullptr; + pFollow->m_pPrevLink = nullptr; + + if ( pFollow->ContainsContent() ) + { + // The Master sucks up the content of the Follow + SwLayoutFrame *pUpper = pMaster; + if ( pUpper->Lower()->IsColumnFrame() ) + { + pUpper = static_cast<SwLayoutFrame*>(pUpper->GetLastLower()); + pUpper = static_cast<SwLayoutFrame*>(pUpper->Lower()); // The (Column)BodyFrame + OSL_ENSURE( pUpper && pUpper->IsColBodyFrame(), "Missing ColumnBody" ); + } + SwFlyFrame *pFoll = pFollow; + while ( pFoll ) + { + SwFrame *pTmp = ::SaveContent( pFoll ); + if ( pTmp ) + ::RestoreContent( pTmp, pUpper, pMaster->FindLastLower() ); + pFoll->SetCompletePaint(); + pFoll->InvalidateSize(); + pFoll = pFoll->GetNextLink(); + } + } + + // The Follow needs his own content to be served + const SwFormatContent &rContent = pFollow->GetFormat()->GetContent(); + OSL_ENSURE( rContent.GetContentIdx(), ":-( No content prepared." ); + SwNodeOffset nIndex = rContent.GetContentIdx()->GetIndex(); + // Lower() means SwColumnFrame: this one contains another SwBodyFrame + ::InsertCnt_( pFollow->Lower() ? const_cast<SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(pFollow->Lower())->Lower())) + : static_cast<SwLayoutFrame*>(pFollow), + pFollow->GetFormat()->GetDoc(), ++nIndex ); + + // invalidate accessible relation set (accessibility wrapper) +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + SwViewShell* pSh = pMaster->getRootFrame()->GetCurrShell(); + if( pSh ) + { + SwRootFrame* pLayout = pMaster->getRootFrame(); + if( pLayout && pLayout->IsAnyShellAccessible() ) + pSh->Imp()->InvalidateAccessibleRelationSet( pMaster, pFollow ); + } +#endif +} + +SwFlyFrame *SwFlyFrame::FindChainNeighbour( SwFrameFormat const &rChain, SwFrame *pAnch ) +{ + // We look for the Fly that's in the same Area. + // Areas can for now only be Head/Footer or Flys. + + if ( !pAnch ) // If an Anchor was passed along, that one counts (ctor!) + pAnch = AnchorFrame(); + + SwLayoutFrame *pLay; + if ( pAnch->IsInFly() ) + pLay = pAnch->FindFlyFrame(); + else + { + // FindFooterOrHeader is not appropriate here, as we may not have a + // connection to the Anchor yet. + pLay = pAnch->GetUpper(); + while ( pLay && !(pLay->GetType() & (SwFrameType::Header|SwFrameType::Footer)) ) + pLay = pLay->GetUpper(); + } + + SwIterator<SwFlyFrame,SwFormat> aIter( rChain ); + SwFlyFrame *pFly = aIter.First(); + if ( pLay ) + { + while ( pFly ) + { + if ( pFly->GetAnchorFrame() ) + { + if ( pFly->GetAnchorFrame()->IsInFly() ) + { + if ( pFly->AnchorFrame()->FindFlyFrame() == pLay ) + break; + } + else if ( pLay == pFly->FindFooterOrHeader() ) + break; + } + pFly = aIter.Next(); + } + } + else if ( pFly ) + { + OSL_ENSURE( !aIter.Next(), "chain with more than one instance" ); + } + return pFly; +} + +SwFrame *SwFlyFrame::FindLastLower() +{ + SwFrame *pRet = ContainsAny(); + if ( pRet && pRet->IsInTab() ) + pRet = pRet->FindTabFrame(); + SwFrame *pNxt = pRet; + while ( pNxt && IsAnLower( pNxt ) ) + { pRet = pNxt; + pNxt = pNxt->FindNext(); + } + return pRet; +} + +bool SwFlyFrame::FrameSizeChg( const SwFormatFrameSize &rFrameSize ) +{ + bool bRet = false; + SwTwips nDiffHeight = getFrameArea().Height(); + if ( rFrameSize.GetHeightSizeType() == SwFrameSize::Variable ) + mbFixSize = m_bMinHeight = false; + else + { + if ( rFrameSize.GetHeightSizeType() == SwFrameSize::Fixed ) + { + mbFixSize = true; + m_bMinHeight = false; + } + else if ( rFrameSize.GetHeightSizeType() == SwFrameSize::Minimum ) + { + mbFixSize = false; + m_bMinHeight = true; + } + nDiffHeight -= rFrameSize.GetHeight(); + } + // If the Fly contains columns, we already need to set the Fly + // and the Columns to the required value or else we run into problems. + if ( Lower() ) + { + if ( Lower()->IsColumnFrame() ) + { + const SwRect aOld( GetObjRectWithSpaces() ); + const Size aOldSz( getFramePrintArea().SSize() ); + const SwTwips nDiffWidth = getFrameArea().Width() - rFrameSize.GetWidth(); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( aFrm.Height() - nDiffHeight ); + aFrm.Width ( aFrm.Width() - nDiffWidth ); + } + + // #i68520# + InvalidateObjRectWithSpaces(); + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( aPrt.Height() - nDiffHeight ); + aPrt.Width ( aPrt.Width() - nDiffWidth ); + } + + ChgLowersProp( aOldSz ); + ::Notify( this, FindPageFrame(), aOld ); + setFrameAreaPositionValid(false); + bRet = true; + } + else if ( Lower()->IsNoTextFrame() ) + { + mbFixSize = true; + m_bMinHeight = false; + } + } + return bRet; +} + +void SwFlyFrame::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) +{ + if (rHint.GetId() == SfxHintId::SwLegacyModify) + { + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + SwFlyFrameInvFlags eInvFlags = SwFlyFrameInvFlags::NONE; + if(pLegacy->m_pNew && pLegacy->m_pOld && RES_ATTRSET_CHG == pLegacy->m_pNew->Which()) + { + SfxItemIter aNIter(*static_cast<const SwAttrSetChg*>(pLegacy->m_pNew)->GetChgSet()); + SfxItemIter aOIter(*static_cast<const SwAttrSetChg*>(pLegacy->m_pOld)->GetChgSet()); + const SfxPoolItem* pNItem = aNIter.GetCurItem(); + const SfxPoolItem* pOItem = aOIter.GetCurItem(); + SwAttrSetChg aOldSet(*static_cast<const SwAttrSetChg*>(pLegacy->m_pOld)); + SwAttrSetChg aNewSet(*static_cast<const SwAttrSetChg*>(pLegacy->m_pNew)); + do + { + UpdateAttr_(pOItem, pNItem, eInvFlags, &aOldSet, &aNewSet); + pNItem = aNIter.NextItem(); + pOItem = aOIter.NextItem(); + } while(pNItem); + if(aOldSet.Count() || aNewSet.Count()) + SwLayoutFrame::SwClientNotify(rMod, sw::LegacyModifyHint(&aOldSet, &aNewSet)); + } + else + UpdateAttr_(pLegacy->m_pOld, pLegacy->m_pNew, eInvFlags); + + if(eInvFlags == SwFlyFrameInvFlags::NONE) + return; + + Invalidate_(); + if(eInvFlags & SwFlyFrameInvFlags::InvalidatePos) + { + InvalidatePos_(); + // #i68520# + InvalidateObjRectWithSpaces(); + } + if(eInvFlags & SwFlyFrameInvFlags::InvalidateSize) + { + InvalidateSize_(); + // #i68520# + InvalidateObjRectWithSpaces(); + } + if(eInvFlags & SwFlyFrameInvFlags::InvalidatePrt) + InvalidatePrt_(); + if(eInvFlags & SwFlyFrameInvFlags::SetNotifyBack) + SetNotifyBack(); + if(eInvFlags & SwFlyFrameInvFlags::SetCompletePaint) + SetCompletePaint(); + if((eInvFlags & SwFlyFrameInvFlags::ClearContourCache) && Lower() && Lower()->IsNoTextFrame()) + ClrContourCache( GetVirtDrawObj() ); + SwRootFrame *pRoot; + if(eInvFlags & SwFlyFrameInvFlags::InvalidateBrowseWidth && nullptr != (pRoot = getRootFrame())) + pRoot->InvalidateBrowseWidth(); + // #i28701# + if(eInvFlags & SwFlyFrameInvFlags::UpdateObjInSortedList) + { + // update sorted object lists, the Writer fly frame is registered at. + UpdateObjInSortedList(); + } + + // #i87645# - reset flags for the layout process (only if something has been invalidated) + ResetLayoutProcessBools(); + } + else if (auto pGetZOrdnerHint = dynamic_cast<const sw::GetZOrderHint*>(&rHint)) + { + const auto& rFormat(dynamic_cast<const SwFrameFormat&>(rMod)); + if (rFormat.Which() == RES_FLYFRMFMT && rFormat.getIDocumentLayoutAccess().GetCurrentViewShell()) // #i11176# + pGetZOrdnerHint->m_rnZOrder = GetVirtDrawObj()->GetOrdNum(); + } + else if (auto pConnectedHint = dynamic_cast<const sw::GetObjectConnectedHint*>(&rHint)) + { + const auto& rFormat(dynamic_cast<const SwFrameFormat&>(rMod)); + if (!pConnectedHint->m_risConnected && rFormat.Which() == RES_FLYFRMFMT && (!pConnectedHint->m_pRoot || pConnectedHint->m_pRoot == getRootFrame())) + pConnectedHint->m_risConnected = true; + } +} + +void SwFlyFrame::UpdateAttr_( const SfxPoolItem *pOld, const SfxPoolItem *pNew, + SwFlyFrameInvFlags &rInvFlags, + SwAttrSetChg *pOldSet, SwAttrSetChg *pNewSet ) +{ + bool bClear = true; + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + switch( nWhich ) + { + case RES_VERT_ORIENT: + case RES_HORI_ORIENT: + // #i18732# - consider new option 'follow text flow' + case RES_FOLLOW_TEXT_FLOW: + { + // ATTENTION: Always also change Action in ChgRePos()! + rInvFlags |= SwFlyFrameInvFlags::InvalidatePos | SwFlyFrameInvFlags::SetNotifyBack; + } + break; + // #i28701# - consider new option 'wrap influence on position' + case RES_WRAP_INFLUENCE_ON_OBJPOS: + { + rInvFlags |= SwFlyFrameInvFlags::InvalidatePos | SwFlyFrameInvFlags::SetNotifyBack + | SwFlyFrameInvFlags::UpdateObjInSortedList; + } + break; + case RES_SURROUND: + { + //#i28701# - invalidate position on change of + // wrapping style. + rInvFlags |= SwFlyFrameInvFlags::InvalidatePos | SwFlyFrameInvFlags::ClearContourCache; + // The background needs to be messaged and invalidated + const SwRect aTmp( GetObjRectWithSpaces() ); + NotifyBackground( FindPageFrame(), aTmp, PrepareHint::FlyFrameAttributesChanged ); + + // By changing the flow of frame-bound Frames, a vertical alignment + // can be activated/deactivated => MakeFlyPos + if( RndStdIds::FLY_AT_FLY == GetFormat()->GetAnchor().GetAnchorId() ) + rInvFlags |= SwFlyFrameInvFlags::InvalidatePos | SwFlyFrameInvFlags::SetNotifyBack; + + // Delete contour in the Node if necessary + if ( Lower() && Lower()->IsNoTextFrame() && + !GetFormat()->GetSurround().IsContour() ) + { + SwNoTextNode *pNd = static_cast<SwNoTextNode*>(static_cast<SwNoTextFrame*>(Lower())->GetNode()); + if ( pNd->HasContour() ) + pNd->SetContour( nullptr ); + } + // #i28701# - perform reorder of object lists + // at anchor frame and at page frame. + rInvFlags |= SwFlyFrameInvFlags::UpdateObjInSortedList; + } + break; + + case RES_PROTECT: + if (pNew) + { + const SvxProtectItem *pP = static_cast<const SvxProtectItem*>(pNew); + GetVirtDrawObj()->SetMoveProtect( pP->IsPosProtected() ); + GetVirtDrawObj()->SetResizeProtect( pP->IsSizeProtected() ); +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if( pSh ) + { + SwRootFrame* pLayout = getRootFrame(); + if( pLayout && pLayout->IsAnyShellAccessible() ) + pSh->Imp()->InvalidateAccessibleEditableState( true, this ); + } +#endif + } + break; + case RES_COL: + if (pOld && pNew) + { + ChgColumns( *static_cast<const SwFormatCol*>(pOld), *static_cast<const SwFormatCol*>(pNew) ); + const SwFormatFrameSize &rNew = GetFormat()->GetFrameSize(); + if ( FrameSizeChg( rNew ) ) + NotifyDrawObj(); + rInvFlags |= SwFlyFrameInvFlags::InvalidateSize | SwFlyFrameInvFlags::SetNotifyBack + | SwFlyFrameInvFlags::SetCompletePaint; + } + break; + + case RES_FRM_SIZE: + case RES_FMT_CHG: + { + const SwFormatFrameSize &rNew = GetFormat()->GetFrameSize(); + if ( FrameSizeChg( rNew ) ) + NotifyDrawObj(); + rInvFlags |= SwFlyFrameInvFlags::InvalidatePos | SwFlyFrameInvFlags::InvalidateSize + | SwFlyFrameInvFlags::InvalidatePrt | SwFlyFrameInvFlags::SetNotifyBack + | SwFlyFrameInvFlags::SetCompletePaint + | SwFlyFrameInvFlags::InvalidateBrowseWidth + | SwFlyFrameInvFlags::ClearContourCache; + if (pOld && RES_FMT_CHG == nWhich) + { + SwRect aNew( GetObjRectWithSpaces() ); + SwRect aOld( getFrameArea() ); + const SvxULSpaceItem &rUL = static_cast<const SwFormatChg*>(pOld)->pChangedFormat->GetULSpace(); + aOld.Top( std::max( aOld.Top() - tools::Long(rUL.GetUpper()), tools::Long(0) ) ); + aOld.AddHeight(rUL.GetLower() ); + const SvxLRSpaceItem &rLR = static_cast<const SwFormatChg*>(pOld)->pChangedFormat->GetLRSpace(); + aOld.Left ( std::max( aOld.Left() - rLR.GetLeft(), tools::Long(0) ) ); + aOld.AddWidth(rLR.GetRight() ); + aNew.Union( aOld ); + NotifyBackground( FindPageFrame(), aNew, PrepareHint::Clear ); + + // Special case: + // When assigning a template we cannot rely on the old column + // attribute. As there need to be at least enough for ChgColumns, + // we need to create a temporary attribute. + SwFormatCol aCol; + if ( Lower() && Lower()->IsColumnFrame() ) + { + sal_uInt16 nCol = 0; + SwFrame *pTmp = Lower(); + do + { ++nCol; + pTmp = pTmp->GetNext(); + } while ( pTmp ); + aCol.Init( nCol, 0, 1000 ); + } + ChgColumns( aCol, GetFormat()->GetCol() ); + } + + SwFormatURL aURL( GetFormat()->GetURL() ); + + SwFormatFrameSize *pNewFormatFrameSize = nullptr; + SwFormatChg *pOldFormatChg = nullptr; + if (nWhich == RES_FRM_SIZE) + pNewFormatFrameSize = const_cast<SwFormatFrameSize*>(static_cast<const SwFormatFrameSize*>(pNew)); + else + pOldFormatChg = const_cast<SwFormatChg*>(static_cast<const SwFormatChg*>(pOld)); + + if (aURL.GetMap() && (pNewFormatFrameSize || pOldFormatChg)) + { + const SwFormatFrameSize &rOld = pNewFormatFrameSize ? + *pNewFormatFrameSize : + pOldFormatChg->pChangedFormat->GetFrameSize(); + //#35091# Can be "times zero", when loading the template + if ( rOld.GetWidth() && rOld.GetHeight() ) + { + + Fraction aScaleX( rOld.GetWidth(), rNew.GetWidth() ); + Fraction aScaleY( rOld.GetHeight(), rOld.GetHeight() ); + aURL.GetMap()->Scale( aScaleX, aScaleY ); + SwFrameFormat *pFormat = GetFormat(); + pFormat->LockModify(); + pFormat->SetFormatAttr( aURL ); + pFormat->UnlockModify(); + } + } + const SvxProtectItem &rP = GetFormat()->GetProtect(); + GetVirtDrawObj()->SetMoveProtect( rP.IsPosProtected() ); + GetVirtDrawObj()->SetResizeProtect( rP.IsSizeProtected() ); + + if ( pSh ) + pSh->InvalidateWindows( getFrameArea() ); + const IDocumentDrawModelAccess& rIDDMA = GetFormat()->getIDocumentDrawModelAccess(); + const SdrLayerID nId = GetFormat()->GetOpaque().GetValue() ? + rIDDMA.GetHeavenId() : + rIDDMA.GetHellId(); + GetVirtDrawObj()->SetLayer( nId ); + + if ( Lower() ) + { + // Delete contour in the Node if necessary + if( Lower()->IsNoTextFrame() && + !GetFormat()->GetSurround().IsContour() ) + { + SwNoTextNode *pNd = static_cast<SwNoTextNode*>(static_cast<SwNoTextFrame*>(Lower())->GetNode()); + if ( pNd->HasContour() ) + pNd->SetContour( nullptr ); + } + else if( !Lower()->IsColumnFrame() ) + { + SwFrame* pFrame = GetLastLower(); + if( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsUndersized() ) + pFrame->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + } + } + + // #i28701# - perform reorder of object lists + // at anchor frame and at page frame. + rInvFlags |= SwFlyFrameInvFlags::UpdateObjInSortedList; + + break; + } + case RES_UL_SPACE: + case RES_LR_SPACE: + { + rInvFlags |= SwFlyFrameInvFlags::InvalidatePos | SwFlyFrameInvFlags::ClearContourCache; + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + getRootFrame()->InvalidateBrowseWidth(); + SwRect aNew( GetObjRectWithSpaces() ); + SwRect aOld( getFrameArea() ); + if (pNew) + { + if ( RES_UL_SPACE == nWhich ) + { + const SvxULSpaceItem &rUL = *static_cast<const SvxULSpaceItem*>(pNew); + aOld.Top( std::max( aOld.Top() - tools::Long(rUL.GetUpper()), tools::Long(0) ) ); + aOld.AddHeight(rUL.GetLower() ); + } + else + { + const SvxLRSpaceItem &rLR = *static_cast<const SvxLRSpaceItem*>(pNew); + aOld.Left ( std::max( aOld.Left() - rLR.GetLeft(), tools::Long(0) ) ); + aOld.AddWidth(rLR.GetRight() ); + } + } + aNew.Union( aOld ); + NotifyBackground( FindPageFrame(), aNew, PrepareHint::Clear ); + } + break; + + case RES_TEXT_VERT_ADJUST: + { + InvalidateContentPos(); + rInvFlags |= SwFlyFrameInvFlags::SetCompletePaint; + } + break; + + case RES_BOX: + case RES_SHADOW: + rInvFlags |= SwFlyFrameInvFlags::InvalidatePos | SwFlyFrameInvFlags::InvalidateSize + | SwFlyFrameInvFlags::InvalidatePrt | SwFlyFrameInvFlags::SetCompletePaint; + break; + + case RES_FRAMEDIR : + SetDerivedVert( false ); + SetDerivedR2L( false ); + CheckDirChange(); + break; + + case RES_OPAQUE: + if (pNew) + { + if ( pSh ) + pSh->InvalidateWindows( getFrameArea() ); + + const IDocumentDrawModelAccess& rIDDMA = GetFormat()->getIDocumentDrawModelAccess(); + const SdrLayerID nId = static_cast<const SvxOpaqueItem*>(pNew)->GetValue() ? + rIDDMA.GetHeavenId() : + rIDDMA.GetHellId(); + GetVirtDrawObj()->SetLayer( nId ); +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if( pSh ) + { + SwRootFrame* pLayout = getRootFrame(); + if( pLayout && pLayout->IsAnyShellAccessible() ) + { + pSh->Imp()->DisposeAccessibleFrame( this ); + pSh->Imp()->AddAccessibleFrame( this ); + } + } +#endif + // #i28701# - perform reorder of object lists + // at anchor frame and at page frame. + rInvFlags |= SwFlyFrameInvFlags::UpdateObjInSortedList; + } + break; + + case RES_URL: + // The interface changes the frame size when interacting with text frames, + // the Map, however, needs to be relative to FrameSize(). + if ( (!Lower() || !Lower()->IsNoTextFrame()) && pNew && pOld && + static_cast<const SwFormatURL*>(pNew)->GetMap() && static_cast<const SwFormatURL*>(pOld)->GetMap() ) + { + const SwFormatFrameSize &rSz = GetFormat()->GetFrameSize(); + if ( rSz.GetHeight() != getFrameArea().Height() || + rSz.GetWidth() != getFrameArea().Width() ) + { + SwFormatURL aURL( GetFormat()->GetURL() ); + Fraction aScaleX( getFrameArea().Width(), rSz.GetWidth() ); + Fraction aScaleY( getFrameArea().Height(), rSz.GetHeight() ); + aURL.GetMap()->Scale( aScaleX, aScaleY ); + SwFrameFormat *pFormat = GetFormat(); + pFormat->LockModify(); + pFormat->SetFormatAttr( aURL ); + pFormat->UnlockModify(); + } + } + // No invalidation necessary + break; + + case RES_CHAIN: + if (pNew) + { + const SwFormatChain *pChain = static_cast<const SwFormatChain*>(pNew); + if ( pChain->GetNext() ) + { + SwFlyFrame *pFollow = FindChainNeighbour( *pChain->GetNext() ); + if ( GetNextLink() && pFollow != GetNextLink() ) + SwFlyFrame::UnchainFrames( this, GetNextLink()); + if ( pFollow ) + { + if ( pFollow->GetPrevLink() && + pFollow->GetPrevLink() != this ) + SwFlyFrame::UnchainFrames( pFollow->GetPrevLink(), + pFollow ); + if ( !GetNextLink() ) + SwFlyFrame::ChainFrames( this, pFollow ); + } + } + else if ( GetNextLink() ) + SwFlyFrame::UnchainFrames( this, GetNextLink() ); + if ( pChain->GetPrev() ) + { + SwFlyFrame *pMaster = FindChainNeighbour( *pChain->GetPrev() ); + if ( GetPrevLink() && pMaster != GetPrevLink() ) + SwFlyFrame::UnchainFrames( GetPrevLink(), this ); + if ( pMaster ) + { + if ( pMaster->GetNextLink() && + pMaster->GetNextLink() != this ) + SwFlyFrame::UnchainFrames( pMaster, + pMaster->GetNextLink() ); + if ( !GetPrevLink() ) + SwFlyFrame::ChainFrames( pMaster, this ); + } + } + else if ( GetPrevLink() ) + SwFlyFrame::UnchainFrames( GetPrevLink(), this ); + } + [[fallthrough]]; + default: + bClear = false; + } + if ( !bClear ) + return; + + if ( pOldSet || pNewSet ) + { + if ( pOldSet ) + pOldSet->ClearItem( nWhich ); + if ( pNewSet ) + pNewSet->ClearItem( nWhich ); + } + else + { + SwModify aMod; + SwLayoutFrame::SwClientNotify(aMod, sw::LegacyModifyHint(pOld, pNew)); + } +} + +/// Gets information from the Modify +bool SwFlyFrame::GetInfo( SfxPoolItem & rInfo ) const +{ + if( RES_AUTOFMT_DOCNODE == rInfo.Which() ) + return false; // There's a FlyFrame, so use it + return true; // Continue searching +} + +void SwFlyFrame::Invalidate_( SwPageFrame const *pPage ) +{ + InvalidatePage( pPage ); + m_bNotifyBack = m_bInvalid = true; + + SwFlyFrame *pFrame; + if ( GetAnchorFrame() && nullptr != (pFrame = AnchorFrame()->FindFlyFrame()) ) + { + // Very bad case: If the Fly is bound within another Fly which + // contains columns, the Format should be from that one. + if ( !pFrame->IsLocked() && !pFrame->IsColLocked() && + pFrame->Lower() && pFrame->Lower()->IsColumnFrame() ) + pFrame->InvalidateSize(); + } + + // #i85216# + // if vertical position is oriented at a layout frame inside a ghost section, + // assure that the position is invalidated and that the information about + // the vertical position oriented frame is cleared + if ( GetVertPosOrientFrame() && GetVertPosOrientFrame()->IsLayoutFrame() ) + { + const SwSectionFrame* pSectFrame( GetVertPosOrientFrame()->FindSctFrame() ); + if ( pSectFrame && pSectFrame->GetSection() == nullptr ) + { + InvalidatePos(); + ClearVertPosOrientFrame(); + } + } +} + +/** Change the relative position + * + * The position will be Fix automatically and the attribute is changed accordingly. + */ +void SwFlyFrame::ChgRelPos( const Point &rNewPos ) +{ + if ( GetCurrRelPos() == rNewPos ) + return; + + SwFrameFormat *pFormat = GetFormat(); + const bool bVert = GetAnchorFrame()->IsVertical(); + const SwTwips nNewY = bVert ? rNewPos.X() : rNewPos.Y(); + SwTwips nTmpY = nNewY == LONG_MAX ? 0 : nNewY; + if( bVert ) + nTmpY = -nTmpY; + SfxItemSetFixed<RES_VERT_ORIENT, RES_HORI_ORIENT> aSet( pFormat->GetDoc()->GetAttrPool() ); + + SwFormatVertOrient aVert( pFormat->GetVertOrient() ); + const SwTextFrame *pAutoFrame = nullptr; + // #i34948# - handle also at-page and at-fly anchored + // Writer fly frames + const RndStdIds eAnchorType = GetFrameFormat().GetAnchor().GetAnchorId(); + if ( eAnchorType == RndStdIds::FLY_AT_PAGE ) + { + aVert.SetVertOrient( text::VertOrientation::NONE ); + aVert.SetRelationOrient( text::RelOrientation::PAGE_FRAME ); + } + else if ( eAnchorType == RndStdIds::FLY_AT_FLY ) + { + aVert.SetVertOrient( text::VertOrientation::NONE ); + aVert.SetRelationOrient( text::RelOrientation::FRAME ); + } + else if ( IsFlyAtContentFrame() || text::VertOrientation::NONE != aVert.GetVertOrient() ) + { + if( text::RelOrientation::CHAR == aVert.GetRelationOrient() && IsAutoPos() ) + { + if( LONG_MAX != nNewY ) + { + aVert.SetVertOrient( text::VertOrientation::NONE ); + assert(GetAnchorFrame()->IsTextFrame()); + pAutoFrame = static_cast<const SwTextFrame*>(GetAnchorFrame()); + TextFrameIndex const nOfs(pAutoFrame->MapModelToViewPos( + *pFormat->GetAnchor().GetContentAnchor())); + while( pAutoFrame->GetFollow() && + pAutoFrame->GetFollow()->GetOffset() <= nOfs ) + { + if( pAutoFrame == GetAnchorFrame() ) + nTmpY += pAutoFrame->GetRelPos().Y(); + nTmpY -= pAutoFrame->GetUpper()->getFramePrintArea().Height(); + pAutoFrame = pAutoFrame->GetFollow(); + } + nTmpY = static_cast<SwFlyAtContentFrame*>(this)->GetRelCharY(pAutoFrame)-nTmpY; + } + else + aVert.SetVertOrient( text::VertOrientation::CHAR_BOTTOM ); + } + else + { + aVert.SetVertOrient( text::VertOrientation::NONE ); + aVert.SetRelationOrient( text::RelOrientation::FRAME ); + } + } + aVert.SetPos( nTmpY ); + aSet.Put( aVert ); + + // For Flys in the Cnt, the horizontal orientation is of no interest, + // as it's always 0 + if ( !IsFlyInContentFrame() ) + { + const SwTwips nNewX = bVert ? rNewPos.Y() : rNewPos.X(); + SwTwips nTmpX = nNewX == LONG_MAX ? 0 : nNewX; + SwFormatHoriOrient aHori( pFormat->GetHoriOrient() ); + // #i34948# - handle also at-page and at-fly anchored + // Writer fly frames + if ( eAnchorType == RndStdIds::FLY_AT_PAGE ) + { + aHori.SetHoriOrient( text::HoriOrientation::NONE ); + aHori.SetRelationOrient( text::RelOrientation::PAGE_FRAME ); + aHori.SetPosToggle( false ); + } + else if ( eAnchorType == RndStdIds::FLY_AT_FLY ) + { + aHori.SetHoriOrient( text::HoriOrientation::NONE ); + aHori.SetRelationOrient( text::RelOrientation::FRAME ); + aHori.SetPosToggle( false ); + } + else if ( IsFlyAtContentFrame() || text::HoriOrientation::NONE != aHori.GetHoriOrient() ) + { + aHori.SetHoriOrient( text::HoriOrientation::NONE ); + if( text::RelOrientation::CHAR == aHori.GetRelationOrient() && IsAutoPos() ) + { + if( LONG_MAX != nNewX ) + { + if( !pAutoFrame ) + { + assert(GetAnchorFrame()->IsTextFrame()); + pAutoFrame = static_cast<const SwTextFrame*>(GetAnchorFrame()); + TextFrameIndex const nOfs(pAutoFrame->MapModelToViewPos( + *pFormat->GetAnchor().GetContentAnchor())); + while( pAutoFrame->GetFollow() && + pAutoFrame->GetFollow()->GetOffset() <= nOfs ) + pAutoFrame = pAutoFrame->GetFollow(); + } + nTmpX -= static_cast<SwFlyAtContentFrame*>(this)->GetRelCharX(pAutoFrame); + } + } + else + aHori.SetRelationOrient( text::RelOrientation::FRAME ); + aHori.SetPosToggle( false ); + } + aHori.SetPos( nTmpX ); + aSet.Put( aHori ); + } + SetCurrRelPos( rNewPos ); + pFormat->GetDoc()->SetAttr( aSet, *pFormat ); + +} + +/** "Formats" the Frame; Frame and PrtArea. + * + * The FixSize is not inserted here. + */ +void SwFlyFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs *pAttrs ) +{ + OSL_ENSURE( pAttrs, "FlyFrame::Format, pAttrs is 0." ); + + ColLock(); + + if ( !isFrameAreaSizeValid() ) + { + if ( getFrameArea().Top() == FAR_AWAY && getFrameArea().Left() == FAR_AWAY ) + { + // Remove safety switch (see SwFrame::CTor) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX(0); + aFrm.Pos().setY(0); + } + + // #i68520# + InvalidateObjRectWithSpaces(); + } + + // Check column width and set it if needed + if ( Lower() && Lower()->IsColumnFrame() ) + AdjustColumns( nullptr, false ); + + setFrameAreaSizeValid(true); + + const SwTwips nUL = pAttrs->CalcTopLine() + pAttrs->CalcBottomLine(); + const SwTwips nLR = pAttrs->CalcLeftLine() + pAttrs->CalcRightLine(); + const SwFormatFrameSize &rFrameSz = GetFormat()->GetFrameSize(); + Size aRelSize( CalcRel( rFrameSz ) ); + + OSL_ENSURE( pAttrs->GetSize().Height() != 0 || rFrameSz.GetHeightPercent(), "FrameAttr height is 0." ); + OSL_ENSURE( pAttrs->GetSize().Width() != 0 || rFrameSz.GetWidthPercent(), "FrameAttr width is 0." ); + + SwRectFnSet aRectFnSet(this); + if( !HasFixSize() ) + { + tools::Long nMinHeight = 0; + if( IsMinHeight() ) + nMinHeight = aRectFnSet.IsVert() ? aRelSize.Width() : aRelSize.Height(); + + SwTwips nRemaining = CalcContentHeight(pAttrs, nMinHeight, nUL); + if( IsMinHeight() && (nRemaining + nUL) < nMinHeight ) + nRemaining = nMinHeight - nUL; + // Because the Grow/Shrink of the Flys does not directly + // set the size - only indirectly by triggering a Format() + // via Invalidate() - the sizes need to be set here. + // Notification is running along already. + // As we already got a lot of zeros per attribute, we block them + // from now on. + + if ( nRemaining < MINFLY ) + nRemaining = MINFLY; + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, nRemaining ); + } + + nRemaining -= aRectFnSet.GetHeight(getFrameArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nRemaining + nUL ); + } + + // #i68520# + if ( nRemaining + nUL != 0 ) + { + InvalidateObjRectWithSpaces(); + } + + setFrameAreaSizeValid(true); + + if (SwFrameFormat* pShapeFormat = SwTextBoxHelper::getOtherTextBoxFormat(GetFormat(), RES_FLYFRMFMT)) + { + // This fly is a textbox of a draw shape. + SdrObject* pShape = pShapeFormat->FindSdrObject(); + if (SdrObjCustomShape* pCustomShape = dynamic_cast<SdrObjCustomShape*>( pShape) ) + { + // The shape is a customshape: then inform it about the calculated fly size. + Size aSize(getFrameArea().Width(), getFrameArea().Height()); + pCustomShape->SuggestTextFrameSize(aSize); + // Do the calculations normally done after touching editeng text of the shape. + pCustomShape->NbcSetOutlinerParaObjectForText(std::nullopt, nullptr); + } + } + } + else + { + // Fixed Frames do not Format itself + setFrameAreaSizeValid(true); + + // Flys set their size using the attr + SwTwips nNewSize = aRectFnSet.IsVert() ? aRelSize.Width() : aRelSize.Height(); + nNewSize -= nUL; + if( nNewSize < MINFLY ) + nNewSize = MINFLY; + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, nNewSize ); + } + + nNewSize += nUL - aRectFnSet.GetHeight(getFrameArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nNewSize ); + } + + // #i68520# + if ( nNewSize != 0 ) + { + InvalidateObjRectWithSpaces(); + } + } + + if ( !m_bFormatHeightOnly ) + { + OSL_ENSURE( aRelSize == CalcRel( rFrameSz ), "SwFlyFrame::Format CalcRel problem" ); + SwTwips nNewSize = aRectFnSet.IsVert() ? aRelSize.Height() : aRelSize.Width(); + + if ( rFrameSz.GetWidthSizeType() != SwFrameSize::Fixed ) + { + // #i9046# Autowidth for fly frames + const SwTwips nAutoWidth = lcl_CalcAutoWidth( *this ); + if ( nAutoWidth ) + { + if( SwFrameSize::Minimum == rFrameSz.GetWidthSizeType() ) + nNewSize = std::max( nNewSize - nLR, nAutoWidth ); + else + nNewSize = nAutoWidth; + } + } + else + nNewSize -= nLR; + + if( nNewSize < MINFLY ) + nNewSize = MINFLY; + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetWidth( aPrt, nNewSize ); + } + + nNewSize += nLR - aRectFnSet.GetWidth(getFrameArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddRight( aFrm, nNewSize ); + } + + // #i68520# + if ( nNewSize != 0 ) + { + InvalidateObjRectWithSpaces(); + } + } + } + ColUnlock(); +} + +// #i11760# - change parameter <bNoColl>: type <bool>; +// add new parameter <bNoCalcFollow> with +// new parameter <bNoCalcFollow> was used by method +// <FormatWidthCols(..)> to avoid follow formatting +// for text frames. But, unformatted follows causes +// problems in method <SwContentFrame::WouldFit_(..)>, +// which assumes that the follows are formatted. +// Thus, <bNoCalcFollow> no longer used by <FormatWidthCols(..)>. +void CalcContent( SwLayoutFrame *pLay, bool bNoColl ) +{ + vcl::RenderContext* pRenderContext = pLay->getRootFrame()->GetCurrShell()->GetOut(); + SwSectionFrame* pSect; + bool bCollect = false; + if( pLay->IsSctFrame() ) + { + pSect = static_cast<SwSectionFrame*>(pLay); + if( pSect->IsEndnAtEnd() && !bNoColl ) + { + bCollect = true; + SwLayouter::CollectEndnotes( pLay->GetFormat()->GetDoc(), pSect ); + } + pSect->CalcFootnoteContent(); + } + else + pSect = nullptr; + SwFrame *pFrame = pLay->ContainsAny(); + if ( !pFrame ) + { + if( pSect ) + { + if( pSect->HasFollow() ) + pFrame = pSect->GetFollow()->ContainsAny(); + if( !pFrame ) + { + if( pSect->IsEndnAtEnd() ) + { + if( bCollect ) + pLay->GetFormat()->GetDoc()->getIDocumentLayoutAccess().GetLayouter()-> + InsertEndnotes( pSect ); + bool bLock = pSect->IsFootnoteLock(); + pSect->SetFootnoteLock( true ); + pSect->CalcFootnoteContent(); + pSect->CalcFootnoteContent(); + pSect->SetFootnoteLock( bLock ); + } + return; + } + pFrame->InvalidatePos_(); + } + else + return; + } + pFrame->InvalidatePage(); + + do + { + // local variables to avoid loops caused by anchored object positioning + SwAnchoredObject* pAgainObj1 = nullptr; + SwAnchoredObject* pAgainObj2 = nullptr; + + // FME 2007-08-30 #i81146# new loop control + int nLoopControlRuns = 0; + // tdf#152106 loop control for multi-column sections + int nLoopControlRunsInMultiCol = 0; + const int nLoopControlMax = 20; + const SwFrame* pLoopControlCond = nullptr; + + SwFrame* pLast; + do + { + pLast = pFrame; + bool const wasFrameLowerOfLay(pLay->IsAnLower(pFrame)); + if( pFrame->IsVertical() ? + ( pFrame->GetUpper()->getFramePrintArea().Height() != pFrame->getFrameArea().Height() ) + : ( pFrame->GetUpper()->getFramePrintArea().Width() != pFrame->getFrameArea().Width() ) ) + { + pFrame->Prepare( PrepareHint::FixSizeChanged ); + pFrame->InvalidateSize_(); + } + + if ( pFrame->IsTabFrame() ) + { + static_cast<SwTabFrame*>(pFrame)->m_bCalcLowers = true; + // #i18103# - lock move backward of follow table, + // if no section content is formatted or follow table belongs + // to the section, which content is formatted. + if ( static_cast<SwTabFrame*>(pFrame)->IsFollow() && + ( !pSect || pSect == pFrame->FindSctFrame() ) ) + { + static_cast<SwTabFrame*>(pFrame)->m_bLockBackMove = true; + } + } + + { + SwFrameDeleteGuard aDeletePageGuard(pSect ? pSect->FindPageFrame() : nullptr); + SwFrameDeleteGuard aDeleteGuard(pSect); + pFrame->Calc(pRenderContext); + } + + // #i11760# - reset control flag for follow format. + if ( pFrame->IsTextFrame() ) + { + static_cast<SwTextFrame*>(pFrame)->AllowFollowFormat(); + } + + // The keep-attribute can cause the position + // of the prev to be invalid: + // Do not consider invalid previous frame + // due to its keep-attribute, if current frame is a follow or is locked. + // #i44049# - do not consider invalid previous + // frame due to its keep-attribute, if it can't move forward. + // #i57765# - do not consider invalid previous + // frame, if current frame has a column/page break before attribute. + SwFrame* pTmpPrev = pFrame->FindPrev(); + SwFlowFrame* pTmpPrevFlowFrame = pTmpPrev && pTmpPrev->IsFlowFrame() ? SwFlowFrame::CastFlowFrame(pTmpPrev) : nullptr; + SwFlowFrame* pTmpFlowFrame = pFrame->IsFlowFrame() ? SwFlowFrame::CastFlowFrame(pFrame) : nullptr; + + bool bPrevInvalid = pTmpPrevFlowFrame && pTmpFlowFrame && + !pTmpFlowFrame->IsFollow() && + !StackHack::IsLocked() && // #i76382# + !pTmpFlowFrame->IsJoinLocked() && + !pTmpPrev->isFrameAreaPositionValid() && + pLay->IsAnLower( pTmpPrev ) && + pTmpPrevFlowFrame->IsKeep(pTmpPrev->GetAttrSet()->GetKeep(), pTmpPrev->GetBreakItem()) && + pTmpPrevFlowFrame->IsKeepFwdMoveAllowed(); + + // format floating screen objects anchored to the frame. + if ( !bPrevInvalid && pFrame->GetDrawObjs() && pLay->IsAnLower( pFrame ) ) + { + bool bAgain = false; + bool bRestartLayoutProcess = false; + size_t nCnt = pFrame->GetDrawObjs()->size(); + size_t i = 0; + while ( i < nCnt ) + { + // pFrame can move to a different page in FormatObj() + SwPageFrame *const pPageFrame = pFrame->FindPageFrame(); + + // #i28701# + SwAnchoredObject* pAnchoredObj = (*pFrame->GetDrawObjs())[i]; + assert(pAnchoredObj); + + // determine if anchored object has to be + // formatted and, in case, format it + if ( !pAnchoredObj->PositionLocked() && pAnchoredObj->IsFormatPossible() ) + { + // #i43737# - no invalidation of + // anchored object needed - causes loops for as-character + // anchored objects. + //pAnchoredObj->InvalidateObjPos(); + SwRect aRect( pAnchoredObj->GetObjRect() ); + if ( !SwObjectFormatter::FormatObj( *pAnchoredObj, pFrame, pPageFrame ) ) + { + bRestartLayoutProcess = true; + break; + } + // #i3317# - restart layout process, + // if the position of the anchored object is locked now. + if ( pAnchoredObj->PositionLocked() ) + { + bRestartLayoutProcess = true; + break; + } + + if ( aRect != pAnchoredObj->GetObjRect() ) + { + bAgain = true; + if ( pAgainObj2 == pAnchoredObj ) + { + OSL_FAIL( "::CalcContent(..) - loop detected, perform attribute changes to avoid the loop" ); + // Prevent oscillation + SwFrameFormat& rFormat = pAnchoredObj->GetFrameFormat(); + SwFormatSurround aAttr( rFormat.GetSurround() ); + if( css::text::WrapTextMode_THROUGH != aAttr.GetSurround() ) + { + // When on auto position, we can only set it to + // flow through + if ((rFormat.GetAnchor().GetAnchorId() == + RndStdIds::FLY_AT_CHAR) && + (css::text::WrapTextMode_PARALLEL == + aAttr.GetSurround())) + { + aAttr.SetSurround( css::text::WrapTextMode_THROUGH ); + } + else + { + aAttr.SetSurround( css::text::WrapTextMode_PARALLEL ); + } + rFormat.LockModify(); + rFormat.SetFormatAttr( aAttr ); + rFormat.UnlockModify(); + } + } + else + { + if ( pAgainObj1 == pAnchoredObj ) + pAgainObj2 = pAnchoredObj; + pAgainObj1 = pAnchoredObj; + } + } + + if ( !pFrame->GetDrawObjs() ) + break; + if ( pFrame->GetDrawObjs()->size() < nCnt ) + { + --nCnt; + // Do not increment index, in this case + continue; + } + } + ++i; + } + + // #i28701# - restart layout process, if + // requested by floating screen object formatting + if (bRestartLayoutProcess + // tdf#152106 loop control in multi-column sections to avoid of freezing + && nLoopControlRunsInMultiCol < nLoopControlMax + // tdf#142080 if it was already on next page, and still is, + // ignore restart, as restart could cause infinite loop + && (wasFrameLowerOfLay || pLay->IsAnLower(pFrame))) + { + bool bIsMultiColumn = pSect && pSect->GetSection() && pSect->Lower() && + pSect->Lower()->IsColumnFrame() && pSect->Lower()->GetNext(); + if ( bIsMultiColumn ) + ++nLoopControlRunsInMultiCol; + pFrame = pLay->ContainsAny(); + pAgainObj1 = nullptr; + pAgainObj2 = nullptr; + continue; + } + + // #i28701# - format anchor frame after its objects + // are formatted, if the wrapping style influence has to be considered. + if ( pLay->GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) + { + pFrame->Calc(pRenderContext); + } + + if ( bAgain ) + { + pFrame = pLay->ContainsContent(); + if ( pFrame && pFrame->IsInTab() ) + pFrame = pFrame->FindTabFrame(); + if( pFrame && pFrame->IsInSct() ) + { + SwSectionFrame* pTmp = pFrame->FindSctFrame(); + if( pTmp != pLay && pLay->IsAnLower( pTmp ) ) + pFrame = pTmp; + } + + if ( pFrame == pLoopControlCond ) + ++nLoopControlRuns; + else + { + nLoopControlRuns = 0; + pLoopControlCond = pFrame; + } + + if ( nLoopControlRuns < nLoopControlMax ) + continue; + + OSL_FAIL( "LoopControl in CalcContent" ); + } + } + if ( pFrame->IsTabFrame() ) + { + if ( static_cast<SwTabFrame*>(pFrame)->IsFollow() ) + static_cast<SwTabFrame*>(pFrame)->m_bLockBackMove = false; + } + + pFrame = bPrevInvalid ? pTmpPrev : pFrame->FindNext(); + if( !bPrevInvalid && pFrame && pFrame->IsSctFrame() && pSect ) + { + // Empty SectionFrames could be present here + while( pFrame && pFrame->IsSctFrame() && !static_cast<SwSectionFrame*>(pFrame)->GetSection() ) + pFrame = pFrame->FindNext(); + + // If FindNext returns the Follow of the original Area, we want to + // continue with this content as long as it flows back. + if( pFrame && pFrame->IsSctFrame() && ( pFrame == pSect->GetFollow() || + static_cast<SwSectionFrame*>(pFrame)->IsAnFollow( pSect ) ) ) + { + pFrame = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pFrame ) + pFrame->InvalidatePos_(); + } + } + // Stay in the pLay. + // Except for SectionFrames with Follow: the first ContentFrame of the + // Follow will be formatted, so that it gets a chance to move back + // into the pLay. Continue as long as these Frames land in pLay. + } while ( pFrame && + ( pLay->IsAnLower( pFrame ) || + ( pSect && + ( ( pSect->HasFollow() && + ( pLay->IsAnLower( pLast ) || + ( pLast->IsInSct() && + pLast->FindSctFrame()->IsAnFollow(pSect) ) ) && + pSect->GetFollow()->IsAnLower( pFrame ) ) || + ( pFrame->IsInSct() && + pFrame->FindSctFrame()->IsAnFollow( pSect ) ) ) ) ) ); + if( pSect ) + { + if( bCollect ) + { + pLay->GetFormat()->GetDoc()->getIDocumentLayoutAccess().GetLayouter()->InsertEndnotes(pSect); + pSect->CalcFootnoteContent(); + } + if( pSect->HasFollow() ) + { + SwSectionFrame* pNxt = pSect->GetFollow(); + while( pNxt && !pNxt->ContainsContent() ) + pNxt = pNxt->GetFollow(); + if( pNxt ) + pNxt->CalcFootnoteContent(); + } + if( bCollect ) + { + pFrame = pLay->ContainsAny(); + bCollect = false; + if( pFrame ) + continue; + } + } + break; + } + while( true ); +} + +void SwFlyFrame::MakeObjPos() +{ + if ( isFrameAreaPositionValid() ) + return; + + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + setFrameAreaPositionValid(true); + + // use new class to position object + GetAnchorFrame()->Calc(pRenderContext); + objectpositioning::SwToLayoutAnchoredObjectPosition + aObjPositioning( *GetVirtDrawObj() ); + aObjPositioning.CalcPosition(); + + // #i58280# + // update relative position + SetCurrRelPos( aObjPositioning.GetRelPos() ); + + { + SwRectFnSet aRectFnSet(GetAnchorFrame()); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos( aObjPositioning.GetRelPos() ); + aFrm.Pos() += aRectFnSet.GetPos(GetAnchorFrame()->getFrameArea()); + } + + // #i69335# + InvalidateObjRectWithSpaces(); +} + +void SwFlyFrame::MakePrtArea( const SwBorderAttrs &rAttrs ) +{ + if ( !isFramePrintAreaValid() ) + { + setFramePrintAreaValid(true); + + // consider vertical layout + SwRectFnSet aRectFnSet(this); + aRectFnSet.SetXMargins( *this, rAttrs.CalcLeftLine(), + rAttrs.CalcRightLine() ); + aRectFnSet.SetYMargins( *this, rAttrs.CalcTopLine(), + rAttrs.CalcBottomLine() ); + } +} + +void SwFlyFrame::MakeContentPos( const SwBorderAttrs &rAttrs ) +{ + if ( m_bValidContentPos ) + return; + + m_bValidContentPos = true; + + const SwTwips nUL = rAttrs.CalcTopLine() + rAttrs.CalcBottomLine(); + Size aRelSize( CalcRel( GetFormat()->GetFrameSize() ) ); + + SwRectFnSet aRectFnSet(this); + tools::Long nMinHeight = 0; + if( IsMinHeight() ) + nMinHeight = aRectFnSet.IsVert() ? aRelSize.Width() : aRelSize.Height(); + + Point aNewContentPos = getFramePrintArea().Pos(); + const SdrTextVertAdjust nAdjust = GetFormat()->GetTextVertAdjust().GetValue(); + + if( nAdjust != SDRTEXTVERTADJUST_TOP ) + { + const SwTwips nContentHeight = CalcContentHeight(&rAttrs, nMinHeight, nUL); + SwTwips nDiff = 0; + + if( nContentHeight != 0) + nDiff = aRectFnSet.GetHeight(getFramePrintArea()) - nContentHeight; + + if( nDiff > 0 ) + { + if( nAdjust == SDRTEXTVERTADJUST_CENTER ) + { + if( aRectFnSet.IsVertL2R() ) + aNewContentPos.setX(aNewContentPos.getX() + nDiff/2); + else if( aRectFnSet.IsVert() ) + aNewContentPos.setX(aNewContentPos.getX() - nDiff/2); + else + aNewContentPos.setY(aNewContentPos.getY() + nDiff/2); + } + else if( nAdjust == SDRTEXTVERTADJUST_BOTTOM ) + { + if( aRectFnSet.IsVertL2R() ) + aNewContentPos.setX(aNewContentPos.getX() + nDiff); + else if( aRectFnSet.IsVert() ) + aNewContentPos.setX(aNewContentPos.getX() - nDiff); + else + aNewContentPos.setY(aNewContentPos.getY() + nDiff); + } + } + } + if( aNewContentPos != ContentPos() ) + { + ContentPos() = aNewContentPos; + for( SwFrame *pFrame = Lower(); pFrame; pFrame = pFrame->GetNext()) + { + pFrame->InvalidatePos(); + } + } + +} + +void SwFlyFrame::InvalidateContentPos() +{ + m_bValidContentPos = false; + Invalidate_(); +} + +void SwFlyFrame::SelectionHasChanged(SwFEShell* pShell) +{ + SwWrtShell* pWrtSh = dynamic_cast< SwWrtShell* >(pShell); + if (pWrtSh == nullptr) + return; + + UpdateUnfloatButton(pWrtSh, IsShowUnfloatButton(pWrtSh)); +} + +bool SwFlyFrame::IsShowUnfloatButton(SwWrtShell* pWrtSh) const +{ + if (pWrtSh == nullptr) + return false; + + // In read only mode we don't allow unfloat operation + if (pWrtSh->GetViewOptions()->IsReadonly()) + return false; + + const SdrObject *pObj = GetFrameFormat().FindRealSdrObject(); + if (pObj == nullptr) + return false; + + // SwFlyFrame itself can mean images, ole objects, etc, but we interested in actual text frames + if (SwFEShell::GetObjCntType(*pObj) != OBJCNT_FLY) + return false; + + // We show the button only for the selected text frame + SwDrawView *pView = pWrtSh->Imp()->GetDrawView(); + if (pView == nullptr) + return false; + + // Fly frame can be selected only alone + if (pView->GetMarkedObjectList().GetMarkCount() != 1) + return false; + + if(!pView->IsObjMarked(pObj)) + return false; + + // A frame is a floating table if there is only one table (and maybe some whitespaces) inside it + int nTableCount = 0; + const SwFrame* pLower = GetLower(); + const SwTabFrame* pTable = nullptr; + while (pLower) + { + if (pLower->IsTabFrame()) + { + pTable = static_cast<const SwTabFrame*>(pLower); + ++nTableCount; + if (nTableCount > 1 || pTable == nullptr) + return false; + } + + if (pLower->IsTextFrame()) + { + const SwTextFrame* pTextFrame = static_cast<const SwTextFrame*>(pLower); + if (!pTextFrame->GetText().trim().isEmpty()) + return false; + } + pLower = pLower->GetNext(); + } + + if (nTableCount != 1 || pTable == nullptr) + return false; + + // Show the unfold button only for multipage tables + const SwBodyFrame *pBody = GetAnchorFrame()->FindBodyFrame(); + if (pBody == nullptr) + return false; + + tools::Long nBodyHeight = pBody->getFrameArea().Height(); + tools::Long nTableHeight = pTable->getFrameArea().Height(); + tools::Long nFrameOffset = std::abs(GetAnchorFrame()->getFrameArea().Top() - pBody->getFrameArea().Top()); + + return nBodyHeight < nTableHeight + nFrameOffset; +} + +void SwFlyFrame::ActiveUnfloatButton(SwWrtShell* pWrtSh) +{ + SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin(); + SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager(); + SwFrameControlPtr pControl = rMngr.GetControl(FrameControlType::FloatingTable, this); + if (pControl && pControl->GetWindow()) + { + pControl->GetWindow()->MouseButtonDown(MouseEvent()); + } +} + +void SwFlyFrame::UpdateUnfloatButton(SwWrtShell* pWrtSh, bool bShow) const +{ + if (pWrtSh == nullptr) + return; + + SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin(); + SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager(); + Point aTopRightPixel = rEditWin.LogicToPixel( getFrameArea().TopRight() ); + rMngr.SetUnfloatTableButton(this, bShow, aTopRightPixel); +} + +SwTwips SwFlyFrame::Grow_( SwTwips nDist, bool bTst ) +{ + SwRectFnSet aRectFnSet(this); + if ( Lower() && !IsColLocked() && !HasFixSize() ) + { + SwTwips nSize = aRectFnSet.GetHeight(getFrameArea()); + if( nSize > 0 && nDist > ( LONG_MAX - nSize ) ) + nDist = LONG_MAX - nSize; + + if ( nDist <= 0 ) + return 0; + + if ( Lower()->IsColumnFrame() ) + { // If it's a Column Frame, the Format takes control of the + // resizing (due to the adjustment). + if ( !bTst ) + { + // #i28701# - unlock position of Writer fly frame + UnlockPosition(); + InvalidatePos_(); + InvalidateSize(); + } + return 0; + } + + if ( !bTst ) + { + const SwRect aOld( GetObjRectWithSpaces() ); + InvalidateSize_(); + const bool bOldLock = m_bLocked; + Unlock(); + if ( IsFlyFreeFrame() ) + { + // #i37068# - no format of position here + // and prevent move in method <CheckClip(..)>. + // This is needed to prevent layout loop caused by nested + // Writer fly frames - inner Writer fly frames format its + // anchor, which grows/shrinks the outer Writer fly frame. + // Note: position will be invalidated below. + setFrameAreaPositionValid(true); + + // #i55416# + // Suppress format of width for autowidth frame, because the + // format of the width would call <SwTextFrame::CalcFitToContent()> + // for the lower frame, which initiated this grow. + const bool bOldFormatHeightOnly = m_bFormatHeightOnly; + const SwFormatFrameSize& rFrameSz = GetFormat()->GetFrameSize(); + if ( rFrameSz.GetWidthSizeType() != SwFrameSize::Fixed ) + { + m_bFormatHeightOnly = true; + } + SwViewShell* pSh = getRootFrame()->GetCurrShell(); + if (pSh) + { + static_cast<SwFlyFreeFrame*>(this)->SetNoMoveOnCheckClip( true ); + static_cast<SwFlyFreeFrame*>(this)->SwFlyFreeFrame::MakeAll(pSh->GetOut()); + static_cast<SwFlyFreeFrame*>(this)->SetNoMoveOnCheckClip( false ); + } + // #i55416# + if ( rFrameSz.GetWidthSizeType() != SwFrameSize::Fixed ) + { + m_bFormatHeightOnly = bOldFormatHeightOnly; + } + } + else + MakeAll(getRootFrame()->GetCurrShell()->GetOut()); + InvalidateSize_(); + InvalidatePos(); + if ( bOldLock ) + Lock(); + const SwRect aNew( GetObjRectWithSpaces() ); + if ( aOld != aNew ) + ::Notify( this, FindPageFrame(), aOld ); + return aRectFnSet.GetHeight(aNew)-aRectFnSet.GetHeight(aOld); + } + return nDist; + } + return 0; +} + +SwTwips SwFlyFrame::Shrink_( SwTwips nDist, bool bTst ) +{ + if( Lower() && !IsColLocked() && !HasFixSize() ) + { + SwRectFnSet aRectFnSet(this); + SwTwips nHeight = aRectFnSet.GetHeight(getFrameArea()); + if ( nDist > nHeight ) + nDist = nHeight; + + SwTwips nVal = nDist; + if ( IsMinHeight() ) + { + const SwFormatFrameSize& rFormatSize = GetFormat()->GetFrameSize(); + SwTwips nFormatHeight = aRectFnSet.IsVert() ? rFormatSize.GetWidth() : rFormatSize.GetHeight(); + + nVal = std::min( nDist, nHeight - nFormatHeight ); + } + + if ( nVal <= 0 ) + return 0; + + if ( Lower()->IsColumnFrame() ) + { // If it's a Column Frame, the Format takes control of the + // resizing (due to the adjustment). + if ( !bTst ) + { + SwRect aOld( GetObjRectWithSpaces() ); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nHeight - nVal ); + } + + // #i68520# + if ( nHeight - nVal != 0 ) + { + InvalidateObjRectWithSpaces(); + } + + nHeight = aRectFnSet.GetHeight(getFramePrintArea()); + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, nHeight - nVal ); + } + + InvalidatePos_(); + InvalidateSize(); + ::Notify( this, FindPageFrame(), aOld ); + NotifyDrawObj(); + if ( GetAnchorFrame()->IsInFly() ) + AnchorFrame()->FindFlyFrame()->Shrink( nDist, bTst ); + } + return 0; + } + + if ( !bTst ) + { + const SwRect aOld( GetObjRectWithSpaces() ); + InvalidateSize_(); + const bool bOldLocked = m_bLocked; + Unlock(); + if ( IsFlyFreeFrame() ) + { + // #i37068# - no format of position here + // and prevent move in method <CheckClip(..)>. + // This is needed to prevent layout loop caused by nested + // Writer fly frames - inner Writer fly frames format its + // anchor, which grows/shrinks the outer Writer fly frame. + // Note: position will be invalidated below. + setFrameAreaPositionValid(true); + + // #i55416# + // Suppress format of width for autowidth frame, because the + // format of the width would call <SwTextFrame::CalcFitToContent()> + // for the lower frame, which initiated this shrink. + const bool bOldFormatHeightOnly = m_bFormatHeightOnly; + const SwFormatFrameSize& rFrameSz = GetFormat()->GetFrameSize(); + if ( rFrameSz.GetWidthSizeType() != SwFrameSize::Fixed ) + { + m_bFormatHeightOnly = true; + } + static_cast<SwFlyFreeFrame*>(this)->SetNoMoveOnCheckClip( true ); + static_cast<SwFlyFreeFrame*>(this)->SwFlyFreeFrame::MakeAll(getRootFrame()->GetCurrShell()->GetOut()); + static_cast<SwFlyFreeFrame*>(this)->SetNoMoveOnCheckClip( false ); + // #i55416# + if ( rFrameSz.GetWidthSizeType() != SwFrameSize::Fixed ) + { + m_bFormatHeightOnly = bOldFormatHeightOnly; + } + } + else + MakeAll(getRootFrame()->GetCurrShell()->GetOut()); + InvalidateSize_(); + InvalidatePos(); + if ( bOldLocked ) + Lock(); + const SwRect aNew( GetObjRectWithSpaces() ); + if ( aOld != aNew ) + { + ::Notify( this, FindPageFrame(), aOld ); + if ( GetAnchorFrame()->IsInFly() ) + AnchorFrame()->FindFlyFrame()->Shrink( nDist, bTst ); + } + return aRectFnSet.GetHeight(aOld) - + aRectFnSet.GetHeight(aNew); + } + return nVal; + } + return 0; +} + +Size SwFlyFrame::ChgSize( const Size& aNewSize ) +{ + // #i53298# + // If the fly frame anchored at-paragraph or at-character contains an OLE + // object, assure that the new size fits into the current clipping area + // of the fly frame + Size aAdjustedNewSize( aNewSize ); + { + if ( dynamic_cast<SwFlyAtContentFrame*>(this) && + Lower() && dynamic_cast<SwNoTextFrame*>(Lower()) && + static_cast<SwNoTextFrame*>(Lower())->GetNode()->GetOLENode() ) + { + SwRect aClipRect; + ::CalcClipRect( GetVirtDrawObj(), aClipRect, false ); + if ( aAdjustedNewSize.Width() > aClipRect.Width() ) + { + aAdjustedNewSize.setWidth( aClipRect.Width() ); + } + if ( aAdjustedNewSize.Height() > aClipRect.Height() ) + { + aAdjustedNewSize.setWidth( aClipRect.Height() ); + } + } + } + + if ( aAdjustedNewSize != getFrameArea().SSize() ) + { + SwFrameFormat *pFormat = GetFormat(); + SwFormatFrameSize aSz( pFormat->GetFrameSize() ); + aSz.SetWidth( aAdjustedNewSize.Width() ); + aSz.SetHeight( aAdjustedNewSize.Height() ); + // go via the Doc for UNDO + pFormat->GetDoc()->SetAttr( aSz, *pFormat ); + return aSz.GetSize(); + } + else + return getFrameArea().SSize(); +} + +bool SwFlyFrame::IsLowerOf( const SwLayoutFrame* pUpperFrame ) const +{ + OSL_ENSURE( GetAnchorFrame(), "8-( Fly is lost in Space." ); + const SwFrame* pFrame = GetAnchorFrame(); + do + { + if ( pFrame == pUpperFrame ) + return true; + pFrame = pFrame->IsFlyFrame() + ? static_cast<const SwFlyFrame*>(pFrame)->GetAnchorFrame() + : pFrame->GetUpper(); + } while ( pFrame ); + return false; +} + +void SwFlyFrame::Cut() +{ +} + +void SwFrame::AppendFly( SwFlyFrame *pNew ) +{ + if (!m_pDrawObjs) + { + m_pDrawObjs.reset(new SwSortedObjs()); + } + m_pDrawObjs->Insert( *pNew ); + pNew->ChgAnchorFrame( this ); + + // Register at the page + // If there's none present, register via SwPageFrame::PreparePage + SwPageFrame* pPage = FindPageFrame(); + if ( pPage != nullptr ) + { + pPage->AppendFlyToPage( pNew ); + } +} + +void SwFrame::RemoveFly( SwFlyFrame *pToRemove ) +{ + // Deregister from the page + // Could already have happened, if the page was already destructed + SwPageFrame *pPage = pToRemove->FindPageFrame(); + if ( pPage && pPage->GetSortedObjs() ) + { + pPage->RemoveFlyFromPage( pToRemove ); + } + // #i73201# +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + else + { + if ( pToRemove->IsAccessibleFrame() && + pToRemove->GetFormat() && + !pToRemove->IsFlyInContentFrame() ) + { + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() ) + { + SwViewShell *pVSh = pRootFrame->GetCurrShell(); + if( pVSh && pVSh->Imp() ) + { + pVSh->Imp()->DisposeAccessibleFrame( pToRemove ); + } + } + } + } +#endif + + m_pDrawObjs->Remove(*pToRemove); + if (!m_pDrawObjs->size()) + { + m_pDrawObjs.reset(); + } + + pToRemove->ChgAnchorFrame( nullptr ); + + if ( !pToRemove->IsFlyInContentFrame() && GetUpper() && IsInTab() )//MA_FLY_HEIGHT + GetUpper()->InvalidateSize(); +} + +void SwFrame::AppendDrawObj( SwAnchoredObject& _rNewObj ) +{ + assert(!m_pDrawObjs || m_pDrawObjs->is_sorted()); + + if ( dynamic_cast<const SwAnchoredDrawObject*>( &_rNewObj) == nullptr ) + { + OSL_FAIL( "SwFrame::AppendDrawObj(..) - anchored object of unexpected type -> object not appended" ); + return; + } + + if ( dynamic_cast<const SwDrawVirtObj*>(_rNewObj.GetDrawObj()) == nullptr && + _rNewObj.GetAnchorFrame() && _rNewObj.GetAnchorFrame() != this ) + { + assert(!m_pDrawObjs || m_pDrawObjs->is_sorted()); + // perform disconnect from layout, if 'master' drawing object is appended + // to a new frame. + static_cast<SwDrawContact*>(::GetUserCall( _rNewObj.GetDrawObj() ))-> + DisconnectFromLayout( false ); + assert(!m_pDrawObjs || m_pDrawObjs->is_sorted()); + } + + if ( _rNewObj.GetAnchorFrame() != this ) + { + if (!m_pDrawObjs) + { + m_pDrawObjs.reset(new SwSortedObjs()); + } + m_pDrawObjs->Insert(_rNewObj); + _rNewObj.ChgAnchorFrame( this ); + } + + // #i113730# + // Assure the control objects and group objects containing controls are on the control layer + if ( ::CheckControlLayer( _rNewObj.DrawObj() ) ) + { + const IDocumentDrawModelAccess& rIDDMA = getIDocumentDrawModelAccess(); + const SdrLayerID aCurrentLayer(_rNewObj.DrawObj()->GetLayer()); + const SdrLayerID aControlLayerID(rIDDMA.GetControlsId()); + const SdrLayerID aInvisibleControlLayerID(rIDDMA.GetInvisibleControlsId()); + + if(aCurrentLayer != aControlLayerID && aCurrentLayer != aInvisibleControlLayerID) + { + if ( aCurrentLayer == rIDDMA.GetInvisibleHellId() || + aCurrentLayer == rIDDMA.GetInvisibleHeavenId() ) + { + _rNewObj.DrawObj()->SetLayer(aInvisibleControlLayerID); + } + else + { + _rNewObj.DrawObj()->SetLayer(aControlLayerID); + } + //The layer is part of the key used to sort the obj, so update + //its position if the layer changed. + m_pDrawObjs->Update(_rNewObj); + } + } + + // no direct positioning needed, but invalidate the drawing object position + _rNewObj.InvalidateObjPos(); + + // register at page frame + SwPageFrame* pPage = FindPageFrame(); + if ( pPage ) + { + pPage->AppendDrawObjToPage( _rNewObj ); + } + + // Notify accessible layout. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + SwViewShell* pSh = getRootFrame()->GetCurrShell(); + if( pSh ) + { + SwRootFrame* pLayout = getRootFrame(); + if( pLayout && pLayout->IsAnyShellAccessible() ) + { + pSh->Imp()->AddAccessibleObj( _rNewObj.GetDrawObj() ); + } + } +#endif + + assert(!m_pDrawObjs || m_pDrawObjs->is_sorted()); +} + +void SwFrame::RemoveDrawObj( SwAnchoredObject& _rToRemoveObj ) +{ + // Notify accessible layout. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + SwViewShell* pSh = getRootFrame()->GetCurrShell(); + if( pSh ) + { + SwRootFrame* pLayout = getRootFrame(); + if (pLayout && pLayout->IsAnyShellAccessible()) + pSh->Imp()->DisposeAccessibleObj(_rToRemoveObj.GetDrawObj(), false); + } +#endif + + // deregister from page frame + SwPageFrame* pPage = _rToRemoveObj.GetPageFrame(); + if ( pPage && pPage->GetSortedObjs() ) + pPage->RemoveDrawObjFromPage( _rToRemoveObj ); + + m_pDrawObjs->Remove(_rToRemoveObj); + if (!m_pDrawObjs->size()) + { + m_pDrawObjs.reset(); + } + _rToRemoveObj.ChgAnchorFrame( nullptr ); + + assert(!m_pDrawObjs || m_pDrawObjs->is_sorted()); +} + +void SwFrame::InvalidateObjs( const bool _bNoInvaOfAsCharAnchoredObjs ) +{ + if ( !GetDrawObjs() ) + return; + + // #i26945# - determine page the frame is on, + // in order to check, if anchored object is registered at the same + // page. + const SwPageFrame* pPageFrame = FindPageFrame(); + // #i28701# - re-factoring + for (SwAnchoredObject* pAnchoredObj : *GetDrawObjs()) + { + if ( _bNoInvaOfAsCharAnchoredObjs && + (pAnchoredObj->GetFrameFormat().GetAnchor().GetAnchorId() + == RndStdIds::FLY_AS_CHAR) ) + { + continue; + } + // #i26945# - no invalidation, if anchored object + // isn't registered at the same page and instead is registered at + // the page, where its anchor character text frame is on. + if ( pAnchoredObj->GetPageFrame() && + pAnchoredObj->GetPageFrame() != pPageFrame ) + { + SwTextFrame* pAnchorCharFrame = pAnchoredObj->FindAnchorCharFrame(); + if ( pAnchorCharFrame && + pAnchoredObj->GetPageFrame() == pAnchorCharFrame->FindPageFrame() ) + { + continue; + } + // #115759# - unlock its position, if anchored + // object isn't registered at the page, where its anchor + // character text frame is on, respectively if it has no + // anchor character text frame. + else + { + pAnchoredObj->UnlockPosition(); + } + } + // #i51474# - reset flag, that anchored object + // has cleared environment, and unlock its position, if the anchored + // object is registered at the same page as the anchor frame is on. + if ( pAnchoredObj->ClearedEnvironment() && + pAnchoredObj->GetPageFrame() && + pAnchoredObj->GetPageFrame() == pPageFrame ) + { + pAnchoredObj->UnlockPosition(); + pAnchoredObj->SetClearedEnvironment( false ); + } + // distinguish between writer fly frames and drawing objects + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + pFly->Invalidate_(); + pFly->InvalidatePos_(); + } + else + { + pAnchoredObj->InvalidateObjPos(); + } + } // end of loop on objects, which are connected to the frame +} + +// #i26945# - correct check, if anchored object is a lower +// of the layout frame. E.g., anchor character text frame can be a follow text +// frame. +// #i44016# - add parameter <_bUnlockPosOfObjs> to +// force an unlockposition call for the lower objects. +void SwLayoutFrame::NotifyLowerObjs( const bool _bUnlockPosOfObjs ) +{ + // invalidate lower floating screen objects + SwPageFrame* pPageFrame = FindPageFrame(); + if ( !(pPageFrame && pPageFrame->GetSortedObjs()) ) + return; + + SwSortedObjs& rObjs = *(pPageFrame->GetSortedObjs()); + for (SwAnchoredObject* pObj : rObjs) + { + // #i26945# - check if anchored object is a lower + // of the layout frame is changed to check, if its anchor frame + // is a lower of the layout frame. + // Determine the anchor frame - usually it's the anchor frame, + // for at-character/as-character anchored objects the anchor character + // text frame is taken. + const SwFrame* pAnchorFrame = pObj->GetAnchorFrameContainingAnchPos(); + if ( auto pFly = pObj->DynCastFlyFrame() ) + { + if ( pFly->getFrameArea().Left() == FAR_AWAY ) + continue; + + if ( pFly->IsAnLower( this ) ) + continue; + + // #i26945# - use <pAnchorFrame> to check, if + // fly frame is lower of layout frame resp. if fly frame is + // at a different page registered as its anchor frame is on. + const bool bLow = IsAnLower( pAnchorFrame ); + if ( bLow || pAnchorFrame->FindPageFrame() != pPageFrame ) + { + pFly->Invalidate_( pPageFrame ); + if ( !bLow || pFly->IsFlyAtContentFrame() ) + { + // #i44016# + if ( _bUnlockPosOfObjs ) + { + pFly->UnlockPosition(); + } + pFly->InvalidatePos_(); + } + else + pFly->InvalidatePrt_(); + } + } + else + { + assert( dynamic_cast<const SwAnchoredDrawObject*>( pObj) && + "<SwLayoutFrame::NotifyFlys() - anchored object of unexpected type" ); + // #i26945# - use <pAnchorFrame> to check, if + // fly frame is lower of layout frame resp. if fly frame is + // at a different page registered as its anchor frame is on. + if ( IsAnLower( pAnchorFrame ) || + pAnchorFrame->FindPageFrame() != pPageFrame ) + { + // #i44016# + if ( _bUnlockPosOfObjs ) + { + pObj->UnlockPosition(); + } + pObj->InvalidateObjPos(); + } + } + } +} + +void SwFlyFrame::NotifyDrawObj() +{ + SwVirtFlyDrawObj* pObj = GetVirtDrawObj(); + pObj->SetRect(); + pObj->SetBoundAndSnapRectsDirty(); + pObj->SetChanged(); + pObj->BroadcastObjectChange(); + + if ( GetFormat()->GetSurround().IsContour() ) + { + ClrContourCache( pObj ); + } + else if(IsFlyFreeFrame() && static_cast< const SwFlyFreeFrame* >(this)->supportsAutoContour()) + { + // RotateFlyFrame3: Also need to clear when changes happen + // Caution: isTransformableSwFrame is already reset when resetting rotation, so + // *additionally* reset in SwFlyFreeFrame::MakeAll when no more rotation + ClrContourCache( pObj ); + } +} + +Size SwFlyFrame::CalcRel( const SwFormatFrameSize &rSz ) const +{ + Size aRet( rSz.GetSize() ); + + const SwFrame *pRel = IsFlyLayFrame() ? GetAnchorFrame() : GetAnchorFrame()->GetUpper(); + if( pRel ) // LAYER_IMPL + { + tools::Long nRelWidth = LONG_MAX, nRelHeight = LONG_MAX; + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( ( pRel->IsBodyFrame() || pRel->IsPageFrame() ) && + pSh && pSh->GetViewOptions()->getBrowseMode() && + pSh->VisArea().HasArea() ) + { + nRelWidth = pSh->GetBrowseWidth(); + nRelHeight = pSh->VisArea().Height(); + Size aBorder = pSh->GetOut()->PixelToLogic( pSh->GetBrowseBorder() ); + nRelWidth = std::min( nRelWidth, pRel->getFramePrintArea().Width() ); + nRelHeight -= 2*aBorder.Height(); + nRelHeight = std::min( nRelHeight, pRel->getFramePrintArea().Height() ); + } + + // At the moment only the "== PAGE_FRAME" and "!= PAGE_FRAME" cases are handled. + // When size is a relative to page size, ignore size of SwBodyFrame. + if (rSz.GetWidthPercentRelation() != text::RelOrientation::PAGE_FRAME) + nRelWidth = std::min( nRelWidth, pRel->getFramePrintArea().Width() ); + else if ( pRel->IsPageFrame() ) + nRelWidth = std::min( nRelWidth, pRel->getFrameArea().Width() ); + + if (rSz.GetHeightPercentRelation() != text::RelOrientation::PAGE_FRAME) + nRelHeight = std::min( nRelHeight, pRel->getFramePrintArea().Height() ); + else if ( pRel->IsPageFrame() ) + nRelHeight = std::min( nRelHeight, pRel->getFrameArea().Height() ); + + if( !pRel->IsPageFrame() ) + { + const SwPageFrame* pPage = FindPageFrame(); + if( pPage ) + { + if (rSz.GetWidthPercentRelation() == text::RelOrientation::PAGE_FRAME) + // Ignore margins of pPage. + nRelWidth = std::min( nRelWidth, pPage->getFrameArea().Width() ); + else + nRelWidth = std::min( nRelWidth, pPage->getFramePrintArea().Width() ); + if (rSz.GetHeightPercentRelation() == text::RelOrientation::PAGE_FRAME) + // Ignore margins of pPage. + nRelHeight = std::min( nRelHeight, pPage->getFrameArea().Height() ); + else + nRelHeight = std::min( nRelHeight, pPage->getFramePrintArea().Height() ); + } + } + + if ( rSz.GetWidthPercent() && rSz.GetWidthPercent() != SwFormatFrameSize::SYNCED ) + aRet.setWidth( nRelWidth * rSz.GetWidthPercent() / 100 ); + if ( rSz.GetHeightPercent() && rSz.GetHeightPercent() != SwFormatFrameSize::SYNCED ) + aRet.setHeight( nRelHeight * rSz.GetHeightPercent() / 100 ); + + if ( rSz.GetHeight() && rSz.GetWidthPercent() == SwFormatFrameSize::SYNCED ) + { + aRet.setWidth( aRet.Width() * ( aRet.Height()) ); + aRet.setWidth( aRet.Width() / ( rSz.GetHeight()) ); + } + else if ( rSz.GetWidth() && rSz.GetHeightPercent() == SwFormatFrameSize::SYNCED ) + { + aRet.setHeight( aRet.Height() * ( aRet.Width()) ); + aRet.setHeight( aRet.Height() / ( rSz.GetWidth()) ); + } + } + return aRet; +} + +static SwTwips lcl_CalcAutoWidth( const SwLayoutFrame& rFrame ) +{ + SwTwips nRet = 0; + SwTwips nMin = 0; + const SwFrame* pFrame = rFrame.Lower(); + + // No autowidth defined for columned frames + if ( !pFrame || pFrame->IsColumnFrame() ) + return nRet; + + int nParagraphCount = 0; + while ( pFrame ) + { + nParagraphCount++; + if ( pFrame->IsSctFrame() ) + { + nMin = lcl_CalcAutoWidth( *static_cast<const SwSectionFrame*>(pFrame) ); + } + if ( pFrame->IsTextFrame() ) + { + nMin = const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pFrame))->CalcFitToContent(); + const SvxLRSpaceItem &rSpace = + static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet().GetLRSpace(); + if (!static_cast<const SwTextFrame*>(pFrame)->IsLocked()) + nMin += rSpace.GetRight() + rSpace.GetTextLeft() + rSpace.GetTextFirstLineOffset(); + } + else if ( pFrame->IsTabFrame() ) + { + const SwFormatFrameSize& rTableFormatSz = static_cast<const SwTabFrame*>(pFrame)->GetTable()->GetFrameFormat()->GetFrameSize(); + if ( USHRT_MAX == rTableFormatSz.GetSize().Width() || + text::HoriOrientation::NONE == static_cast<const SwTabFrame*>(pFrame)->GetFormat()->GetHoriOrient().GetHoriOrient() ) + { + const SwPageFrame* pPage = rFrame.FindPageFrame(); + // auto width table + nMin = pFrame->GetUpper()->IsVertical() ? + pPage->getFramePrintArea().Height() : + pPage->getFramePrintArea().Width(); + } + else + { + nMin = rTableFormatSz.GetSize().Width(); + } + } + + if ( nMin > nRet ) + nRet = nMin; + + pFrame = pFrame->GetNext(); + } + + // tdf#124423 In Microsoft compatibility mode: widen the frame to max (PrintArea of the frame it anchored to) if it contains at least 2 paragraphs, + // or 1 paragraph wider than its parent area. + if (rFrame.GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::FRAME_AUTOWIDTH_WITH_MORE_PARA)) + { + const SwFrame* pFrameRect = rFrame.IsFlyFrame() ? static_cast<const SwFlyFrame*>(&rFrame)->GetAnchorFrame() : rFrame.Lower()->FindPageFrame(); + SwTwips nParentWidth = rFrame.IsVertical() ? pFrameRect->getFramePrintArea().Height() : pFrameRect->getFramePrintArea().Width(); + if (nParagraphCount > 1 || nRet > nParentWidth) + { + return nParentWidth; + } + } + + return nRet; +} + +/// #i13147# - If called for paint and the <SwNoTextFrame> contains +/// a graphic, load of intrinsic graphic has to be avoided. +bool SwFlyFrame::GetContour( tools::PolyPolygon& rContour, + const bool _bForPaint ) const +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + bool bRet = false; + const bool bIsCandidate(Lower() && Lower()->IsNoTextFrame()); + + if(bIsCandidate) + { + if(GetFormat()->GetSurround().IsContour()) + { + SwNoTextNode *pNd = const_cast<SwNoTextNode*>(static_cast<const SwNoTextNode*>(static_cast<const SwNoTextFrame*>(Lower())->GetNode())); + // #i13147# - determine <GraphicObject> instead of <Graphic> + // in order to avoid load of graphic, if <SwNoTextNode> contains a graphic + // node and method is called for paint. + std::unique_ptr<GraphicObject> xTmpGrfObj; + const GraphicObject* pGrfObj = nullptr; + const SwGrfNode* pGrfNd = pNd->GetGrfNode(); + if ( pGrfNd && _bForPaint ) + { + pGrfObj = &(pGrfNd->GetGrfObj()); + } + else + { + xTmpGrfObj.reset(new GraphicObject(pNd->GetGraphic())); + pGrfObj = xTmpGrfObj.get(); + } + assert(pGrfObj && "SwFlyFrame::GetContour() - No Graphic/GraphicObject found at <SwNoTextNode>."); + if (pGrfObj->GetType() != GraphicType::NONE) + { + if( !pNd->HasContour() ) + { + //#i13147# - no <CreateContour> for a graphic + // during paint. Thus, return (value of <bRet> should be <false>). + if ( pGrfNd && _bForPaint ) + { + OSL_FAIL( "SwFlyFrame::GetContour() - No Contour found at <SwNoTextNode> during paint." ); + return bRet; + } + pNd->CreateContour(); + } + pNd->GetContour( rContour ); + // The Node holds the Polygon matching the original size of the graphic + // We need to include the scaling here + SwRect aClip; + SwRect aOrig; + Lower()->Calc(pRenderContext); + static_cast<const SwNoTextFrame*>(Lower())->GetGrfArea( aClip, &aOrig ); + // #i13147# - copy method code <SvxContourDlg::ScaleContour(..)> + // in order to avoid that graphic has to be loaded for contour scale. + //SvxContourDlg::ScaleContour( rContour, aGrf, MapUnit::MapTwip, aOrig.SSize() ); + { + OutputDevice* pOutDev = Application::GetDefaultDevice(); + const MapMode aDispMap( MapUnit::MapTwip ); + const MapMode aGrfMap( pGrfObj->GetPrefMapMode() ); + const Size aGrfSize( pGrfObj->GetPrefSize() ); + Size aOrgSize; + Point aNewPoint; + bool bPixelMap = aGrfMap.GetMapUnit() == MapUnit::MapPixel; + + if ( bPixelMap ) + aOrgSize = pOutDev->PixelToLogic( aGrfSize, aDispMap ); + else + aOrgSize = OutputDevice::LogicToLogic( aGrfSize, aGrfMap, aDispMap ); + + if ( aOrgSize.Width() && aOrgSize.Height() ) + { + double fScaleX = static_cast<double>(aOrig.Width()) / aOrgSize.Width(); + double fScaleY = static_cast<double>(aOrig.Height()) / aOrgSize.Height(); + + for ( sal_uInt16 j = 0, nPolyCount = rContour.Count(); j < nPolyCount; j++ ) + { + tools::Polygon& rPoly = rContour[ j ]; + + for ( sal_uInt16 i = 0, nCount = rPoly.GetSize(); i < nCount; i++ ) + { + if ( bPixelMap ) + aNewPoint = pOutDev->PixelToLogic( rPoly[ i ], aDispMap ); + else + aNewPoint = OutputDevice::LogicToLogic( rPoly[ i ], aGrfMap, aDispMap ); + + rPoly[ i ] = Point( FRound( aNewPoint.getX() * fScaleX ), FRound( aNewPoint.getY() * fScaleY ) ); + } + } + } + } + // destroy created <GraphicObject>. + xTmpGrfObj.reset(); + rContour.Move( aOrig.Left(), aOrig.Top() ); + if( !aClip.Width() ) + aClip.Width( 1 ); + if( !aClip.Height() ) + aClip.Height( 1 ); + rContour.Clip( aClip.SVRect() ); + rContour.Optimize(PolyOptimizeFlags::CLOSE); + bRet = true; + } + } + else if (IsFlyFreeFrame()) + { + const SwFlyFreeFrame* pSwFlyFreeFrame(static_cast< const SwFlyFreeFrame* >(this)); + + if(nullptr != pSwFlyFreeFrame && + pSwFlyFreeFrame->supportsAutoContour() && + // isTransformableSwFrame already used in supportsAutoContour(), but + // better check twice when it may get changed there... + pSwFlyFreeFrame->isTransformableSwFrame()) + { + // RotateFlyFrame: use untransformed SwFrame to allow text floating around. + // Will be transformed below + const TransformableSwFrame* pTransformableSwFrame(pSwFlyFreeFrame->getTransformableSwFrame()); + const SwRect aFrameArea(pTransformableSwFrame->getUntransformedFrameArea()); + rContour = tools::PolyPolygon(tools::Polygon(aFrameArea.SVRect())); + bRet = (0 != rContour.Count()); + } + } + + if(bRet && 0 != rContour.Count()) + { + if (IsFlyFreeFrame() && + static_cast< const SwFlyFreeFrame* >(this)->isTransformableSwFrame()) + { + // Need to adapt contour to transformation + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + getFrameAreaTransformation().decompose(aScale, aTranslate, fRotate, fShearX); + + if(!basegfx::fTools::equalZero(fRotate)) + { + basegfx::B2DPolyPolygon aSource(rContour.getB2DPolyPolygon()); + const basegfx::B2DPoint aCenter(getFrameAreaTransformation() * basegfx::B2DPoint(0.5, 0.5)); + const basegfx::B2DHomMatrix aRotateAroundCenter( + basegfx::utils::createRotateAroundPoint( + aCenter.getX(), + aCenter.getY(), + fRotate)); + aSource.transform(aRotateAroundCenter); + rContour = tools::PolyPolygon(aSource); + } + } + } + } + + return bRet; +} + + +const SwVirtFlyDrawObj* SwFlyFrame::GetVirtDrawObj() const +{ + return static_cast<const SwVirtFlyDrawObj*>(GetDrawObj()); +} +SwVirtFlyDrawObj* SwFlyFrame::GetVirtDrawObj() +{ + return static_cast<SwVirtFlyDrawObj*>(DrawObj()); +} + +// implementation of pure virtual method declared in +// base class <SwAnchoredObject> + +void SwFlyFrame::InvalidateObjPos() +{ + InvalidatePos(); + // #i68520# + InvalidateObjRectWithSpaces(); +} + +SwFrameFormat& SwFlyFrame::GetFrameFormat() +{ + OSL_ENSURE( GetFormat(), + "<SwFlyFrame::GetFrameFormat()> - missing frame format -> crash." ); + return *GetFormat(); +} +const SwFrameFormat& SwFlyFrame::GetFrameFormat() const +{ + OSL_ENSURE( GetFormat(), + "<SwFlyFrame::GetFrameFormat()> - missing frame format -> crash." ); + return *GetFormat(); +} + +SwRect SwFlyFrame::GetObjRect() const +{ + return getFrameArea(); +} + +// #i70122# +// for Writer fly frames the bounding rectangle equals the object rectangles +SwRect SwFlyFrame::GetObjBoundRect() const +{ + return GetObjRect(); +} + +// #i68520# +bool SwFlyFrame::SetObjTop_( const SwTwips _nTop ) +{ + const bool bChanged( getFrameArea().Pos().getY() != _nTop ); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setY(_nTop); + + return bChanged; +} +bool SwFlyFrame::SetObjLeft_( const SwTwips _nLeft ) +{ + const bool bChanged( getFrameArea().Pos().getX() != _nLeft ); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX(_nLeft); + + return bChanged; +} + +/** method to assure that anchored object is registered at the correct + page frame + + OD 2004-07-02 #i28701# +*/ +void SwFlyFrame::RegisterAtCorrectPage() +{ + // default behaviour is to do nothing. +} + +void SwFlyFrame::RegisterAtPage(SwPageFrame &) +{ + // default behaviour is to do nothing. +} + +/** method to determine, if a <MakeAll()> on the Writer fly frame is possible + + OD 2004-05-11 #i28701# +*/ +bool SwFlyFrame::IsFormatPossible() const +{ + return SwAnchoredObject::IsFormatPossible() && + !IsLocked() && !IsColLocked(); +} + +void SwFlyFrame::GetAnchoredObjects( std::vector<SwAnchoredObject*>& aVector, const SwFormat& rFormat ) +{ + SwIterator<SwFlyFrame,SwFormat> aIter( rFormat ); + for( SwFlyFrame* pFlyFrame = aIter.First(); pFlyFrame; pFlyFrame = aIter.Next() ) + aVector.push_back( pFlyFrame ); +} + +const SwFlyFrameFormat * SwFlyFrame::GetFormat() const +{ + return static_cast< const SwFlyFrameFormat * >( GetDep() ); +} + +SwFlyFrameFormat * SwFlyFrame::GetFormat() +{ + return static_cast< SwFlyFrameFormat * >( GetDep() ); +} + +void SwFlyFrame::Calc(vcl::RenderContext* pRenderContext) const +{ + if ( !m_bValidContentPos ) + const_cast<SwFlyFrame*>(this)->PrepareMake(pRenderContext); + else + SwLayoutFrame::Calc(pRenderContext); +} + +SwTwips SwFlyFrame::CalcContentHeight(const SwBorderAttrs *pAttrs, const SwTwips nMinHeight, const SwTwips nUL) +{ + SwRectFnSet aRectFnSet(this); + SwTwips nHeight = 0; + if ( Lower() ) + { + if ( Lower()->IsColumnFrame() ) + { + FormatWidthCols( *pAttrs, nUL, nMinHeight ); + nHeight = aRectFnSet.GetHeight(Lower()->getFrameArea()); + } + else + { + SwFrame *pFrame = Lower(); + while ( pFrame ) + { + nHeight += aRectFnSet.GetHeight(pFrame->getFrameArea()); + if( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsUndersized() ) + // This TextFrame would like to be a bit larger + nHeight += static_cast<SwTextFrame*>(pFrame)->GetParHeight() + - aRectFnSet.GetHeight(pFrame->getFramePrintArea()); + else if( pFrame->IsSctFrame() && static_cast<SwSectionFrame*>(pFrame)->IsUndersized() ) + nHeight += static_cast<SwSectionFrame*>(pFrame)->Undersize(); + pFrame = pFrame->GetNext(); + } + } + if ( GetDrawObjs() ) + { + const size_t nCnt = GetDrawObjs()->size(); + SwTwips nTop = aRectFnSet.GetTop(getFrameArea()); + SwTwips nBorder = aRectFnSet.GetHeight(getFrameArea()) - + aRectFnSet.GetHeight(getFramePrintArea()); + for ( size_t i = 0; i < nCnt; ++i ) + { + SwAnchoredObject* pAnchoredObj = (*GetDrawObjs())[i]; + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + // consider only Writer fly frames, which follow the text flow. + if ( pFly->IsFlyLayFrame() && + pFly->getFrameArea().Top() != FAR_AWAY && + pFly->GetFormat()->GetFollowTextFlow().GetValue() ) + { + SwTwips nDist = -aRectFnSet.BottomDist( pFly->getFrameArea(), nTop ); + if( nDist > nBorder + nHeight ) + nHeight = nDist - nBorder; + } + } + } + } + } + return nHeight; +} + +const SwFormatAnchor* SwFlyFrame::GetAnchorFromPoolItem(const SfxPoolItem& rItem) +{ + switch(rItem.Which()) + { + case RES_ATTRSET_CHG: + return rItem.StaticWhichCast(RES_ATTRSET_CHG).GetChgSet()->GetItem(RES_ANCHOR, false); + case RES_ANCHOR: + return static_cast<const SwFormatAnchor*>(&rItem); + default: + return nullptr; + } +} + +const SwFlyFrame* SwFlyFrame::DynCastFlyFrame() const +{ + return this; +} + +SwFlyFrame* SwFlyFrame::DynCastFlyFrame() +{ + return this; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/flycnt.cxx b/sw/source/core/layout/flycnt.cxx new file mode 100644 index 000000000..f7611392d --- /dev/null +++ b/sw/source/core/layout/flycnt.cxx @@ -0,0 +1,1522 @@ +/* -*- 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 <sal/log.hxx> +#include <osl/diagnose.h> +#include <svx/swframetypes.hxx> +#include <pagefrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <doc.hxx> +#include <pam.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <frmtool.hxx> +#include <dflyobj.hxx> +#include <fmtanchr.hxx> +#include <fmtornt.hxx> +#include <fmtfsize.hxx> +#include <fmtsrnd.hxx> +#include <txatbase.hxx> + +#include <tabfrm.hxx> +#include <flyfrms.hxx> +#include <crstate.hxx> +#include <sectfrm.hxx> + +#include <tocntntanchoredobjectposition.hxx> +#include <sortedobjs.hxx> +#include <layouter.hxx> +#include "objectformattertxtfrm.hxx" +#include <HandleAnchorNodeChg.hxx> +#include <ndtxt.hxx> +#include <textboxhelper.hxx> +#include <fmtfollowtextflow.hxx> +#include <unoprnms.hxx> + +using namespace ::com::sun::star; + +namespace +{ + +SwTwips lcl_GetTopForObjPos(const SwContentFrame* pCnt, const bool bVert, const bool bVertL2R) +{ + if ( bVert ) + { + SwTwips aResult = pCnt->getFrameArea().Left(); + if ( bVertL2R ) + aResult += pCnt->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); + else + aResult += pCnt->getFrameArea().Width() - pCnt->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); + return aResult; + } + else + return pCnt->getFrameArea().Top() + pCnt->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); +} + +} + +SwFlyAtContentFrame::SwFlyAtContentFrame( SwFlyFrameFormat *pFormat, SwFrame* pSib, SwFrame *pAnch ) : + SwFlyFreeFrame( pFormat, pSib, pAnch ) +{ + m_bAtCnt = true; + m_bAutoPosition = (RndStdIds::FLY_AT_CHAR == pFormat->GetAnchor().GetAnchorId()); +} + +// #i28701# + +void SwFlyAtContentFrame::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + { + SwFlyFrame::SwClientNotify(rMod, rHint); + return; + } + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + const SwFormatAnchor* pAnch = pLegacy->m_pNew ? GetAnchorFromPoolItem(*pLegacy->m_pNew) : nullptr; + if(!pAnch) + { + SwFlyFrame::SwClientNotify(rMod, rHint); + return; + } + OSL_ENSURE(pAnch->GetAnchorId() == GetFormat()->GetAnchor().GetAnchorId(), + "Illegal change of anchor type."); + + //Unregister, get hold of a new anchor and attach it + SwRect aOld(GetObjRectWithSpaces()); + SwPageFrame* pOldPage = FindPageFrame(); + const SwFrame* pOldAnchor = GetAnchorFrame(); + SwContentFrame* pContent = const_cast<SwContentFrame*>(static_cast<const SwContentFrame*>(GetAnchorFrame())); + AnchorFrame()->RemoveFly(this); + + const bool bBodyFootnote = (pContent->IsInDocBody() || pContent->IsInFootnote()); + + // Search the new anchor using the NodeIdx; the relation between old + // and new NodeIdx determines the search direction + const SwNodeIndex aNewIdx(pAnch->GetContentAnchor()->nNode); + SwNodeIndex aOldIdx(pContent->IsTextFrame() + // sw_redlinehide: can pick any node here, the compare with + // FrameContainsNode should catch it + ? *static_cast<SwTextFrame *>(pContent)->GetTextNodeFirst() + : *static_cast<SwNoTextFrame *>(pContent)->GetNode()); + + //fix: depending on which index was smaller, searching in the do-while + //loop previously was done forward or backwards respectively. This however + //could lead to an infinite loop. To at least avoid the loop, searching + //is now done in only one direction. Getting hold of a frame from the node + //is still possible if the new anchor could not be found. Chances are + //good that this will be the correct one. + // consider the case that at found anchor frame candidate already a + // fly frame of the given fly format is registered. + // consider, that <pContent> is the already + // the new anchor frame. + bool bFound(FrameContainsNode(*pContent, aNewIdx.GetIndex())); + const bool bNext = !bFound && aOldIdx < aNewIdx; + while(pContent && !bFound) + { + do + { + if(bNext) + pContent = pContent->GetNextContentFrame(); + else + pContent = pContent->GetPrevContentFrame(); + } while(pContent && + (bBodyFootnote != (pContent->IsInDocBody() || pContent->IsInFootnote()))); + if(pContent) + bFound = FrameContainsNode(*pContent, aNewIdx.GetIndex()); + + // check, if at found anchor frame candidate already a fly frame + // of the given fly frame format is registered. + if(bFound && pContent && pContent->GetDrawObjs()) + { + SwFrameFormat* pMyFlyFrameFormat(&GetFrameFormat()); + SwSortedObjs &rObjs = *pContent->GetDrawObjs(); + for(SwAnchoredObject* rObj : rObjs) + { + SwFlyFrame* pFlyFrame = rObj->DynCastFlyFrame(); + if (pFlyFrame && + &(pFlyFrame->GetFrameFormat()) == pMyFlyFrameFormat) + { + bFound = false; + break; + } + } + } + } + if(!pContent) + { + SwContentNode *pNode = aNewIdx.GetNode().GetContentNode(); + std::pair<Point, bool> const tmp(pOldAnchor->getFrameArea().Pos(), false); + pContent = pNode->getLayoutFrame(getRootFrame(), nullptr, &tmp); + OSL_ENSURE(pContent, "New anchor not found"); + } + //Flys are never attached to a follow, but always on the master which + //we are going to search now. + SwContentFrame* pFlow = pContent; + while(pFlow->IsFollow()) + pFlow = pFlow->FindMaster(); + pContent = pFlow; + + //and *puff* it's attached... + pContent->AppendFly( this ); + if(pOldPage && pOldPage != FindPageFrame()) + NotifyBackground(pOldPage, aOld, PrepareHint::FlyFrameLeave); + + //Fix(3495) + InvalidatePos_(); + InvalidatePage(); + SetNotifyBack(); + // #i28701# - reset member <maLastCharRect> and + // <mnLastTopOfLine> for to-character anchored objects. + ClearCharRectAndTopOfLine(); +} + +//We need some helper classes to monitor the oscillation and a few functions +//to not get lost. + +namespace { + +// #i3317# - re-factoring of the position stack +class SwOszControl +{ + static const SwFlyFrame* s_pStack1; + static const SwFlyFrame* s_pStack2; + static const SwFlyFrame* s_pStack3; + static const SwFlyFrame* s_pStack4; + static const SwFlyFrame* s_pStack5; + + const SwFlyFrame* m_pFly; + std::vector<Point> maObjPositions; + +public: + explicit SwOszControl( const SwFlyFrame *pFrame ); + ~SwOszControl(); + bool ChkOsz(); + static bool IsInProgress( const SwFlyFrame *pFly ); +}; + +} + +const SwFlyFrame* SwOszControl::s_pStack1 = nullptr; +const SwFlyFrame* SwOszControl::s_pStack2 = nullptr; +const SwFlyFrame* SwOszControl::s_pStack3 = nullptr; +const SwFlyFrame* SwOszControl::s_pStack4 = nullptr; +const SwFlyFrame* SwOszControl::s_pStack5 = nullptr; + +SwOszControl::SwOszControl(const SwFlyFrame* pFrame) + : m_pFly(pFrame) +{ + if (!SwOszControl::s_pStack1) + SwOszControl::s_pStack1 = m_pFly; + else if (!SwOszControl::s_pStack2) + SwOszControl::s_pStack2 = m_pFly; + else if (!SwOszControl::s_pStack3) + SwOszControl::s_pStack3 = m_pFly; + else if (!SwOszControl::s_pStack4) + SwOszControl::s_pStack4 = m_pFly; + else if (!SwOszControl::s_pStack5) + SwOszControl::s_pStack5 = m_pFly; +} + +SwOszControl::~SwOszControl() +{ + if (SwOszControl::s_pStack1 == m_pFly) + SwOszControl::s_pStack1 = nullptr; + else if (SwOszControl::s_pStack2 == m_pFly) + SwOszControl::s_pStack2 = nullptr; + else if (SwOszControl::s_pStack3 == m_pFly) + SwOszControl::s_pStack3 = nullptr; + else if (SwOszControl::s_pStack4 == m_pFly) + SwOszControl::s_pStack4 = nullptr; + else if (SwOszControl::s_pStack5 == m_pFly) + SwOszControl::s_pStack5 = nullptr; + // #i3317# + maObjPositions.clear(); +} + +bool SwOszControl::IsInProgress( const SwFlyFrame *pFly ) +{ + if (SwOszControl::s_pStack1 && !pFly->IsLowerOf(SwOszControl::s_pStack1)) + return true; + if (SwOszControl::s_pStack2 && !pFly->IsLowerOf(SwOszControl::s_pStack2)) + return true; + if (SwOszControl::s_pStack3 && !pFly->IsLowerOf(SwOszControl::s_pStack3)) + return true; + if (SwOszControl::s_pStack4 && !pFly->IsLowerOf(SwOszControl::s_pStack4)) + return true; + if (SwOszControl::s_pStack5 && !pFly->IsLowerOf(SwOszControl::s_pStack5)) + return true; + return false; +} + +bool SwOszControl::ChkOsz() +{ + bool bOscillationDetected = false; + + if ( maObjPositions.size() == 20 ) + { + // #i3317# position stack is full -> oscillation + bOscillationDetected = true; + } + else + { + Point aNewObjPos = m_pFly->GetObjRect().Pos(); + for ( auto const & pt : maObjPositions ) + { + if ( aNewObjPos == pt ) + { + // position already occurred -> oscillation + bOscillationDetected = true; + break; + } + } + if ( !bOscillationDetected ) + { + maObjPositions.push_back( aNewObjPos ); + } + } + + return bOscillationDetected; +} + +/** +|* With a paragraph-anchored fly it's absolutely possible that +|* the anchor reacts to changes of the fly. To this reaction the fly must +|* certainly react too. Sadly this can lead to oscillations; for example the +|* fly wants to go down therefore the content can go up - this leads to a +|* smaller TextFrame thus the fly needs to go up again whereby the text will +|* get pushed down... +|* To avoid such oscillations, a small position stack is built. If the fly +|* reaches a position which it already had once, the action is stopped. +|* To not run into problems, the stack is designed to hold five positions. +|* If the stack flows over, the action is stopped too. +|* Cancellation leads to the situation that the fly has a bad position in +|* the end. In case of cancellation, the frame is set to automatic top +|* alignment to not trigger a 'big oscillation' when calling from outside +|* again. +|*/ +void SwFlyAtContentFrame::MakeAll(vcl::RenderContext* pRenderContext) +{ + if ( !GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( GetVirtDrawObj()->GetLayer() ) ) + { + return; + } + + if ( SwOszControl::IsInProgress( this ) || IsLocked() || IsColLocked() ) + return; + + // #i28701# - use new method <GetPageFrame()> + if( !GetPageFrame() && GetAnchorFrame() && GetAnchorFrame()->IsInFly() ) + { + SwFlyFrame* pFly = AnchorFrame()->FindFlyFrame(); + SwPageFrame *pTmpPage = pFly ? pFly->FindPageFrame() : nullptr; + if( pTmpPage ) + pTmpPage->AppendFlyToPage( this ); + } + // #i28701# - use new method <GetPageFrame()> + if( !GetPageFrame() ) + return; + + bSetCompletePaintOnInvalidate = true; + { + SwFlyFrameFormat *pFormat = GetFormat(); + const SwFormatFrameSize &rFrameSz = GetFormat()->GetFrameSize(); + if( rFrameSz.GetHeightPercent() != SwFormatFrameSize::SYNCED && + rFrameSz.GetHeightPercent() >= 100 ) + { + pFormat->LockModify(); + SwFormatSurround aMain( pFormat->GetSurround() ); + if ( aMain.GetSurround() == css::text::WrapTextMode_NONE ) + { + aMain.SetSurround( css::text::WrapTextMode_THROUGH ); + pFormat->SetFormatAttr( aMain ); + } + pFormat->UnlockModify(); + } + } + + SwOszControl aOszCntrl( this ); + + // #i43255# + // #i50356# - format the anchor frame, which + // contains the anchor position. E.g., for at-character anchored + // object this can be the follow frame of the anchor frame. + const bool bFormatAnchor = + !static_cast<const SwTextFrame*>( GetAnchorFrameContainingAnchPos() )->IsAnyJoinLocked() && + !ConsiderObjWrapInfluenceOnObjPos() && + !ConsiderObjWrapInfluenceOfOtherObjs(); + + const SwFrame* pFooter = GetAnchorFrame()->FindFooterOrHeader(); + if( pFooter && !pFooter->IsFooterFrame() ) + pFooter = nullptr; + bool bOsz = false; + bool bExtra = Lower() && Lower()->IsColumnFrame(); + // #i3317# - boolean, to apply temporarily the + // 'straightforward positioning process' for the frame due to its + // overlapping with a previous column. + bool bConsiderWrapInfluenceDueToOverlapPrevCol( false ); + // #i35911# - boolean, to apply temporarily the + // 'straightforward positioning process' for the frame due to fact + // that it causes the complete content of its layout environment + // to move forward. + // #i40444# - extend usage of this boolean: + // apply temporarily the 'straightforward positioning process' for + // the frame due to the fact that the frame clears the area for + // the anchor frame, thus it has to move forward. + bool bConsiderWrapInfluenceDueToMovedFwdAnchor( false ); + do { + SwRectFnSet aRectFnSet(this); + Point aOldPos( aRectFnSet.GetPos(getFrameArea()) ); + SwFlyFreeFrame::MakeAll(pRenderContext); + + const bool bPosChgDueToOwnFormat = + aOldPos != aRectFnSet.GetPos(getFrameArea()); + // #i3317# + if ( !ConsiderObjWrapInfluenceOnObjPos() && + OverlapsPrevColumn() ) + { + bConsiderWrapInfluenceDueToOverlapPrevCol = true; + } + // #i28701# - no format of anchor frame, if + // wrapping style influence is considered on object positioning + if ( bFormatAnchor ) + { + SwTextFrame& rAnchPosAnchorFrame = + *GetAnchorFrameContainingAnchPos()->DynCastTextFrame(); + // #i58182# - For the usage of new method + // <SwObjectFormatterTextFrame::CheckMovedFwdCondition(..)> + // to check move forward of anchor frame due to the object + // positioning it's needed to know, if the object is anchored + // at the master frame before the anchor frame is formatted. + const bool bAnchoredAtMaster(!rAnchPosAnchorFrame.IsFollow()); + + // #i56300# + // perform complete format of anchor text frame and its + // previous frames, which have become invalid due to the + // fly frame format. + SwObjectFormatterTextFrame::FormatAnchorFrameAndItsPrevs( rAnchPosAnchorFrame ); + // #i35911# + // #i40444# + // #i58182# - usage of new method + // <SwObjectFormatterTextFrame::CheckMovedFwdCondition(..)> + sal_uInt32 nToPageNum( 0 ); + bool bDummy( false ); + bool bPageHasFlysAnchoredBelowThis(false); + if ( SwObjectFormatterTextFrame::CheckMovedFwdCondition( +// TODO: what if this fly moved bc it's in table? does sth prevent that? + *this, *GetPageFrame(), + bAnchoredAtMaster, nToPageNum, bDummy, + bPageHasFlysAnchoredBelowThis) ) + { + if (!bPageHasFlysAnchoredBelowThis) + { + bConsiderWrapInfluenceDueToMovedFwdAnchor = true; + } + // mark anchor text frame + // directly, that it is moved forward by object positioning. + SwTextFrame* pAnchorTextFrame( static_cast<SwTextFrame*>(AnchorFrame()) ); + bool bInsert( true ); + sal_uInt32 nAnchorFrameToPageNum( 0 ); + const SwDoc& rDoc = *(GetFrameFormat().GetDoc()); + if ( SwLayouter::FrameMovedFwdByObjPos( + rDoc, *pAnchorTextFrame, nAnchorFrameToPageNum ) ) + { + if ( nAnchorFrameToPageNum < nToPageNum ) + { + if (!bPageHasFlysAnchoredBelowThis) + { + SwLayouter::RemoveMovedFwdFrame(rDoc, *pAnchorTextFrame); + } + } + else + bInsert = false; + } + if ( bInsert ) + { + if (!bPageHasFlysAnchoredBelowThis) + { + SwLayouter::InsertMovedFwdFrame(rDoc, *pAnchorTextFrame, + nToPageNum); + } + } + } + } + + if ( aOldPos != aRectFnSet.GetPos(getFrameArea()) || + ( !isFrameAreaPositionValid() && + ( pFooter || bPosChgDueToOwnFormat ) ) ) + { + bOsz = aOszCntrl.ChkOsz(); + + // special loop prevention for dedicated document: + if ( bOsz && + HasFixSize() && IsClipped() && + GetAnchorFrame()->GetUpper()->IsCellFrame() ) + { + SwFrameFormat* pFormat = GetFormat(); + const SwFormatFrameSize& rFrameSz = pFormat->GetFrameSize(); + if ( rFrameSz.GetWidthPercent() && + rFrameSz.GetHeightPercent() == SwFormatFrameSize::SYNCED ) + { + SwFormatSurround aSurround( pFormat->GetSurround() ); + if ( aSurround.GetSurround() == css::text::WrapTextMode_NONE ) + { + pFormat->LockModify(); + aSurround.SetSurround( css::text::WrapTextMode_THROUGH ); + pFormat->SetFormatAttr( aSurround ); + pFormat->UnlockModify(); + bOsz = false; + OSL_FAIL( "<SwFlyAtContentFrame::MakeAll()> - special loop prevention for dedicated document of b6403541 applied" ); + } + } + } + } + + if ( bExtra && Lower() && !Lower()->isFrameAreaPositionValid() ) + { + // If a multi column frame leaves invalid columns because of + // a position change, we loop once more and format + // our content using FormatWidthCols again. + InvalidateSize_(); + bExtra = false; // Ensure only one additional loop run + } + } while ( !isFrameAreaDefinitionValid() && !bOsz && + // #i3317# + !bConsiderWrapInfluenceDueToOverlapPrevCol && + // #i40444# + !bConsiderWrapInfluenceDueToMovedFwdAnchor && + GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( GetVirtDrawObj()->GetLayer() ) ); + + // #i3317# - instead of attribute change apply + // temporarily the 'straightforward positioning process'. + // #i80924# + // handle special case during splitting of table rows + if ( bConsiderWrapInfluenceDueToMovedFwdAnchor && + GetAnchorFrame()->IsInTab() && + GetAnchorFrame()->IsInFollowFlowRow() ) + { + const SwFrame* pCellFrame = GetAnchorFrame(); + while ( pCellFrame && !pCellFrame->IsCellFrame() ) + { + pCellFrame = pCellFrame->GetUpper(); + } + if ( pCellFrame ) + { + SwRectFnSet aRectFnSet(pCellFrame); + if ( aRectFnSet.GetTop(pCellFrame->getFrameArea()) == 0 && + aRectFnSet.GetHeight(pCellFrame->getFrameArea()) == 0 ) + { + bConsiderWrapInfluenceDueToMovedFwdAnchor = false; + } + } + } + // tdf#137803: Fix the position of the shape during autoSize + SwFrameFormat* pShapeFormat + = SwTextBoxHelper::getOtherTextBoxFormat(GetFormat(), RES_FLYFRMFMT); + // FIXME: According to tdf37153, ignore FollowTextFlow objs, because + // wrong position will applied in that case. FollowTextFlow needs fix. + if (pShapeFormat && !pShapeFormat->GetFollowTextFlow().GetValue() && + SwTextBoxHelper::getProperty(pShapeFormat, + UNO_NAME_FRAME_ISAUTOMATIC_HEIGHT).hasValue() && + SwTextBoxHelper::getProperty(pShapeFormat, + UNO_NAME_FRAME_ISAUTOMATIC_HEIGHT).get<bool>() ) + { + // get the text area of the shape + const tools::Rectangle aTextRectangle + = SwTextBoxHelper::getRelativeTextRectangle(pShapeFormat->FindRealSdrObject()); + // get the original textframe position + SwFormatHoriOrient aHOri = pShapeFormat->GetHoriOrient(); + SwFormatVertOrient aVOri = pShapeFormat->GetVertOrient(); + // calc the right position of the shape depending on text area + aHOri.SetPos(aHOri.GetPos() + aTextRectangle.Left()); + aVOri.SetPos(aVOri.GetPos() + aTextRectangle.Top()); + // save the new position for the shape + auto pFormat = GetFormat(); + const bool bLocked = pFormat->IsModifyLocked(); + if (!bLocked) + pFormat->LockModify(); + pFormat->SetFormatAttr(aHOri); + pFormat->SetFormatAttr(aVOri); + if (!bLocked) + pFormat->UnlockModify(); + } + if ( bOsz || bConsiderWrapInfluenceDueToOverlapPrevCol || + // #i40444# + bConsiderWrapInfluenceDueToMovedFwdAnchor ) + { + SetTmpConsiderWrapInfluence( true ); + SetRestartLayoutProcess( true ); + SetTmpConsiderWrapInfluenceOfOtherObjs(); + } + bSetCompletePaintOnInvalidate = false; +} + +/** method to determine, if a <MakeAll()> on the Writer fly frame is possible + + #i28701# +*/ +bool SwFlyAtContentFrame::IsFormatPossible() const +{ + return SwFlyFreeFrame::IsFormatPossible() && + !SwOszControl::IsInProgress( this ); +} + +namespace { + +class SwDistance +{ +public: + SwTwips m_nMain, m_nSub; + SwDistance() + : m_nMain(0) + , m_nSub(0) + { + } + bool operator<( const SwDistance& rTwo ) const + { + return m_nMain < rTwo.m_nMain + || (m_nMain == rTwo.m_nMain && m_nSub && rTwo.m_nSub && m_nSub < rTwo.m_nSub); + } + bool operator<=( const SwDistance& rTwo ) const + { + return m_nMain < rTwo.m_nMain + || (m_nMain == rTwo.m_nMain + && (!m_nSub || !rTwo.m_nSub || m_nSub <= rTwo.m_nSub)); + } +}; + +} + +static const SwFrame * lcl_CalcDownDist( SwDistance &rRet, + const Point &rPt, + const SwContentFrame *pCnt ) +{ + rRet.m_nSub = 0; + //If the point stays inside the Cnt everything is clear already; the Content + //automatically has a distance of 0. + if ( pCnt->getFrameArea().Contains( rPt ) ) + { + rRet.m_nMain = 0; + return pCnt; + } + else + { + const SwLayoutFrame *pUp = pCnt->IsInTab() ? pCnt->FindTabFrame()->GetUpper() : pCnt->GetUpper(); + // single column sections need to interconnect to their upper + while( pUp->IsSctFrame() ) + pUp = pUp->GetUpper(); + const bool bVert = pUp->IsVertical(); + + const bool bVertL2R = pUp->IsVertLR(); + + //Follow the text flow. + // #i70582# + // --> OD 2009-03-05 - adopted for Support for Classical Mongolian Script + const SwTwips nTopForObjPos = lcl_GetTopForObjPos(pCnt, bVert, bVertL2R); + if ( pUp->getFrameArea().Contains( rPt ) ) + { + // <rPt> point is inside environment of given content frame + // #i70582# + if( bVert ) + { + if ( bVertL2R ) + rRet.m_nMain = rPt.X() - nTopForObjPos; + else + rRet.m_nMain = nTopForObjPos - rPt.X(); + } + else + rRet.m_nMain = rPt.Y() - nTopForObjPos; + return pCnt; + } + else if ( rPt.Y() <= pUp->getFrameArea().Top() ) + { + // <rPt> point is above environment of given content frame + // correct for vertical layout? + rRet.m_nMain = LONG_MAX; + } + else if( rPt.X() < pUp->getFrameArea().Left() && + rPt.Y() <= ( bVert ? pUp->getFrameArea().Top() : pUp->getFrameArea().Bottom() ) ) + { + // <rPt> point is left of environment of given content frame + // seems not to be correct for vertical layout!? + const SwFrame *pLay = pUp->GetLeaf( MAKEPAGE_NONE, false, pCnt ); + if( !pLay || + (bVert && (pLay->getFrameArea().Top() + pLay->getFramePrintArea().Bottom()) <rPt.Y())|| + (!bVert && (pLay->getFrameArea().Left() + pLay->getFramePrintArea().Right())<rPt.X()) ) + { + // <rPt> point is in left border of environment + // #i70582# + if( bVert ) + { + if ( bVertL2R ) + rRet.m_nMain = rPt.X() - nTopForObjPos; + else + rRet.m_nMain = nTopForObjPos - rPt.X(); + } + else + rRet.m_nMain = rPt.Y() - nTopForObjPos; + return pCnt; + } + else + rRet.m_nMain = LONG_MAX; + } + else + { + rRet.m_nMain + = bVert ? (bVertL2R + ? ((pUp->getFrameArea().Left() + pUp->getFramePrintArea().Right()) + - nTopForObjPos) + : (nTopForObjPos + - (pUp->getFrameArea().Left() + pUp->getFramePrintArea().Left()))) + : ((pUp->getFrameArea().Top() + pUp->getFramePrintArea().Bottom()) + - nTopForObjPos); + + const SwFrame *pPre = pCnt; + const SwFrame *pLay = pUp->GetLeaf( MAKEPAGE_NONE, true, pCnt ); + SwTwips nFrameTop = 0; + SwTwips nPrtHeight = 0; + bool bSct = false; + const SwSectionFrame *pSect = pUp->FindSctFrame(); + if( pSect ) + { + rRet.m_nSub = rRet.m_nMain; + rRet.m_nMain = 0; + } + if( pSect && !pSect->IsAnLower( pLay ) ) + { + bSct = false; + const SwSectionFrame* pNxtSect = pLay ? pLay->FindSctFrame() : nullptr; + if (pSect->IsAnFollow(pNxtSect) && pLay) + { + if( pLay->IsVertical() ) + { + if ( pLay->IsVertLR() ) + nFrameTop = pLay->getFrameArea().Left(); + else + nFrameTop = pLay->getFrameArea().Left() + pLay->getFrameArea().Width(); + nPrtHeight = pLay->getFramePrintArea().Width(); + } + else + { + nFrameTop = pLay->getFrameArea().Top(); + nPrtHeight = pLay->getFramePrintArea().Height(); + } + pSect = pNxtSect; + } + else + { + pLay = pSect->GetUpper(); + if( pLay->IsVertical() ) + { + if ( pLay->IsVertLR() ) + { + nFrameTop = pSect->getFrameArea().Right(); + nPrtHeight = pLay->getFrameArea().Left() + pLay->getFramePrintArea().Left() + + pLay->getFramePrintArea().Width() - pSect->getFrameArea().Left() + - pSect->getFrameArea().Width(); + } + else + { + nFrameTop = pSect->getFrameArea().Left(); + nPrtHeight = pSect->getFrameArea().Left() - pLay->getFrameArea().Left() + - pLay->getFramePrintArea().Left(); + } + } + else + { + nFrameTop = pSect->getFrameArea().Bottom(); + nPrtHeight = pLay->getFrameArea().Top() + pLay->getFramePrintArea().Top() + + pLay->getFramePrintArea().Height() - pSect->getFrameArea().Top() + - pSect->getFrameArea().Height(); + } + pSect = nullptr; + } + } + else if( pLay ) + { + if( pLay->IsVertical() ) + { + if ( pLay->IsVertLR() ) + { + nFrameTop = pLay->getFrameArea().Left(); + nPrtHeight = pLay->getFramePrintArea().Width(); + } + else + { + nFrameTop = pLay->getFrameArea().Left() + pLay->getFrameArea().Width(); + nPrtHeight = pLay->getFramePrintArea().Width(); + } + } + else + { + nFrameTop = pLay->getFrameArea().Top(); + nPrtHeight = pLay->getFramePrintArea().Height(); + } + bSct = nullptr != pSect; + } + while ( pLay && !pLay->getFrameArea().Contains( rPt ) && + ( pLay->getFrameArea().Top() <= rPt.Y() || pLay->IsInFly() || + ( pLay->IsInSct() && + pLay->FindSctFrame()->GetUpper()->getFrameArea().Top() <= rPt.Y())) ) + { + if ( pLay->IsFootnoteContFrame() ) + { + if ( !static_cast<const SwLayoutFrame*>(pLay)->Lower() ) + { + SwFrame *pDel = const_cast<SwFrame*>(pLay); + pDel->Cut(); + SwFrame::DestroyFrame(pDel); + return pPre; + } + return nullptr; + } + else + { + if( bSct || pSect ) + rRet.m_nSub += nPrtHeight; + else + rRet.m_nMain += nPrtHeight; + pPre = pLay; + pLay = pLay->GetLeaf( MAKEPAGE_NONE, true, pCnt ); + if( pSect && !pSect->IsAnLower( pLay ) ) + { // If we're leaving a SwSectionFrame, the next Leaf-Frame + // is the part of the upper below the SectionFrame. + const SwSectionFrame* pNxtSect = pLay ? + pLay->FindSctFrame() : nullptr; + bSct = false; + if (pLay && pSect->IsAnFollow(pNxtSect)) + { + pSect = pNxtSect; + if( pLay->IsVertical() ) + { + if ( pLay->IsVertLR() ) + { + nFrameTop = pLay->getFrameArea().Left(); + nPrtHeight = pLay->getFramePrintArea().Width(); + } + else + { + nFrameTop = pLay->getFrameArea().Left() + pLay->getFrameArea().Width(); + nPrtHeight = pLay->getFramePrintArea().Width(); + } + } + else + { + nFrameTop = pLay->getFrameArea().Top(); + nPrtHeight = pLay->getFramePrintArea().Height(); + } + } + else + { + pLay = pSect->GetUpper(); + if( pLay->IsVertical() ) + { + if ( pLay->IsVertLR() ) + { + nFrameTop = pSect->getFrameArea().Right(); + nPrtHeight = pLay->getFrameArea().Left()+pLay->getFramePrintArea().Left() + + pLay->getFramePrintArea().Width() - pSect->getFrameArea().Left() + - pSect->getFrameArea().Width(); + } + else + { + nFrameTop = pSect->getFrameArea().Left(); + nPrtHeight = pSect->getFrameArea().Left() - + pLay->getFrameArea().Left() - pLay->getFramePrintArea().Left(); + } + } + else + { + nFrameTop = pSect->getFrameArea().Bottom(); + nPrtHeight = pLay->getFrameArea().Top()+pLay->getFramePrintArea().Top() + + pLay->getFramePrintArea().Height() - pSect->getFrameArea().Top() + - pSect->getFrameArea().Height(); + } + pSect = nullptr; + } + } + else if( pLay ) + { + if( pLay->IsVertical() ) + { + if ( pLay->IsVertLR() ) + { + nFrameTop = pLay->getFrameArea().Left(); + nPrtHeight = pLay->getFramePrintArea().Width(); + } + else + { + nFrameTop = pLay->getFrameArea().Left() + pLay->getFrameArea().Width(); + nPrtHeight = pLay->getFramePrintArea().Width(); + } + } + else + { + nFrameTop = pLay->getFrameArea().Top(); + nPrtHeight = pLay->getFramePrintArea().Height(); + } + bSct = nullptr != pSect; + } + } + } + if ( pLay ) + { + if ( pLay->getFrameArea().Contains( rPt ) ) + { + SwTwips nDiff = pLay->IsVertical() ? ( pLay->IsVertLR() ? ( rPt.X() - nFrameTop ) : ( nFrameTop - rPt.X() ) ) + : ( rPt.Y() - nFrameTop ); + if( bSct || pSect ) + rRet.m_nSub += nDiff; + else + rRet.m_nMain += nDiff; + } + if ( pLay->IsFootnoteContFrame() && !static_cast<const SwLayoutFrame*>(pLay)->Lower() ) + { + SwFrame *pDel = const_cast<SwFrame*>(pLay); + pDel->Cut(); + SwFrame::DestroyFrame(pDel); + return nullptr; + } + return pLay; + } + else + rRet.m_nMain = LONG_MAX; + } + } + return nullptr; +} + +static sal_uInt64 lcl_FindCntDiff( const Point &rPt, const SwLayoutFrame *pLay, + const SwContentFrame *& rpCnt, + const bool bBody, const bool bFootnote ) +{ + // Searches below pLay the nearest Cnt to the point. The reference point of + //the Contents is always the left upper corner. + //The Cnt should preferably be above the point. + + rpCnt = nullptr; + sal_uInt64 nDistance = SAL_MAX_UINT64; + sal_uInt64 nNearest = SAL_MAX_UINT64; + const SwContentFrame *pCnt = pLay ? pLay->ContainsContent() : nullptr; + + while ( pCnt && (bBody != pCnt->IsInDocBody() || bFootnote != pCnt->IsInFootnote())) + { + pCnt = pCnt->GetNextContentFrame(); + if ( !pLay->IsAnLower( pCnt ) ) + pCnt = nullptr; + } + const SwContentFrame *pNearest = pCnt; + if ( pCnt ) + { + do + { + //Calculate the distance between those two points. + //'delta' X^2 + 'delta' Y^2 = 'distance'^2 + sal_uInt64 dX = std::max( pCnt->getFrameArea().Left(), rPt.X() ) - + std::min( pCnt->getFrameArea().Left(), rPt.X() ), + dY = std::max( pCnt->getFrameArea().Top(), rPt.Y() ) - + std::min( pCnt->getFrameArea().Top(), rPt.Y() ); + // square of the difference will do fine here + const sal_uInt64 nDiff = (dX * dX) + (dY * dY); + if ( pCnt->getFrameArea().Top() <= rPt.Y() ) + { + if ( nDiff < nDistance ) + { + //This one is the nearer one + nDistance = nNearest = nDiff; + rpCnt = pNearest = pCnt; + } + } + else if ( nDiff < nNearest ) + { + nNearest = nDiff; + pNearest = pCnt; + } + pCnt = pCnt->GetNextContentFrame(); + while ( pCnt && + (bBody != pCnt->IsInDocBody() || bFootnote != pCnt->IsInFootnote())) + pCnt = pCnt->GetNextContentFrame(); + + } while ( pCnt && pLay->IsAnLower( pCnt ) ); + } + if (nDistance == SAL_MAX_UINT64) + { rpCnt = pNearest; + return nNearest; + } + return nDistance; +} + +static const SwContentFrame * lcl_FindCnt( const Point &rPt, const SwContentFrame *pCnt, + const bool bBody, const bool bFootnote ) +{ + //Starting from pCnt searches the ContentFrame whose left upper corner is the + //nearest to the point. + //Always returns a ContentFrame. + + //First the nearest Content inside the page which contains the Content is + //searched. Starting from this page the pages in both directions need to + //be considered. If possible a Content is returned whose Y-position is + //above the point. + const SwContentFrame *pRet, *pNew; + const SwLayoutFrame *pLay = pCnt->FindPageFrame(); + sal_uInt64 nDist; // not sure if a sal_Int32 would be enough? + + nDist = ::lcl_FindCntDiff( rPt, pLay, pNew, bBody, bFootnote ); + if ( pNew ) + pRet = pNew; + else + { pRet = pCnt; + nDist = SAL_MAX_UINT64; + } + const SwContentFrame *pNearest = pRet; + sal_uInt64 nNearest = nDist; + + if ( pLay ) + { + const SwLayoutFrame *pPge = pLay; + sal_uInt64 nOldNew = SAL_MAX_UINT64; + for ( int i = 0; pPge->GetPrev() && (i < 3); ++i ) + { + pPge = static_cast<const SwLayoutFrame*>(pPge->GetPrev()); + const sal_uInt64 nNew = ::lcl_FindCntDiff( rPt, pPge, pNew, bBody, bFootnote ); + if ( nNew < nDist ) + { + if ( pNew->getFrameArea().Top() <= rPt.Y() ) + { + pRet = pNearest = pNew; + nDist = nNearest = nNew; + } + else if ( nNew < nNearest ) + { + pNearest = pNew; + nNearest = nNew; + } + } + else if (nOldNew != SAL_MAX_UINT64 && nNew > nOldNew) + break; + else + nOldNew = nNew; + + } + pPge = pLay; + nOldNew = SAL_MAX_UINT64; + for ( int j = 0; pPge->GetNext() && (j < 3); ++j ) + { + pPge = static_cast<const SwLayoutFrame*>(pPge->GetNext()); + const sal_uInt64 nNew = ::lcl_FindCntDiff( rPt, pPge, pNew, bBody, bFootnote ); + if ( nNew < nDist ) + { + if ( pNew->getFrameArea().Top() <= rPt.Y() ) + { + pRet = pNearest = pNew; + nDist = nNearest = nNew; + } + else if ( nNew < nNearest ) + { + pNearest = pNew; + nNearest = nNew; + } + } + else if (nOldNew != SAL_MAX_UINT64 && nNew > nOldNew) + break; + else + nOldNew = nNew; + } + } + if ( pRet->getFrameArea().Top() > rPt.Y() ) + return pNearest; + else + return pRet; +} + +static void lcl_PointToPrt( Point &rPoint, const SwFrame *pFrame ) +{ + SwRect aTmp( pFrame->getFramePrintArea() ); + aTmp += pFrame->getFrameArea().Pos(); + if ( rPoint.getX() < aTmp.Left() ) + rPoint.setX(aTmp.Left()); + else if ( rPoint.getX() > aTmp.Right() ) + rPoint.setX(aTmp.Right()); + if ( rPoint.getY() < aTmp.Top() ) + rPoint.setY(aTmp.Top()); + else if ( rPoint.getY() > aTmp.Bottom() ) + rPoint.setY(aTmp.Bottom()); + +} + +/** Searches an anchor for paragraph bound objects starting from pOldAnch. + * + * This is used to show anchors as well as changing anchors + * when dragging paragraph bound objects. + */ +const SwContentFrame *FindAnchor( const SwFrame *pOldAnch, const Point &rNew, + const bool bBodyOnly ) +{ + //Search the nearest Cnt around the given document position in the text + //flow. The given anchor is the starting Frame. + const SwContentFrame* pCnt; + if ( pOldAnch->IsContentFrame() ) + { + pCnt = static_cast<const SwContentFrame*>(pOldAnch); + } + else + { + Point aTmp( rNew ); + const SwLayoutFrame *pTmpLay = static_cast<const SwLayoutFrame*>(pOldAnch); + if( pTmpLay->IsRootFrame() ) + { + SwRect aTmpRect( aTmp, Size(0,0) ); + pTmpLay = static_cast<const SwLayoutFrame*>(::FindPage( aTmpRect, pTmpLay->Lower() )); + } + pCnt = pTmpLay->GetContentPos( aTmp, false, bBodyOnly ); + } + + //Take care to use meaningful ranges during search. This means to not enter + //or leave header/footer in this case. + const bool bBody = pCnt->IsInDocBody() || bBodyOnly; + const bool bFootnote = !bBodyOnly && pCnt->IsInFootnote(); + + Point aNew( rNew ); + if ( bBody ) + { + //#38848 drag from page margin into the body. + const SwFrame *pPage = pCnt->FindPageFrame(); + ::lcl_PointToPrt( aNew, pPage->GetUpper() ); + SwRect aTmp( aNew, Size( 0, 0 ) ); + pPage = ::FindPage( aTmp, pPage ); + ::lcl_PointToPrt( aNew, pPage ); + } + + if ( pCnt->IsInDocBody() == bBody && pCnt->getFrameArea().Contains( aNew ) ) + return pCnt; + else if ( pOldAnch->IsInDocBody() || pOldAnch->IsPageFrame() ) + { + // Maybe the selected anchor is on the same page as the current anchor. + // With this we won't run into problems with the columns. + Point aTmp( aNew ); + const SwContentFrame *pTmp = pCnt->FindPageFrame()-> + GetContentPos( aTmp, false, true ); + if ( pTmp && pTmp->getFrameArea().Contains( aNew ) ) + return pTmp; + } + + //Starting from the anchor we now search in both directions until we found + //the nearest one respectively. + //Not the direct distance is relevant but the distance which needs to be + //traveled through the text flow. + const SwContentFrame *pUpLst; + const SwContentFrame *pUpFrame = pCnt; + SwDistance nUp, nUpLst; + ::lcl_CalcDownDist( nUp, aNew, pUpFrame ); + SwDistance nDown = nUp; + bool bNegAllowed = true;// Make it possible to leave the negative section once. + do + { + pUpLst = pUpFrame; nUpLst = nUp; + pUpFrame = pUpLst->GetPrevContentFrame(); + while ( pUpFrame && + (bBody != pUpFrame->IsInDocBody() || bFootnote != pUpFrame->IsInFootnote())) + pUpFrame = pUpFrame->GetPrevContentFrame(); + if ( pUpFrame ) + { + ::lcl_CalcDownDist( nUp, aNew, pUpFrame ); + //It makes sense to search further, if the distance grows inside + //a table. + if ( pUpLst->IsInTab() && pUpFrame->IsInTab() ) + { + while ( pUpFrame && ((nUpLst < nUp && pUpFrame->IsInTab()) || + bBody != pUpFrame->IsInDocBody()) ) + { + pUpFrame = pUpFrame->GetPrevContentFrame(); + if ( pUpFrame ) + ::lcl_CalcDownDist( nUp, aNew, pUpFrame ); + } + } + } + if ( !pUpFrame ) + nUp.m_nMain = LONG_MAX; + if (nUp.m_nMain >= 0 && LONG_MAX != nUp.m_nMain) + { + bNegAllowed = false; + if (nUpLst.m_nMain < 0) //don't take the wrong one, if the value + //just changed from negative to positive. + { pUpLst = pUpFrame; + nUpLst = nUp; + } + } + } while (pUpFrame && ((bNegAllowed && nUp.m_nMain < 0) || (nUp <= nUpLst))); + + const SwContentFrame *pDownLst; + const SwContentFrame *pDownFrame = pCnt; + SwDistance nDownLst; + if (nDown.m_nMain < 0) + nDown.m_nMain = LONG_MAX; + do + { + pDownLst = pDownFrame; nDownLst = nDown; + pDownFrame = pDownLst->GetNextContentFrame(); + while ( pDownFrame && + (bBody != pDownFrame->IsInDocBody() || bFootnote != pDownFrame->IsInFootnote())) + pDownFrame = pDownFrame->GetNextContentFrame(); + if ( pDownFrame ) + { + ::lcl_CalcDownDist( nDown, aNew, pDownFrame ); + if (nDown.m_nMain < 0) + nDown.m_nMain = LONG_MAX; + //It makes sense to search further, if the distance grows inside + //a table. + if ( pDownLst->IsInTab() && pDownFrame->IsInTab() ) + { + while (pDownFrame + && ((nDown.m_nMain != LONG_MAX && pDownFrame->IsInTab()) + || bBody != pDownFrame->IsInDocBody())) + { + pDownFrame = pDownFrame->GetNextContentFrame(); + if ( pDownFrame ) + ::lcl_CalcDownDist( nDown, aNew, pDownFrame ); + if (nDown.m_nMain < 0) + nDown.m_nMain = LONG_MAX; + } + } + } + if ( !pDownFrame ) + nDown.m_nMain = LONG_MAX; + + } while (pDownFrame && nDown <= nDownLst && nDown.m_nMain != LONG_MAX + && nDownLst.m_nMain != LONG_MAX); + + //If we couldn't find one in both directions, we'll search the Content whose + //left upper corner is the nearest to the point. Such a situation may + //happen, if the point doesn't lay in the text flow but in any margin. + if (nDownLst.m_nMain == LONG_MAX && nUpLst.m_nMain == LONG_MAX) + { + // If an OLE objects, which is contained in a fly frame + // is resized in inplace mode and the new Position is outside the + // fly frame, we do not want to leave our fly frame. + if ( pCnt->IsInFly() ) + return pCnt; + + return ::lcl_FindCnt( aNew, pCnt, bBody, bFootnote ); + } + else + return nDownLst < nUpLst ? pDownLst : pUpLst; +} + +void SwFlyAtContentFrame::SetAbsPos( const Point &rNew ) +{ + SwPageFrame *pOldPage = FindPageFrame(); + const SwRect aOld( GetObjRectWithSpaces() ); + Point aNew( rNew ); + + if( ( GetAnchorFrame()->IsVertical() && !GetAnchorFrame()->IsVertLR() ) || GetAnchorFrame()->IsRightToLeft() ) + aNew.setX(aNew.getX() + getFrameArea().Width()); + SwContentFrame *pCnt = const_cast<SwContentFrame*>(::FindAnchor( GetAnchorFrame(), aNew )); + if( pCnt->IsProtected() ) + pCnt = const_cast<SwContentFrame*>(static_cast<const SwContentFrame*>(GetAnchorFrame())); + + SwPageFrame *pTmpPage = nullptr; + const bool bVert = pCnt->IsVertical(); + + const bool bVertL2R = pCnt->IsVertLR(); + const bool bRTL = pCnt->IsRightToLeft(); + + if( ( !bVert != !GetAnchorFrame()->IsVertical() ) || + ( !bRTL != !GetAnchorFrame()->IsRightToLeft() ) ) + { + if( bVert || bRTL ) + aNew.setX(aNew.getX() + getFrameArea().Width()); + else + aNew.setX(aNew.getX() - getFrameArea().Width()); + } + + if ( pCnt->IsInDocBody() ) + { + //#38848 drag from page margin into the body. + pTmpPage = pCnt->FindPageFrame(); + ::lcl_PointToPrt( aNew, pTmpPage->GetUpper() ); + SwRect aTmp( aNew, Size( 0, 0 ) ); + pTmpPage = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(::FindPage( aTmp, pTmpPage ))); + ::lcl_PointToPrt( aNew, pTmpPage ); + } + + //Setup RelPos, only invalidate if requested. + //rNew is an absolute position. We need to calculate the distance from rNew + //to the anchor inside the text flow to correctly set RelPos. +//!!!!!We can optimize here: FindAnchor could also return RelPos! + const SwFrame *pFrame = nullptr; + SwTwips nY; + if ( pCnt->getFrameArea().Contains( aNew ) ) + { + // #i70582# + if ( bVert ) + { + nY = pCnt->getFrameArea().Left() - rNew.X(); + if ( bVertL2R ) + nY = -nY; + else + nY += pCnt->getFrameArea().Width() - getFrameArea().Width(); + nY -= pCnt->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); + } + else + nY = rNew.Y() - pCnt->getFrameArea().Top() - pCnt->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); + } + else + { + SwDistance aDist; + pFrame = ::lcl_CalcDownDist( aDist, aNew, pCnt ); + nY = aDist.m_nMain + aDist.m_nSub; + } + + SwTwips nX = 0; + + if ( pCnt->IsFollow() ) + { + // Flys are never attached to the follow but always to the master, + // which we're going to search now. + const SwContentFrame *pOriginal = pCnt; + const SwContentFrame *pFollow = pCnt; + while ( pCnt->IsFollow() ) + { + do + { + SwContentFrame* pPrev = pCnt->GetPrevContentFrame(); + if (!pPrev) + { + SAL_WARN("sw.core", "very unexpected missing PrevContentFrame"); + break; + } + pCnt = pPrev; + } + while ( pCnt->GetFollow() != pFollow ); + pFollow = pCnt; + } + SwTwips nDiff = 0; + do + { const SwFrame *pUp = pFollow->GetUpper(); + if( pUp->IsVertical() ) + { + if ( pUp->IsVertLR() ) + nDiff += pUp->getFramePrintArea().Width() - pFollow->GetRelPos().getX(); + else + nDiff += pFollow->getFrameArea().Left() + pFollow->getFrameArea().Width() + - pUp->getFrameArea().Left() - pUp->getFramePrintArea().Left(); + } + else + nDiff += pUp->getFramePrintArea().Height() - pFollow->GetRelPos().Y(); + pFollow = pFollow->GetFollow(); + } while ( pFollow != pOriginal ); + nY += nDiff; + if( bVert ) + nX = pCnt->getFrameArea().Top() - pOriginal->getFrameArea().Top(); + else + nX = pCnt->getFrameArea().Left() - pOriginal->getFrameArea().Left(); + } + + if ( nY == LONG_MAX ) + { + // #i70582# + const SwTwips nTopForObjPos = lcl_GetTopForObjPos(pCnt, bVert, bVertL2R); + if( bVert ) + { + if ( bVertL2R ) + nY = rNew.X() - nTopForObjPos; + else + nY = nTopForObjPos - rNew.X(); + } + else + { + nY = rNew.Y() - nTopForObjPos; + } + } + + SwFlyFrameFormat *pFormat = GetFormat(); + + if( bVert ) + { + if( !pFrame ) + nX += rNew.Y() - pCnt->getFrameArea().Top(); + else + nX = rNew.Y() - pFrame->getFrameArea().Top(); + } + else + { + if( !pFrame ) + { + if ( pCnt->IsRightToLeft() ) + nX += pCnt->getFrameArea().Right() - rNew.X() - getFrameArea().Width(); + else + nX += rNew.X() - pCnt->getFrameArea().Left(); + } + else + { + if ( pFrame->IsRightToLeft() ) + nX += pFrame->getFrameArea().Right() - rNew.X() - getFrameArea().Width(); + else + nX = rNew.X() - pFrame->getFrameArea().Left(); + } + } + GetFormat()->GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + + if( pCnt != GetAnchorFrame() || ( IsAutoPos() && pCnt->IsTextFrame() && + GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE)) ) + { + //Set the anchor attribute according to the new Cnt. + SwFormatAnchor aAnch( pFormat->GetAnchor() ); + SwPosition pos = *aAnch.GetContentAnchor(); + if( IsAutoPos() && pCnt->IsTextFrame() ) + { + SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(pCnt)); + SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText ); + Point aPt( rNew ); + if( pCnt->GetModelPositionForViewPoint( &pos, aPt, &eTmpState ) + && FrameContainsNode(*pTextFrame, pos.nNode.GetIndex())) + { + const SwTextAttr *const pTextInputField = + pos.nNode.GetNode().GetTextNode()->GetTextAttrAt( + pos.nContent.GetIndex(), RES_TXTATR_INPUTFIELD, SwTextNode::PARENT ); + if (pTextInputField != nullptr) + { + pos.nContent = pTextInputField->GetStart(); + } + ResetLastCharRectHeight(); + if( text::RelOrientation::CHAR == pFormat->GetVertOrient().GetRelationOrient() ) + nY = LONG_MAX; + if( text::RelOrientation::CHAR == pFormat->GetHoriOrient().GetRelationOrient() ) + nX = LONG_MAX; + } + else + { + pos = pTextFrame->MapViewToModelPos(TextFrameIndex(0)); + } + } + else if (pCnt->IsTextFrame()) + { + pos = static_cast<SwTextFrame const*>(pCnt)->MapViewToModelPos(TextFrameIndex(0)); + } + else // is that even possible? maybe if there was a change of anchor type from AT_FLY or something? + { + assert(pCnt->IsNoTextFrame()); + pos.nNode = *static_cast<SwNoTextFrame*>(pCnt)->GetNode(); + pos.nContent.Assign(static_cast<SwNoTextFrame*>(pCnt)->GetNode(), 0); + } + aAnch.SetAnchor( &pos ); + + // handle change of anchor node: + // if count of the anchor frame also change, the fly frames have to be + // re-created. Thus, delete all fly frames except the <this> before the + // anchor attribute is change and re-create them afterwards. + { + SwHandleAnchorNodeChg aHandleAnchorNodeChg( *pFormat, aAnch, this ); + pFormat->GetDoc()->SetAttr( aAnch, *pFormat ); + } + } + else if ( pTmpPage && pTmpPage != GetPageFrame() ) + GetPageFrame()->MoveFly( this, pTmpPage ); + + const Point aRelPos = bVert ? Point( -nY, nX ) : Point( nX, nY ); + ChgRelPos( aRelPos ); + GetFormat()->GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + + if ( pOldPage != FindPageFrame() ) + ::Notify_Background( GetVirtDrawObj(), pOldPage, aOld, PrepareHint::FlyFrameLeave, false ); +} + +/** method to assure that anchored object is registered at the correct + page frame + + #i28701# + takes over functionality of deleted method <SwFlyAtContentFrame::AssertPage()> +*/ +void SwFlyAtContentFrame::RegisterAtCorrectPage() +{ + SwPageFrame* pPageFrame( nullptr ); + if ( GetVertPosOrientFrame() ) + { + pPageFrame = const_cast<SwPageFrame*>(GetVertPosOrientFrame()->FindPageFrame()); + } + if ( pPageFrame && GetPageFrame() != pPageFrame ) + { + RegisterAtPage(*pPageFrame); + } +} + +void SwFlyAtContentFrame::RegisterAtPage(SwPageFrame & rPageFrame) +{ + assert(GetPageFrame() != &rPageFrame); + if (GetPageFrame()) + { + GetPageFrame()->MoveFly( this, &rPageFrame ); + } + else + { + rPageFrame.AppendFlyToPage( this ); + } +} + +// #i26791# +void SwFlyAtContentFrame::MakeObjPos() +{ + // if fly frame position is valid, nothing is to do. Thus, return + if ( isFrameAreaPositionValid() ) + { + return; + } + + // #i26791# - validate position flag here. + setFrameAreaPositionValid(true); + + // #i35911# - no calculation of new position, if + // anchored object is marked that it clears its environment and its + // environment is already cleared. + // before checking for cleared environment + // check, if member <mpVertPosOrientFrame> is set. + if ( GetVertPosOrientFrame() && + ClearedEnvironment() && HasClearedEnvironment() ) + { + return; + } + + // use new class to position object + objectpositioning::SwToContentAnchoredObjectPosition + aObjPositioning( *GetVirtDrawObj() ); + aObjPositioning.CalcPosition(); + + SetVertPosOrientFrame ( aObjPositioning.GetVertPosOrientFrame() ); +} + +// #i28701# +bool SwFlyAtContentFrame::InvalidationAllowed( const InvalidationType _nInvalid ) const +{ + bool bAllowed( SwFlyFreeFrame::InvalidationAllowed( _nInvalid ) ); + + // forbiddance of base instance can't be over ruled. + if ( bAllowed ) + { + if ( _nInvalid == INVALID_POS || + _nInvalid == INVALID_ALL ) + { + bAllowed = InvalidationOfPosAllowed(); + } + } + + return bAllowed; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/flyincnt.cxx b/sw/source/core/layout/flyincnt.cxx new file mode 100644 index 000000000..2455f7a6d --- /dev/null +++ b/sw/source/core/layout/flyincnt.cxx @@ -0,0 +1,285 @@ +/* -*- 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 <doc.hxx> +#include <frmtool.hxx> +#include <hints.hxx> +#include <fmtornt.hxx> +#include <rootfrm.hxx> +#include <flyfrms.hxx> +#include <dflyobj.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <osl/diagnose.h> +#include <o3tl/deleter.hxx> + +SwFlyInContentFrame::SwFlyInContentFrame( SwFlyFrameFormat *pFormat, SwFrame* pSib, SwFrame *pAnch ) : + SwFlyFrame( pFormat, pSib, pAnch ) +{ + m_bInCnt = true; + SwTwips nRel = pFormat->GetVertOrient().GetPos(); + // OD 2004-05-27 #i26791# - member <aRelPos> moved to <SwAnchoredObject> + Point aRelPos; + if( pAnch && pAnch->IsVertical() ) + aRelPos.setX(-nRel); + else + aRelPos.setY(nRel); + SetCurrRelPos( aRelPos ); +} + +void SwFlyInContentFrame::DestroyImpl() +{ + if ( !GetFormat()->GetDoc()->IsInDtor() && GetAnchorFrame() ) + { + SwRect aTmp( GetObjRectWithSpaces() ); + SwFlyInContentFrame::NotifyBackground( FindPageFrame(), aTmp, PrepareHint::FlyFrameLeave ); + } + + SwFlyFrame::DestroyImpl(); +} + +SwFlyInContentFrame::~SwFlyInContentFrame() +{ +} + +// #i28701# + +void SwFlyInContentFrame::SetRefPoint( const Point& rPoint, + const Point& rRelAttr, + const Point& rRelPos ) +{ + // OD 2004-05-27 #i26791# - member <aRelPos> moved to <SwAnchoredObject> + OSL_ENSURE( rPoint != m_aRef || rRelAttr != GetCurrRelPos(), "SetRefPoint: no change" ); + std::unique_ptr<SwFlyNotify, o3tl::default_delete<SwFlyNotify>> xNotify; + // No notify at a locked fly frame, if a fly frame is locked, there's + // already a SwFlyNotify object on the stack (MakeAll). + if( !IsLocked() ) + xNotify.reset(new SwFlyNotify( this )); + m_aRef = rPoint; + SetCurrRelPos( rRelAttr ); + SwRectFnSet aRectFnSet(GetAnchorFrame()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetPos( aFrm, rPoint + rRelPos ); + } + + // #i68520# + InvalidateObjRectWithSpaces(); + if (xNotify) + { + InvalidatePage(); + setFrameAreaPositionValid(false); + m_bInvalid = true; + Calc(getRootFrame()->GetCurrShell()->GetOut()); + xNotify.reset(); + } +} + +void SwFlyInContentFrame::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + std::pair<std::unique_ptr<SwAttrSetChg>, std::unique_ptr<SwAttrSetChg>> aTweakedChgs; + std::pair<const SfxPoolItem*, const SfxPoolItem*> aSuperArgs(nullptr, nullptr); + switch(pLegacy->GetWhich()) + { + case RES_ATTRSET_CHG: + { + auto pOldAttrSetChg = static_cast<const SwAttrSetChg*>(pLegacy->m_pOld); + auto pNewAttrSetChg = static_cast<const SwAttrSetChg*>(pLegacy->m_pNew); + if(pOldAttrSetChg + && pNewAttrSetChg + && ((SfxItemState::SET == pNewAttrSetChg->GetChgSet()->GetItemState(RES_SURROUND, false)) + || (SfxItemState::SET == pNewAttrSetChg->GetChgSet()->GetItemState(RES_FRMMACRO, false)))) + { + aTweakedChgs.second = std::make_unique<SwAttrSetChg>(*pOldAttrSetChg); + aTweakedChgs.second->ClearItem(RES_SURROUND); + aTweakedChgs.second->ClearItem(RES_FRMMACRO); + if(aTweakedChgs.second->Count()) + { + aTweakedChgs.first = std::make_unique<SwAttrSetChg>(*pOldAttrSetChg); + aTweakedChgs.first->ClearItem(RES_SURROUND); + aTweakedChgs.first->ClearItem(RES_FRMMACRO); + aSuperArgs = std::pair<const SfxPoolItem*, const SfxPoolItem*>(aTweakedChgs.first.get(), aTweakedChgs.second.get()); + } + } else if (pNewAttrSetChg && pNewAttrSetChg->GetChgSet()->Count()) + aSuperArgs = std::pair<const SfxPoolItem*, const SfxPoolItem*>(pLegacy->m_pOld, pLegacy->m_pNew); + break; + } + case RES_SURROUND: + case RES_FRMMACRO: + break; + default: + aSuperArgs = std::pair<const SfxPoolItem*, const SfxPoolItem*>(pLegacy->m_pOld, pLegacy->m_pNew); + } + if(aSuperArgs.second) + { + SwFlyFrame::SwClientNotify(rMod, sw::LegacyModifyHint(aSuperArgs.first, aSuperArgs.second)); + if(GetAnchorFrame()) + AnchorFrame()->Prepare(PrepareHint::FlyFrameAttributesChanged, GetFormat()); + } +} + +/// Here the content gets formatted initially. +void SwFlyInContentFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttrs ) +{ + if ( !getFrameArea().Height() ) + { + Lock(); //don't format the anchor on the crook. + SwContentFrame *pContent = ContainsContent(); + while ( pContent ) + { pContent->Calc(pRenderContext); + pContent = pContent->GetNextContentFrame(); + } + Unlock(); + } + SwFlyFrame::Format( pRenderContext, pAttrs ); +} + +/** Calculate object position + * + * @note: In contrast to other Frames, we only calculate the relative position + * here. The absolute position is only calculated using SetAbsPos. + **/ +void SwFlyInContentFrame::MakeObjPos() +{ + if ( isFrameAreaPositionValid() ) + return; + + setFrameAreaPositionValid(true); + SwFlyFrameFormat *pFormat = GetFormat(); + const SwFormatVertOrient &rVert = pFormat->GetVertOrient(); + //Update the current values in the format if needed, during this we of + //course must not send any Modify. + const bool bVert = GetAnchorFrame()->IsVertical(); + SwTwips nOld = rVert.GetPos(); + SwTwips nAct = bVert ? -GetCurrRelPos().X() : GetCurrRelPos().Y(); + if( nAct != nOld ) + { + SwFormatVertOrient aVert( rVert ); + aVert.SetPos( nAct ); + pFormat->LockModify(); + pFormat->SetFormatAttr( aVert ); + pFormat->UnlockModify(); + } +} + +void SwFlyInContentFrame::ActionOnInvalidation( const InvalidationType _nInvalid ) +{ + if ( INVALID_POS == _nInvalid || INVALID_ALL == _nInvalid ) + AnchorFrame()->Prepare( PrepareHint::FlyFrameAttributesChanged, &GetFrameFormat() ); +} + +void SwFlyInContentFrame::NotifyBackground( SwPageFrame *, const SwRect& rRect, + PrepareHint eHint) +{ + if ( eHint == PrepareHint::FlyFrameAttributesChanged ) + AnchorFrame()->Prepare( PrepareHint::FlyFrameAttributesChanged ); + else + AnchorFrame()->Prepare( eHint, static_cast<void const *>(&rRect) ); +} + +Point const & SwFlyInContentFrame::GetRelPos() const +{ + Calc(getRootFrame()->GetCurrShell()->GetOut()); + return GetCurrRelPos(); +} + +/// @see SwRowFrame::RegistFlys() +void SwFlyInContentFrame::RegistFlys() +{ + SwPageFrame *pPage = FindPageFrame(); + OSL_ENSURE( pPage, "Register Flys without pages?" ); + ::RegistFlys( pPage, this ); +} + +void SwFlyInContentFrame::MakeAll(vcl::RenderContext* /*pRenderContext*/) +{ + // OD 2004-01-19 #110582# + if ( !GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( GetVirtDrawObj()->GetLayer() ) ) + { + return; + } + + if ( !GetAnchorFrame() || IsLocked() || IsColLocked() || !FindPageFrame() ) + return; + + Lock(); // The curtain falls + + //does the notification in the DTor + const SwFlyNotify aNotify( this ); + SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + if ( IsClipped() ) + { + setFrameAreaSizeValid(false); + m_bHeightClipped = m_bWidthClipped = false; + } + + while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() || !m_bValidContentPos ) + { + //Only stop, if the flag is set!! + if ( !isFrameAreaSizeValid() ) + { + setFramePrintAreaValid(false); + } + + if ( !isFramePrintAreaValid() ) + { + MakePrtArea( rAttrs ); + m_bValidContentPos = false; + } + + if ( !isFrameAreaSizeValid() ) + { + Format( getRootFrame()->GetCurrShell()->GetOut(), &rAttrs ); + } + + if ( !isFrameAreaPositionValid() ) + { + MakeObjPos(); + } + + if ( !m_bValidContentPos ) + MakeContentPos( rAttrs ); + + // re-activate clipping of as-character anchored Writer fly frames + // depending on compatibility option <ClipAsCharacterAnchoredWriterFlyFrames> + if ( isFrameAreaPositionValid() && + isFrameAreaSizeValid() && + GetFormat()->getIDocumentSettingAccess().get( DocumentSettingId::CLIP_AS_CHARACTER_ANCHORED_WRITER_FLY_FRAME ) ) + { + SwFrame* pFrame = AnchorFrame(); + if ( getFrameArea().Left() == (pFrame->getFrameArea().Left()+pFrame->getFramePrintArea().Left()) && + getFrameArea().Width() > pFrame->getFramePrintArea().Width() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( pFrame->getFramePrintArea().Width() ); + setFramePrintAreaValid(false); + m_bWidthClipped = true; + } + } + } + Unlock(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/flylay.cxx b/sw/source/core/layout/flylay.cxx new file mode 100644 index 000000000..209ca1fcf --- /dev/null +++ b/sw/source/core/layout/flylay.cxx @@ -0,0 +1,1505 @@ +/* -*- 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 <config_wasm_strip.h> + +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <cntfrm.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <ftnfrm.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <sectfrm.hxx> +#include <notxtfrm.hxx> +#include <txtfly.hxx> + +#include <svx/svdpage.hxx> +#include <editeng/ulspitem.hxx> +#include <fmtornt.hxx> +#include <fmtfsize.hxx> +#include <ndole.hxx> +#include <tabfrm.hxx> +#include <flyfrms.hxx> +#include <fmtfollowtextflow.hxx> +#include <environmentofanchoredobject.hxx> +#include <sortedobjs.hxx> +#include <viewimp.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <pam.hxx> +#include <ndindex.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <svx/sdr/attribute/sdrallfillattributeshelper.hxx> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; + +SwFlyFreeFrame::SwFlyFreeFrame( SwFlyFrameFormat *pFormat, SwFrame* pSib, SwFrame *pAnch ) +: SwFlyFrame( pFormat, pSib, pAnch ), + // #i34753# + mbNoMakePos( false ), + // #i37068# + mbNoMoveOnCheckClip( false ) +{ +} + +void SwFlyFreeFrame::DestroyImpl() +{ + // #i28701# - use new method <GetPageFrame()> + if( GetPageFrame() ) + { + if( GetFormat()->GetDoc()->IsInDtor() ) + { + // #i29879# - remove also to-frame anchored Writer + // fly frame from page. + const bool bRemoveFromPage = + GetPageFrame()->GetSortedObjs() && + ( IsFlyAtContentFrame() || + ( GetAnchorFrame() && GetAnchorFrame()->IsFlyFrame() ) ); + if ( bRemoveFromPage ) + { + GetPageFrame()->GetSortedObjs()->Remove( *this ); + } + } + else + { + SwRect aTmp( GetObjRectWithSpaces() ); + SwFlyFreeFrame::NotifyBackground( GetPageFrame(), aTmp, PrepareHint::FlyFrameLeave ); + } + } + + SwFlyFrame::DestroyImpl(); +} + +SwFlyFreeFrame::~SwFlyFreeFrame() +{ +#if 0 + // we are possibly in ContourCache, make sure we vanish + ::ClrContourCache(GetVirtDrawObj()); +#endif +} + +// #i28701# +/** Notifies the background (all ContentFrames that currently are overlapping). + * + * Additionally, the window is also directly invalidated (especially where + * there are no overlapping ContentFrames). + * This also takes ContentFrames within other Flys into account. + */ +void SwFlyFreeFrame::NotifyBackground( SwPageFrame *pPageFrame, + const SwRect& rRect, PrepareHint eHint ) +{ + ::Notify_Background( GetVirtDrawObj(), pPageFrame, rRect, eHint, true ); +} + +void SwFlyFreeFrame::MakeAll(vcl::RenderContext* /*pRenderContext*/) +{ + if ( !GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( GetVirtDrawObj()->GetLayer() ) ) + { + return; + } + + if ( !GetAnchorFrame() || IsLocked() || IsColLocked() ) + { + return; + } + + // #i28701# - use new method <GetPageFrame()> + if( !GetPageFrame() && GetAnchorFrame()->IsInFly() ) + { + SwFlyFrame* pFly = AnchorFrame()->FindFlyFrame(); + SwPageFrame *pPageFrame = pFly ? pFly->FindPageFrame() : nullptr; + if( pPageFrame ) + pPageFrame->AppendFlyToPage( this ); + } + + if( !GetPageFrame() ) + { + return; + } + + Lock(); // The curtain drops + + // takes care of the notification in the dtor + const SwFlyNotify aNotify( this ); + + if ( IsClipped() ) + { + setFrameAreaSizeValid(false); + m_bHeightClipped = m_bWidthClipped = false; + // no invalidation of position, + // if anchored object is anchored inside a Writer fly frame, + // its position is already locked, and it follows the text flow. + // #i34753# - add condition: + // no invalidation of position, if no direct move is requested in <CheckClip(..)> + if ( !IsNoMoveOnCheckClip() && + !( PositionLocked() && + GetAnchorFrame()->IsInFly() && + GetFrameFormat().GetFollowTextFlow().GetValue() ) ) + { + setFrameAreaPositionValid(false); + } + } + + // #i81146# new loop control + int nLoopControlRuns = 0; + const int nLoopControlMax = 10; + + // RotateFlyFrame3 - outer frame + const double fRotation(getLocalFrameRotation()); + const bool bRotated(!basegfx::fTools::equalZero(fRotation)); + + if(bRotated) + { + // Re-layout may be partially (see all isFrameAreaDefinitionValid() flags), + // so resetting the local SwFrame(s) in the local SwFrameAreaDefinition is + // needed. Reset to BoundAreas will be done below automatically + if(isTransformableSwFrame()) + { + getTransformableSwFrame()->restoreFrameAreas(); + } + } + + while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() || m_bFormatHeightOnly || !m_bValidContentPos ) + { + SwRectFnSet aRectFnSet(this); + const SwFormatFrameSize *pSz; + { // Additional scope, so aAccess will be destroyed before the check! + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + pSz = &rAttrs.GetAttrSet().GetFrameSize(); + + // Only set when the flag is set! + if ( !isFrameAreaSizeValid() ) + { + setFramePrintAreaValid(false); + } + + if ( !isFramePrintAreaValid() ) + { + MakePrtArea( rAttrs ); + m_bValidContentPos = false; + } + + if ( !isFrameAreaSizeValid() || m_bFormatHeightOnly ) + { + setFrameAreaSizeValid(false); + Format( getRootFrame()->GetCurrShell()->GetOut(), &rAttrs ); + m_bFormatHeightOnly = false; + } + } + + if ( !isFrameAreaPositionValid() ) + { + const Point aOldPos( aRectFnSet.GetPos(getFrameArea()) ); + // #i26791# - use new method <MakeObjPos()> + // #i34753# - no positioning, if requested. + if ( IsNoMakePos() ) + { + setFrameAreaPositionValid(true); + } + else + // #i26791# - use new method <MakeObjPos()> + MakeObjPos(); + if( aOldPos == aRectFnSet.GetPos(getFrameArea()) ) + { + if( !isFrameAreaPositionValid() && GetAnchorFrame()->IsInSct() && + !GetAnchorFrame()->FindSctFrame()->isFrameAreaDefinitionValid() ) + { + setFrameAreaPositionValid(true); + } + } + else + { + setFrameAreaSizeValid(false); + } + } + + if ( !m_bValidContentPos ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + MakeContentPos( rAttrs ); + } + + if ( isFrameAreaPositionValid() && isFrameAreaSizeValid() ) + { + ++nLoopControlRuns; + + OSL_ENSURE( nLoopControlRuns < nLoopControlMax, "LoopControl in SwFlyFreeFrame::MakeAll" ); + + if ( nLoopControlRuns < nLoopControlMax ) + CheckClip( *pSz ); + } + else + nLoopControlRuns = 0; + } + + // RotateFlyFrame3 - outer frame + // Do not refresh transforms/Areas self here, this will be done + // when inner and outer frame are layouted, in SwNoTextFrame::MakeAll + if(bRotated) + { + // RotateFlyFrame3: Safe changes locally + // get center from outer frame (layout frame) to be on the safe side + const Point aCenter(getFrameArea().Center()); + const basegfx::B2DPoint aB2DCenter(aCenter.X(), aCenter.Y()); + + if(!mpTransformableSwFrame) + { + mpTransformableSwFrame.reset(new TransformableSwFrame(*this)); + } + + getTransformableSwFrame()->createFrameAreaTransformations( + fRotation, + aB2DCenter); + getTransformableSwFrame()->adaptFrameAreasToTransformations(); + } + else + { + // RotateFlyFrame3: Also need to clear ContourCache (if used), + // usually done in SwFlyFrame::NotifyDrawObj, but there relies on + // being in transform mode which is already reset then + if(isTransformableSwFrame()) + { + ::ClrContourCache(GetVirtDrawObj()); + } + + // reset transformations to show that they are not used + mpTransformableSwFrame.reset(); + } + + Unlock(); + +#if OSL_DEBUG_LEVEL > 0 + SwRectFnSet aRectFnSet(this); + OSL_ENSURE( m_bHeightClipped || ( aRectFnSet.GetHeight(getFrameArea()) > 0 && + aRectFnSet.GetHeight(getFramePrintArea()) > 0), + "SwFlyFreeFrame::Format(), flipping Fly." ); + +#endif +} + +bool SwFlyFreeFrame::supportsAutoContour() const +{ + static bool bOverrideHandleContourToAlwaysOff(true); // loplugin:constvars:ignore + + // RotateFlyFrameFix: For LO6.0 we need to deactivate the AutoContour feature again, it is simply + // not clear how/if to use and save/load it in ODF. This has to be discussed. + // The reason not to remove is that this may be used as-is now, using a new switch. + // Even when not, the detection if it is possible will be needed in any case later. + if(bOverrideHandleContourToAlwaysOff) + { + return false; + } + + if(!isTransformableSwFrame()) + { + // support only when transformed, else there is no free space + return false; + } + + // Check for Borders. If we have Borders, do (currently) not support, + // since borders do not transform with the object. + // (Will need to be enhanced to take into account if we have Borders and if these + // transform with the object) + SwBorderAttrAccess aAccess(SwFrame::GetCache(), this); + const SwBorderAttrs &rAttrs(*aAccess.Get()); + + if(rAttrs.IsLine()) + { + return false; + } + + // Check for Padding. Do not support when padding is used, this will + // produce a covered space around the object (filled with fill defines) + const SvxBoxItem* pBoxItem(nullptr); + + if(GetFormat() && (pBoxItem = GetFormat()->GetItemIfSet(RES_BOX, false))) + { + if(pBoxItem->HasBorder(/*bTreatPaddingAsBorder*/true)) + { + return false; + } + } + + // check for Fill - if we have fill, it will fill the gaps and we will not + // support AutoContour + if(GetFormat() && GetFormat()->supportsFullDrawingLayerFillAttributeSet()) + { + const drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes(GetFormat()->getSdrAllFillAttributesHelper()); + + if(aFillAttributes && aFillAttributes->isUsed()) + { + return false; + } + } + else + { + const std::unique_ptr<SvxBrushItem> aBack(GetFormat()->makeBackgroundBrushItem()); + + if(aBack->isUsed()) + { + return false; + } + } + + // else, support + return true; +} + +// RotateFlyFrame3 - Support for Transformations - outer frame +basegfx::B2DHomMatrix SwFlyFreeFrame::getFrameAreaTransformation() const +{ + if(isTransformableSwFrame()) + { + // use pre-created transformation + return getTransformableSwFrame()->getLocalFrameAreaTransformation(); + } + + // call parent + return SwFlyFrame::getFrameAreaTransformation(); +} + +basegfx::B2DHomMatrix SwFlyFreeFrame::getFramePrintAreaTransformation() const +{ + if(isTransformableSwFrame()) + { + // use pre-created transformation + return getTransformableSwFrame()->getLocalFramePrintAreaTransformation(); + } + + // call parent + return SwFlyFrame::getFramePrintAreaTransformation(); +} + +// RotateFlyFrame3 - Support for Transformations +void SwFlyFreeFrame::transform_translate(const Point& rOffset) +{ + // call parent - this will do the basic transform for SwRect(s) + // in the SwFrameAreaDefinition + SwFlyFrame::transform_translate(rOffset); + + // check if the Transformations need to be adapted + if(isTransformableSwFrame()) + { + const basegfx::B2DHomMatrix aTransform( + basegfx::utils::createTranslateB2DHomMatrix( + rOffset.X(), rOffset.Y())); + + // transform using TransformableSwFrame + getTransformableSwFrame()->transform(aTransform); + } +} + +// RotateFlyFrame3 - outer frame +double getLocalFrameRotation_from_SwNoTextFrame(const SwNoTextFrame& rNoTextFrame) +{ + return rNoTextFrame.getLocalFrameRotation(); +} + +double SwFlyFreeFrame::getLocalFrameRotation() const +{ + // SwLayoutFrame::Lower() != SwFrame::GetLower(), but SwFrame::GetLower() + // calls SwLayoutFrame::Lower() when it's a SwLayoutFrame - so use GetLower() + const SwNoTextFrame* pSwNoTextFrame(dynamic_cast< const SwNoTextFrame* >(GetLower())); + + if(nullptr != pSwNoTextFrame) + { + return getLocalFrameRotation_from_SwNoTextFrame(*pSwNoTextFrame); + } + + // no rotation + return 0.0; +} + +/** determines, if direct environment of fly frame has 'auto' size + + #i17297# + start with anchor frame and search via <GetUpper()> for a header, footer, + row or fly frame stopping at page frame. + return <true>, if such a frame is found and it has 'auto' size. + otherwise <false> is returned. + + @return boolean indicating, that direct environment has 'auto' size +*/ +bool SwFlyFreeFrame::HasEnvironmentAutoSize() const +{ + bool bRetVal = false; + + const SwFrame* pToBeCheckedFrame = GetAnchorFrame(); + while ( pToBeCheckedFrame && + !pToBeCheckedFrame->IsPageFrame() ) + { + if ( pToBeCheckedFrame->IsHeaderFrame() || + pToBeCheckedFrame->IsFooterFrame() || + pToBeCheckedFrame->IsRowFrame() || + pToBeCheckedFrame->IsFlyFrame() ) + { + bRetVal = SwFrameSize::Fixed != + pToBeCheckedFrame->GetAttrSet()->GetFrameSize().GetHeightSizeType(); + break; + } + else + { + pToBeCheckedFrame = pToBeCheckedFrame->GetUpper(); + } + } + + return bRetVal; +} + +void SwFlyFreeFrame::CheckClip( const SwFormatFrameSize &rSz ) +{ + // It's probably time now to take appropriate measures, if the Fly + // doesn't fit into its surrounding. + // First, the Fly gives up its position, then it's formatted. + // Only if it still doesn't fit after giving up its position, the + // width or height are given up as well. The frame will be squeezed + // as much as needed. + + const SwVirtFlyDrawObj *pObj = GetVirtDrawObj(); + SwRect aClip, aTmpStretch; + ::CalcClipRect( pObj, aClip ); + ::CalcClipRect( pObj, aTmpStretch, false ); + aClip.Intersection_( aTmpStretch ); + + const tools::Long nBot = getFrameArea().Top() + getFrameArea().Height(); + const tools::Long nRig = getFrameArea().Left() + getFrameArea().Width(); + const tools::Long nClipBot = aClip.Top() + aClip.Height(); + const tools::Long nClipRig = aClip.Left() + aClip.Width(); + + const bool bBot = nBot > nClipBot; + const bool bRig = nRig > nClipRig; + if (( bBot || bRig ) && !IsDraggingOffPageAllowed(FindFrameFormat(GetDrawObj()))) + { + bool bAgain = false; + // #i37068# - no move, if it's requested + if ( bBot && !IsNoMoveOnCheckClip() && + !GetDrawObjs() && !GetAnchorFrame()->IsInTab() ) + { + SwFrame* pHeader = FindFooterOrHeader(); + // In a header, correction of the position is no good idea. + // If the fly moves, some paragraphs have to be formatted, this + // could cause a change of the height of the headerframe, + // now the flyframe can change its position and so on ... + if ( !pHeader || !pHeader->IsHeaderFrame() ) + { + const tools::Long nOld = getFrameArea().Top(); + + // tdf#112443 disable positioning if content is completely off page + bool bDisableOffPagePositioning = GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::DISABLE_OFF_PAGE_POSITIONING); + if ( !bDisableOffPagePositioning || nOld <= nClipBot) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setY( std::max( aClip.Top(), nClipBot - aFrm.Height() ) ); + } + + if ( getFrameArea().Top() != nOld ) + { + bAgain = true; + } + + m_bHeightClipped = true; + } + } + if ( bRig ) + { + const tools::Long nOld = getFrameArea().Left(); + + // tdf#112443 disable positioning if content is completely off page + bool bDisableOffPagePositioning = GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::DISABLE_OFF_PAGE_POSITIONING); + if ( !bDisableOffPagePositioning || nOld <= nClipRig ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX( std::max( aClip.Left(), nClipRig - aFrm.Width() ) ); + } + + if ( getFrameArea().Left() != nOld ) + { + const SwFormatHoriOrient &rH = GetFormat()->GetHoriOrient(); + // Left-aligned ones may not be moved to the left when they + // are avoiding another one. + if( rH.GetHoriOrient() == text::HoriOrientation::LEFT ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX( nOld ); + } + else + { + bAgain = true; + } + } + m_bWidthClipped = true; + } + if ( bAgain ) + { + setFrameAreaSizeValid(false); + } + else + { + // If we reach this branch, the Frame protrudes into forbidden + // areas, and correcting the position is not allowed or not + // possible or not required. + + // For Flys with OLE objects as lower, we make sure that + // we always resize proportionally + Size aOldSize( getFrameArea().SSize() ); + + // First, setup the FrameRect, then transfer it to the Frame. + SwRect aFrameRect( getFrameArea() ); + + if ( bBot ) + { + tools::Long nDiff = nClipBot; + nDiff -= aFrameRect.Top(); // nDiff represents the available distance + nDiff = aFrameRect.Height() - nDiff; + aFrameRect.Height( aFrameRect.Height() - nDiff ); + m_bHeightClipped = true; + } + if ( bRig ) + { + tools::Long nDiff = nClipRig; + nDiff -= aFrameRect.Left();// nDiff represents the available distance + nDiff = aFrameRect.Width() - nDiff; + aFrameRect.Width( aFrameRect.Width() - nDiff ); + m_bWidthClipped = true; + } + + // #i17297# - no proportional + // scaling of graphics in environments, which determines its size + // by its content ('auto' size). Otherwise layout loops can occur and + // layout sizes of the environment can be incorrect. + // Such environment are: + // (1) header and footer frames with 'auto' size + // (2) table row frames with 'auto' size + // (3) fly frames with 'auto' size + // Note: section frames seems to be not critical - didn't found + // any critical layout situation so far. + if ( Lower() && Lower()->IsNoTextFrame() && + (static_cast<SwNoTextFrame*>(Lower())->GetNode()->GetOLENode() || + !HasEnvironmentAutoSize() ) ) + { + // If width and height got adjusted, then the bigger + // change is relevant. + if ( aFrameRect.Width() != aOldSize.Width() && + aFrameRect.Height()!= aOldSize.Height() ) + { + if ( (aOldSize.Width() - aFrameRect.Width()) > + (aOldSize.Height()- aFrameRect.Height()) ) + aFrameRect.Height( aOldSize.Height() ); + else + aFrameRect.Width( aOldSize.Width() ); + } + + // Adjusted the width? change height proportionally + if( aFrameRect.Width() != aOldSize.Width() ) + { + aFrameRect.Height( aFrameRect.Width() * aOldSize.Height() / + aOldSize.Width() ); + m_bHeightClipped = true; + } + // Adjusted the height? change width proportionally + else if( aFrameRect.Height() != aOldSize.Height() ) + { + aFrameRect.Width( aFrameRect.Height() * aOldSize.Width() / + aOldSize.Height() ); + m_bWidthClipped = true; + } + + // #i17297# - reactivate change + // of size attribute for fly frames containing an ole object. + + // Added the aFrameRect.HasArea() hack, because + // the environment of the ole object does not have to be valid + // at this moment, or even worse, it does not have to have a + // reasonable size. In this case we do not want to change to + // attributes permanently. Maybe one day somebody dares to remove + // this code. + if ( aFrameRect.HasArea() && + static_cast<SwNoTextFrame*>(Lower())->GetNode()->GetOLENode() && + ( m_bWidthClipped || m_bHeightClipped ) ) + { + SwFlyFrameFormat *pFormat = GetFormat(); + pFormat->LockModify(); + SwFormatFrameSize aFrameSize( rSz ); + aFrameSize.SetWidth( aFrameRect.Width() ); + aFrameSize.SetHeight( aFrameRect.Height() ); + pFormat->SetFormatAttr( aFrameSize ); + pFormat->UnlockModify(); + } + } + + // Now change the Frame; for columns, we put the new values into the attributes, + // otherwise we'll end up with unwanted side-effects/oscillations + const tools::Long nPrtHeightDiff = getFrameArea().Height() - getFramePrintArea().Height(); + const tools::Long nPrtWidthDiff = getFrameArea().Width() - getFramePrintArea().Width(); + maUnclippedFrame = getFrameArea(); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( aFrameRect.Height() ); + aFrm.Width ( std::max( tools::Long(MINLAY), aFrameRect.Width() ) ); + } + + if ( Lower() && Lower()->IsColumnFrame() ) + { + ColLock(); //lock grow/shrink + const Size aTmpOldSize( getFramePrintArea().SSize() ); + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( getFrameArea().Height() - nPrtHeightDiff ); + aPrt.Width ( getFrameArea().Width() - nPrtWidthDiff ); + } + + ChgLowersProp( aTmpOldSize ); + SwFrame *pLow = Lower(); + do + { + pLow->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // also calculate the (Column)BodyFrame + static_cast<SwLayoutFrame*>(pLow)->Lower()->Calc(getRootFrame()->GetCurrShell()->GetOut()); + pLow = pLow->GetNext(); + } while ( pLow ); + ::CalcContent( this ); + ColUnlock(); + + if ( !isFrameAreaSizeValid() && !m_bWidthClipped ) + { + setFrameAreaSizeValid(true); + m_bFormatHeightOnly = true; + } + } + else + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( getFrameArea().Height() - nPrtHeightDiff ); + aPrt.Width ( getFrameArea().Width() - nPrtWidthDiff ); + } + } + } + + // #i26945# + OSL_ENSURE( getFrameArea().Height() >= 0, + "<SwFlyFreeFrame::CheckClip(..)> - fly frame has negative height now." ); +} + +/** method to determine, if a <MakeAll()> on the Writer fly frame is possible + #i43771# +*/ +bool SwFlyFreeFrame::IsFormatPossible() const +{ + return SwFlyFrame::IsFormatPossible() && + ( GetPageFrame() || + ( GetAnchorFrame() && GetAnchorFrame()->IsInFly() ) ); +} + +SwFlyLayFrame::SwFlyLayFrame( SwFlyFrameFormat *pFormat, SwFrame* pSib, SwFrame *pAnch ) : + SwFlyFreeFrame( pFormat, pSib, pAnch ) +{ + m_bLayout = true; +} + +void SwFlyLayFrame::RegisterAtPage(SwPageFrame & rPageFrame) +{ + assert(GetPageFrame() != &rPageFrame); + if (GetPageFrame()) + { + GetPageFrame()->MoveFly( this, &rPageFrame ); + } + else + { + rPageFrame.AppendFlyToPage( this ); + } +} + +// #i28701# + +void SwFlyLayFrame::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + if(!pLegacy->m_pNew) + return; + const auto pAnch = GetAnchorFromPoolItem(*pLegacy->m_pNew); + + if(!pAnch) + { + SwFlyFrame::SwClientNotify(rMod, rHint); + return; + } + SAL_WARN_IF(pAnch->GetAnchorId() == GetFormat()->GetAnchor().GetAnchorId(), "sw.core", "Invalid change of anchor type."); + + // Unregister, get hold of the page, attach to the corresponding LayoutFrame. + SwRect aOld(GetObjRectWithSpaces()); + // #i28701# - use new method <GetPageFrame()> + SwPageFrame* pOldPage = GetPageFrame(); + AnchorFrame()->RemoveFly(this); + + if(RndStdIds::FLY_AT_PAGE == pAnch->GetAnchorId()) + { + SwRootFrame* pRoot = getRootFrame(); + SwPageFrame* pTmpPage = static_cast<SwPageFrame*>(pRoot->Lower()); + sal_uInt16 nPagesToFlip = pAnch->GetPageNum()-1; + while(pTmpPage && nPagesToFlip) + { + pTmpPage = static_cast<SwPageFrame*>(pTmpPage->GetNext()); + --nPagesToFlip; + } + if(pTmpPage && !nPagesToFlip) + { + // #i50432# - adjust synopsis of <PlaceFly(..)> + pTmpPage->PlaceFly(this, nullptr); + } + if(!pTmpPage) + { + pRoot->SetAssertFlyPages(); + pRoot->AssertFlyPages(); + } + } + else + { + SwNodeIndex aIdx(pAnch->GetContentAnchor()->nNode); + SwContentFrame* pContent = GetFormat()->GetDoc()->GetNodes().GoNext(&aIdx)-> + GetContentNode()->getLayoutFrame(getRootFrame(), nullptr, nullptr); + if(pContent) + { + SwFlyFrame *pTmp = pContent->FindFlyFrame(); + if(pTmp) + pTmp->AppendFly(this); + } + } + // #i28701# - use new method <GetPageFrame()> + if ( pOldPage && pOldPage != GetPageFrame() ) + NotifyBackground( pOldPage, aOld, PrepareHint::FlyFrameLeave ); + SetCompletePaint(); + InvalidateAll(); + SetNotifyBack(); +} + +void SwPageFrame::AppendFlyToPage( SwFlyFrame *pNew ) +{ + if ( !pNew->GetVirtDrawObj()->IsInserted() ) + getRootFrame()->GetDrawPage()->InsertObject( + static_cast<SdrObject*>(pNew->GetVirtDrawObj()), + pNew->GetVirtDrawObj()->GetReferencedObj().GetOrdNumDirect() ); + + InvalidateSpelling(); + InvalidateSmartTags(); + InvalidateAutoCompleteWords(); + InvalidateWordCount(); + + if ( GetUpper() ) + { + static_cast<SwRootFrame*>(GetUpper())->SetIdleFlags(); + static_cast<SwRootFrame*>(GetUpper())->InvalidateBrowseWidth(); + } + + SdrObject* pObj = pNew->GetVirtDrawObj(); + OSL_ENSURE( pNew->GetAnchorFrame(), "Fly without Anchor" ); + SwFlyFrame* pFly = const_cast<SwFlyFrame*>(pNew->GetAnchorFrame()->FindFlyFrame()); + if ( pFly && pObj->GetOrdNum() < pFly->GetVirtDrawObj()->GetOrdNum() ) + { + //#i119945# set pFly's OrdNum to _rNewObj's. So when pFly is removed by Undo, the original OrdNum will not be changed. + sal_uInt32 nNewNum = pObj->GetOrdNumDirect(); + SdrObject* pDrawObj = nullptr; + if (auto pFormat = pFly->GetFormat()) + if (auto pShapeFormat = SwTextBoxHelper::getOtherTextBoxFormat(pFormat, RES_FLYFRMFMT)) + pDrawObj = pShapeFormat->FindRealSdrObject(); + + if (pDrawObj) + { + if (auto pPage = pDrawObj->getSdrPageFromSdrObject()) + pPage->SetObjectOrdNum(pDrawObj->GetOrdNumDirect(), nNewNum); + else + pDrawObj->SetOrdNum(nNewNum); + } + + if ( pObj->getSdrPageFromSdrObject() ) + pObj->getSdrPageFromSdrObject()->SetObjectOrdNum( pFly->GetVirtDrawObj()->GetOrdNumDirect(), nNewNum + (pDrawObj ? 1 : 0) ); + else + pFly->GetVirtDrawObj()->SetOrdNum( nNewNum + (pDrawObj ? 1 : 0)); + } + + // Don't look further at Flys that sit inside the Content. + if ( pNew->IsFlyInContentFrame() ) + InvalidateFlyInCnt(); + else + { + InvalidateFlyContent(); + + if ( !m_pSortedObjs ) + { + m_pSortedObjs.reset(new SwSortedObjs()); + } + + const bool bSuccessInserted = m_pSortedObjs->Insert( *pNew ); + OSL_ENSURE( bSuccessInserted, "Fly not inserted in Sorted." ); + + // #i87493# + OSL_ENSURE( pNew->GetPageFrame() == nullptr || pNew->GetPageFrame() == this, + "<SwPageFrame::AppendFlyToPage(..)> - anchored fly frame seems to be registered at another page frame. Serious defect." ); + // #i28701# - use new method <SetPageFrame(..)> + pNew->SetPageFrame( this ); + pNew->InvalidatePage( this ); + // #i28701# + pNew->UnlockPosition(); + // needed to reposition at-page anchored flys moved from different page + pNew->InvalidateObjPos(); + + // Notify accessible layout. That's required at this place for + // frames only where the anchor is moved. Creation of new frames + // is additionally handled by the SwFrameNotify class. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if( GetUpper() && + static_cast< SwRootFrame * >( GetUpper() )->IsAnyShellAccessible() && + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell() ) + { + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell()->Imp() + ->AddAccessibleFrame( pNew ); + } +#endif + } + + // #i28701# - correction: consider also drawing objects + if ( !pNew->GetDrawObjs() ) + return; + + SwSortedObjs &rObjs = *pNew->GetDrawObjs(); + for (SwAnchoredObject* pTmpObj : rObjs) + { + if ( auto pTmpFly = pTmpObj->DynCastFlyFrame() ) + { + // #i28701# - use new method <GetPageFrame()> + if ( pTmpFly->IsFlyFreeFrame() && !pTmpFly->GetPageFrame() ) + AppendFlyToPage( pTmpFly ); + } + else if ( dynamic_cast<const SwAnchoredDrawObject*>( pTmpObj) != nullptr ) + { + // #i87493# + if ( pTmpObj->GetPageFrame() != this ) + { + if ( pTmpObj->GetPageFrame() != nullptr ) + { + pTmpObj->GetPageFrame()->RemoveDrawObjFromPage( *pTmpObj ); + } + AppendDrawObjToPage( *pTmpObj ); + } + } + } +} + +void SwPageFrame::RemoveFlyFromPage( SwFlyFrame *pToRemove ) +{ + const sal_uInt32 nOrdNum = pToRemove->GetVirtDrawObj()->GetOrdNum(); + getRootFrame()->GetDrawPage()->RemoveObject( nOrdNum ); + pToRemove->GetVirtDrawObj()->ReferencedObj().SetOrdNum( nOrdNum ); + + if ( GetUpper() ) + { + if ( !pToRemove->IsFlyInContentFrame() ) + static_cast<SwRootFrame*>(GetUpper())->SetSuperfluous(); + static_cast<SwRootFrame*>(GetUpper())->InvalidateBrowseWidth(); + } + + // Don't look further at Flys that sit inside the Content. + if ( pToRemove->IsFlyInContentFrame() ) + return; + + // Don't delete collections just yet. This will happen at the end of the + // action in the RemoveSuperfluous of the page, kicked off by a method of + // the same name in the root. + // The FlyColl might be gone already, because the page's dtor is being + // executed. + // Remove it _before_ disposing accessible frames to avoid accesses to + // the Frame from event handlers. + if (m_pSortedObjs) + { + m_pSortedObjs->Remove(*pToRemove); + if (!m_pSortedObjs->size()) + { + m_pSortedObjs.reset(); + } + } + + // Notify accessible layout. That's required at this place for + // frames only where the anchor is moved. Creation of new frames + // is additionally handled by the SwFrameNotify class. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if( GetUpper() && + static_cast< SwRootFrame * >( GetUpper() )->IsAnyShellAccessible() && + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell() ) + { + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell()->Imp() + ->DisposeAccessibleFrame( pToRemove, true ); + } +#endif + + // #i28701# - use new method <SetPageFrame(..)> + pToRemove->SetPageFrame( nullptr ); +} + +void SwPageFrame::MoveFly( SwFlyFrame *pToMove, SwPageFrame *pDest ) +{ + // Invalidations + if ( GetUpper() ) + { + static_cast<SwRootFrame*>(GetUpper())->SetIdleFlags(); + if ( !pToMove->IsFlyInContentFrame() && pDest->GetPhyPageNum() < GetPhyPageNum() ) + static_cast<SwRootFrame*>(GetUpper())->SetSuperfluous(); + } + + pDest->InvalidateSpelling(); + pDest->InvalidateSmartTags(); + pDest->InvalidateAutoCompleteWords(); + pDest->InvalidateWordCount(); + + if ( pToMove->IsFlyInContentFrame() ) + { + pDest->InvalidateFlyInCnt(); + return; + } + + // Notify accessible layout. That's required at this place for + // frames only where the anchor is moved. Creation of new frames + // is additionally handled by the SwFrameNotify class. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if( GetUpper() && + static_cast< SwRootFrame * >( GetUpper() )->IsAnyShellAccessible() && + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell() ) + { + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell()->Imp() + ->DisposeAccessibleFrame( pToMove, true ); + } +#endif + + // The FlyColl might be gone already, because the page's dtor is being executed. + if ( m_pSortedObjs ) + { + m_pSortedObjs->Remove( *pToMove ); + if ( !m_pSortedObjs->size() ) + { + m_pSortedObjs.reset(); + } + } + + // Register + if ( !pDest->GetSortedObjs() ) + pDest->m_pSortedObjs.reset(new SwSortedObjs()); + + const bool bSuccessInserted = pDest->GetSortedObjs()->Insert( *pToMove ); + OSL_ENSURE( bSuccessInserted, "Fly not inserted in Sorted." ); + + // #i28701# - use new method <SetPageFrame(..)> + pToMove->SetPageFrame( pDest ); + pToMove->InvalidatePage( pDest ); + pToMove->SetNotifyBack(); + pDest->InvalidateFlyContent(); + // #i28701# + pToMove->UnlockPosition(); + + // Notify accessible layout. That's required at this place for + // frames only where the anchor is moved. Creation of new frames + // is additionally handled by the SwFrameNotify class. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if( GetUpper() && + static_cast< SwRootFrame * >( GetUpper() )->IsAnyShellAccessible() && + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell() ) + { + static_cast< SwRootFrame * >( GetUpper() )->GetCurrShell()->Imp() + ->AddAccessibleFrame( pToMove ); + } +#endif + + // #i28701# - correction: move lowers of Writer fly frame + if ( !pToMove->GetDrawObjs() ) + return; + + SwSortedObjs &rObjs = *pToMove->GetDrawObjs(); + for (SwAnchoredObject* pObj : rObjs) + { + if ( auto pFly = pObj->DynCastFlyFrame() ) + { + if ( pFly->IsFlyFreeFrame() ) + { + // #i28701# - use new method <GetPageFrame()> + SwPageFrame* pPageFrame = pFly->GetPageFrame(); + if ( pPageFrame ) + pPageFrame->MoveFly( pFly, pDest ); + else + pDest->AppendFlyToPage( pFly ); + } + } + else if ( dynamic_cast<const SwAnchoredDrawObject*>( pObj) != nullptr ) + { + RemoveDrawObjFromPage( *pObj ); + pDest->AppendDrawObjToPage( *pObj ); + } + } +} + +void SwPageFrame::AppendDrawObjToPage( SwAnchoredObject& _rNewObj ) +{ + if ( dynamic_cast<const SwAnchoredDrawObject*>( &_rNewObj) == nullptr ) + { + OSL_FAIL( "SwPageFrame::AppendDrawObjToPage(..) - anchored object of unexpected type -> object not appended" ); + return; + } + + if ( GetUpper() ) + { + static_cast<SwRootFrame*>(GetUpper())->InvalidateBrowseWidth(); + } + + assert(_rNewObj.GetAnchorFrame()); + SwFlyFrame* pFlyFrame = const_cast<SwFlyFrame*>(_rNewObj.GetAnchorFrame()->FindFlyFrame()); + if ( pFlyFrame && + _rNewObj.GetDrawObj()->GetOrdNum() < pFlyFrame->GetVirtDrawObj()->GetOrdNum() ) + { + //#i119945# set pFly's OrdNum to _rNewObj's. So when pFly is removed by Undo, the original OrdNum will not be changed. + sal_uInt32 nNewNum = _rNewObj.GetDrawObj()->GetOrdNumDirect(); + if ( _rNewObj.GetDrawObj()->getSdrPageFromSdrObject() ) + _rNewObj.DrawObj()->getSdrPageFromSdrObject()->SetObjectOrdNum( pFlyFrame->GetVirtDrawObj()->GetOrdNumDirect(), nNewNum ); + else + pFlyFrame->GetVirtDrawObj()->SetOrdNum( nNewNum ); + } + + if ( RndStdIds::FLY_AS_CHAR == _rNewObj.GetFrameFormat().GetAnchor().GetAnchorId() ) + { + return; + } + + if ( !m_pSortedObjs ) + { + m_pSortedObjs.reset(new SwSortedObjs()); + } + if ( !m_pSortedObjs->Insert( _rNewObj ) ) + { + OSL_ENSURE( m_pSortedObjs->Contains( _rNewObj ), + "Drawing object not appended into list <pSortedObjs>." ); + } + // #i87493# + OSL_ENSURE( _rNewObj.GetPageFrame() == nullptr || _rNewObj.GetPageFrame() == this, + "<SwPageFrame::AppendDrawObjToPage(..)> - anchored draw object seems to be registered at another page frame. Serious defect." ); + _rNewObj.SetPageFrame( this ); + + // invalidate page in order to force a reformat of object layout of the page. + InvalidateFlyLayout(); +} + +void SwPageFrame::RemoveDrawObjFromPage( SwAnchoredObject& _rToRemoveObj ) +{ + if ( dynamic_cast<const SwAnchoredDrawObject*>( &_rToRemoveObj) == nullptr ) + { + OSL_FAIL( "SwPageFrame::RemoveDrawObjFromPage(..) - anchored object of unexpected type -> object not removed" ); + return; + } + + if ( m_pSortedObjs ) + { + m_pSortedObjs->Remove( _rToRemoveObj ); + if ( !m_pSortedObjs->size() ) + { + m_pSortedObjs.reset(); + } + if ( GetUpper() ) + { + if (RndStdIds::FLY_AS_CHAR != + _rToRemoveObj.GetFrameFormat().GetAnchor().GetAnchorId()) + { + static_cast<SwRootFrame*>(GetUpper())->SetSuperfluous(); + InvalidatePage(); + } + static_cast<SwRootFrame*>(GetUpper())->InvalidateBrowseWidth(); + } + } + _rToRemoveObj.SetPageFrame( nullptr ); +} + +// #i50432# - adjust method description and synopsis. +void SwPageFrame::PlaceFly( SwFlyFrame* pFly, SwFlyFrameFormat* pFormat ) +{ + // #i50432# - consider the case that page is an empty page: + // In this case append the fly frame at the next page + OSL_ENSURE( !IsEmptyPage() || GetNext(), + "<SwPageFrame::PlaceFly(..)> - empty page with no next page! -> fly frame appended at empty page" ); + if ( IsEmptyPage() && GetNext() ) + { + static_cast<SwPageFrame*>(GetNext())->PlaceFly( pFly, pFormat ); + } + else + { + // If we received a Fly, we use that one. Otherwise, create a new + // one using the Format. + if ( pFly ) + AppendFly( pFly ); + else + { + OSL_ENSURE( pFormat, ":-( No Format given for Fly." ); + pFly = new SwFlyLayFrame( pFormat, this, this ); + AppendFly( pFly ); + ::RegistFlys( this, pFly ); + } + } +} + +// #i18732# - adjustments for following text flow or not +// AND alignment at 'page areas' for to paragraph/to character anchored objects +// #i22305# - adjustment for following text flow for to frame anchored objects +// #i29778# - Because calculating the floating screen object's position +// (Writer fly frame or drawing object) doesn't perform a calculation on its +// upper frames and its anchor frame, a calculation of the upper frames in this +// method is no longer sensible. +// #i28701# - if document compatibility option 'Consider wrapping style influence +// on object positioning' is ON, the clip area corresponds to the one as the +// object doesn't follow the text flow. +bool CalcClipRect( const SdrObject *pSdrObj, SwRect &rRect, bool bMove ) +{ + bool bRet = true; + if ( auto pVirtFlyDrawObj = dynamic_cast<const SwVirtFlyDrawObj*>(pSdrObj) ) + { + const SwFlyFrame* pFly = pVirtFlyDrawObj->GetFlyFrame(); + const bool bFollowTextFlow = pFly->GetFormat()->GetFollowTextFlow().GetValue(); + // #i28701# + const bool bConsiderWrapOnObjPos = + pFly->GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION); + const SwFormatVertOrient &rV = pFly->GetFormat()->GetVertOrient(); + if( pFly->IsFlyLayFrame() ) + { + const SwFrame* pClip; + // #i22305# + // #i28701# + if ( !bFollowTextFlow || bConsiderWrapOnObjPos ) + { + pClip = pFly->GetAnchorFrame()->FindPageFrame(); + } + else + { + pClip = pFly->GetAnchorFrame(); + } + + rRect = pClip->getFrameArea(); + SwRectFnSet aRectFnSet(pClip); + + // vertical clipping: Top and Bottom, also to PrtArea if necessary + if( rV.GetVertOrient() != text::VertOrientation::NONE && + rV.GetRelationOrient() == text::RelOrientation::PRINT_AREA ) + { + aRectFnSet.SetTop( rRect, aRectFnSet.GetPrtTop(*pClip) ); + aRectFnSet.SetBottom( rRect, aRectFnSet.GetPrtBottom(*pClip) ); + } + // horizontal clipping: Top and Bottom, also to PrtArea if necessary + const SwFormatHoriOrient &rH = pFly->GetFormat()->GetHoriOrient(); + if( rH.GetHoriOrient() != text::HoriOrientation::NONE && + rH.GetRelationOrient() == text::RelOrientation::PRINT_AREA ) + { + aRectFnSet.SetLeft( rRect, aRectFnSet.GetPrtLeft(*pClip) ); + aRectFnSet.SetRight(rRect, aRectFnSet.GetPrtRight(*pClip)); + } + } + else if( pFly->IsFlyAtContentFrame() ) + { + // #i18732# - consider following text flow or not + // AND alignment at 'page areas' + const SwFrame* pVertPosOrientFrame = pFly->GetVertPosOrientFrame(); + if ( !pVertPosOrientFrame ) + { + OSL_FAIL( "::CalcClipRect(..) - frame, vertical position is oriented at, is missing ."); + pVertPosOrientFrame = pFly->GetAnchorFrame(); + } + + if ( !bFollowTextFlow || bConsiderWrapOnObjPos ) + { + const SwLayoutFrame* pClipFrame = pVertPosOrientFrame->FindPageFrame(); + if (!pClipFrame) + { + OSL_FAIL("!pClipFrame: " + "if you can reproduce this please file a bug"); + return false; + } + rRect = bMove ? pClipFrame->GetUpper()->getFrameArea() + : pClipFrame->getFrameArea(); + // #i26945# - consider that a table, during + // its format, can exceed its upper printing area bottom. + // Thus, enlarge the clip rectangle, if such a case occurred + if ( pFly->GetAnchorFrame()->IsInTab() ) + { + const SwTabFrame* pTabFrame = const_cast<SwFlyFrame*>(pFly) + ->GetAnchorFrameContainingAnchPos()->FindTabFrame(); + SwRect aTmp( pTabFrame->getFramePrintArea() ); + aTmp += pTabFrame->getFrameArea().Pos(); + rRect.Union( aTmp ); + // #i43913# - consider also the cell frame + const SwFrame* pCellFrame = const_cast<SwFlyFrame*>(pFly) + ->GetAnchorFrameContainingAnchPos()->GetUpper(); + while ( pCellFrame && !pCellFrame->IsCellFrame() ) + { + pCellFrame = pCellFrame->GetUpper(); + } + if ( pCellFrame ) + { + aTmp = pCellFrame->getFramePrintArea(); + aTmp += pCellFrame->getFrameArea().Pos(); + rRect.Union( aTmp ); + } + } + } + else if ( rV.GetRelationOrient() == text::RelOrientation::PAGE_FRAME || + rV.GetRelationOrient() == text::RelOrientation::PAGE_PRINT_AREA ) + { + // new class <SwEnvironmentOfAnchoredObject> + objectpositioning::SwEnvironmentOfAnchoredObject + aEnvOfObj( bFollowTextFlow ); + const SwLayoutFrame& rVertClipFrame = + aEnvOfObj.GetVertEnvironmentLayoutFrame( *pVertPosOrientFrame ); + if ( rV.GetRelationOrient() == text::RelOrientation::PAGE_FRAME ) + { + rRect = rVertClipFrame.getFrameArea(); + } + else if ( rV.GetRelationOrient() == text::RelOrientation::PAGE_PRINT_AREA ) + { + if ( rVertClipFrame.IsPageFrame() ) + { + rRect = static_cast<const SwPageFrame&>(rVertClipFrame).PrtWithoutHeaderAndFooter(); + } + else + { + rRect = rVertClipFrame.getFrameArea(); + } + } + const SwLayoutFrame* pHoriClipFrame = + pFly->GetAnchorFrame()->FindPageFrame()->GetUpper(); + SwRectFnSet aRectFnSet(pFly->GetAnchorFrame()); + aRectFnSet.SetLeft( rRect, aRectFnSet.GetLeft(pHoriClipFrame->getFrameArea()) ); + aRectFnSet.SetRight(rRect, aRectFnSet.GetRight(pHoriClipFrame->getFrameArea())); + } + else + { + // #i26945# + const SwFrame *pClip = + const_cast<SwFlyFrame*>(pFly)->GetAnchorFrameContainingAnchPos(); + SwRectFnSet aRectFnSet(pClip); + const SwLayoutFrame *pUp = pClip->GetUpper(); + const SwFrame *pCell = pUp->IsCellFrame() ? pUp : nullptr; + const SwFrameType nType = bMove + ? SwFrameType::Root | SwFrameType::Fly | SwFrameType::Header | + SwFrameType::Footer | SwFrameType::Ftn + : SwFrameType::Body | SwFrameType::Fly | SwFrameType::Header | + SwFrameType::Footer | SwFrameType::Cell| SwFrameType::Ftn; + + while ( !(pUp->GetType() & nType) || pUp->IsColBodyFrame() ) + { + pUp = pUp->GetUpper(); + if ( !pCell && pUp->IsCellFrame() ) + pCell = pUp; + } + if ( bMove && pUp->IsRootFrame() ) + { + rRect = pUp->getFramePrintArea(); + rRect += pUp->getFrameArea().Pos(); + pUp = nullptr; + } + if ( pUp ) + { + if ( pUp->GetType() & SwFrameType::Body ) + { + const SwPageFrame *pPg; + if ( pUp->GetUpper() != (pPg = pFly->FindPageFrame()) ) + pUp = pPg->FindBodyCont(); + if (pUp) + { + rRect = pUp->GetUpper()->getFrameArea(); + aRectFnSet.SetTop( rRect, aRectFnSet.GetPrtTop(*pUp) ); + aRectFnSet.SetBottom(rRect, aRectFnSet.GetPrtBottom(*pUp)); + } + } + else + { + if( ( pUp->GetType() & (SwFrameType::Fly | SwFrameType::Ftn ) ) && + !pUp->getFrameArea().Contains( pFly->getFrameArea().Pos() ) ) + { + if( pUp->IsFlyFrame() ) + { + const SwFlyFrame *pTmpFly = static_cast<const SwFlyFrame*>(pUp); + while( pTmpFly->GetNextLink() ) + { + pTmpFly = pTmpFly->GetNextLink(); + if( pTmpFly->getFrameArea().Contains( pFly->getFrameArea().Pos() ) ) + break; + } + pUp = pTmpFly; + } + else if( pUp->IsInFootnote() ) + { + const SwFootnoteFrame *pTmp = pUp->FindFootnoteFrame(); + while( pTmp->GetFollow() ) + { + pTmp = pTmp->GetFollow(); + if( pTmp->getFrameArea().Contains( pFly->getFrameArea().Pos() ) ) + break; + } + pUp = pTmp; + } + } + rRect = pUp->getFramePrintArea(); + rRect.Pos() += pUp->getFrameArea().Pos(); + if ( pUp->GetType() & (SwFrameType::Header | SwFrameType::Footer) ) + { + rRect.Left ( pUp->GetUpper()->getFrameArea().Left() ); + rRect.Width( pUp->GetUpper()->getFrameArea().Width()); + } + else if ( pUp->IsCellFrame() ) //MA_FLY_HEIGHT + { + const SwFrame *pTab = pUp->FindTabFrame(); + aRectFnSet.SetBottom( rRect, aRectFnSet.GetPrtBottom(*pTab->GetUpper()) ); + // expand to left and right cell border + rRect.Left ( pUp->getFrameArea().Left() ); + rRect.Width( pUp->getFrameArea().Width() ); + } + } + } + if ( pCell ) + { + // CellFrames might also sit in unallowed areas. In this case, + // the Fly is allowed to do so as well + SwRect aTmp( pCell->getFramePrintArea() ); + aTmp += pCell->getFrameArea().Pos(); + rRect.Union( aTmp ); + } + } + } + else + { + const SwFrame *pUp = pFly->GetAnchorFrame()->GetUpper(); + SwRectFnSet aRectFnSet(pFly->GetAnchorFrame()); + while( pUp->IsColumnFrame() || pUp->IsSctFrame() || pUp->IsColBodyFrame()) + pUp = pUp->GetUpper(); + rRect = pUp->getFrameArea(); + if( !pUp->IsBodyFrame() ) + { + rRect += pUp->getFramePrintArea().Pos(); + rRect.SSize( pUp->getFramePrintArea().SSize() ); + if ( pUp->IsCellFrame() ) + { + const SwFrame *pTab = pUp->FindTabFrame(); + aRectFnSet.SetBottom( rRect, aRectFnSet.GetPrtBottom(*pTab->GetUpper()) ); + } + } + else if ( pUp->GetUpper()->IsPageFrame() ) + { + // Objects anchored as character may exceed right margin + // of body frame: + aRectFnSet.SetRight( rRect, aRectFnSet.GetRight(pUp->GetUpper()->getFrameArea()) ); + } + tools::Long nHeight = (9*aRectFnSet.GetHeight(rRect))/10; + tools::Long nTop; + const SwFormat *pFormat = GetUserCall(pSdrObj)->GetFormat(); + const SvxULSpaceItem &rUL = pFormat->GetULSpace(); + if( bMove ) + { + nTop = aRectFnSet.IsVert() ? static_cast<const SwFlyInContentFrame*>(pFly)->GetRefPoint().X() : + static_cast<const SwFlyInContentFrame*>(pFly)->GetRefPoint().Y(); + nTop = aRectFnSet.YInc( nTop, -nHeight ); + tools::Long nWidth = aRectFnSet.GetWidth(pFly->getFrameArea()); + aRectFnSet.SetLeftAndWidth( rRect, aRectFnSet.IsVert() ? + static_cast<const SwFlyInContentFrame*>(pFly)->GetRefPoint().Y() : + static_cast<const SwFlyInContentFrame*>(pFly)->GetRefPoint().X(), nWidth ); + nHeight = 2*nHeight - rUL.GetLower() - rUL.GetUpper(); + } + else + { + nTop = aRectFnSet.YInc( aRectFnSet.GetBottom(pFly->getFrameArea()), + rUL.GetLower() - nHeight ); + nHeight = 2*nHeight - aRectFnSet.GetHeight(pFly->getFrameArea()) + - rUL.GetLower() - rUL.GetUpper(); + } + aRectFnSet.SetTopAndHeight( rRect, nTop, nHeight ); + } + } + else + { + const SwDrawContact *pC = static_cast<const SwDrawContact*>(GetUserCall(pSdrObj)); + const SwFrameFormat *pFormat = pC->GetFormat(); + const SwFormatAnchor &rAnch = pFormat->GetAnchor(); + if ( RndStdIds::FLY_AS_CHAR == rAnch.GetAnchorId() ) + { + const SwFrame* pAnchorFrame = pC->GetAnchorFrame( pSdrObj ); + if( !pAnchorFrame ) + { + OSL_FAIL( "<::CalcClipRect(..)> - missing anchor frame." ); + const_cast<SwDrawContact*>(pC)->ConnectToLayout(); + pAnchorFrame = pC->GetAnchorFrame(); + } + const SwFrame* pUp = pAnchorFrame->GetUpper(); + rRect = pUp->getFramePrintArea(); + rRect += pUp->getFrameArea().Pos(); + SwRectFnSet aRectFnSet(pAnchorFrame); + tools::Long nHeight = (9*aRectFnSet.GetHeight(rRect))/10; + tools::Long nTop; + const SvxULSpaceItem &rUL = pFormat->GetULSpace(); + SwRect aSnapRect( pSdrObj->GetSnapRect() ); + tools::Long nTmpH = 0; + if( bMove ) + { + nTop = aRectFnSet.YInc( aRectFnSet.IsVert() ? pSdrObj->GetAnchorPos().X() : + pSdrObj->GetAnchorPos().Y(), -nHeight ); + tools::Long nWidth = aRectFnSet.GetWidth(aSnapRect); + aRectFnSet.SetLeftAndWidth( rRect, aRectFnSet.IsVert() ? + pSdrObj->GetAnchorPos().Y() : + pSdrObj->GetAnchorPos().X(), nWidth ); + } + else + { + // #i26791# - value of <nTmpH> is needed to + // calculate value of <nTop>. + nTmpH = aRectFnSet.IsVert() ? pSdrObj->GetCurrentBoundRect().GetWidth() : + pSdrObj->GetCurrentBoundRect().GetHeight(); + nTop = aRectFnSet.YInc( aRectFnSet.GetTop(aSnapRect), + rUL.GetLower() + nTmpH - nHeight ); + } + nHeight = 2*nHeight - nTmpH - rUL.GetLower() - rUL.GetUpper(); + aRectFnSet.SetTopAndHeight( rRect, nTop, nHeight ); + } + else + { + // restrict clip rectangle for drawing + // objects in header/footer to the page frame. + // #i26791# + const SwFrame* pAnchorFrame = pC->GetAnchorFrame( pSdrObj ); + if ( pAnchorFrame && pAnchorFrame->FindFooterOrHeader() ) + { + // clip frame is the page frame the header/footer is on. + const SwFrame* pClipFrame = pAnchorFrame->FindPageFrame(); + rRect = pClipFrame->getFrameArea(); + } + else + { + bRet = false; + } + } + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/flypos.cxx b/sw/source/core/layout/flypos.cxx new file mode 100644 index 000000000..0a7ab2dca --- /dev/null +++ b/sw/source/core/layout/flypos.cxx @@ -0,0 +1,65 @@ +/* -*- 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 <doc.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <fmtanchr.hxx> +#include <ndindex.hxx> +#include <frameformats.hxx> +#include <svx/swframetypes.hxx> + +bool SwPosFlyFrameCmp::operator()(const SwPosFlyFramePtr& rA, const SwPosFlyFramePtr& rB) const +{ + if (rA->GetNdIndex() == rB->GetNdIndex()) + { + // In this case, the order number decides! + return rA->GetOrdNum() < rB->GetOrdNum(); + } + + return rA->GetNdIndex() < rB->GetNdIndex(); +} + +SwPosFlyFrame::SwPosFlyFrame(const SwNodeIndex& rIdx, const SwFrameFormat* pFormat, + sal_uInt16 nArrPos) + : m_pFrameFormat(pFormat) + , m_pNodeIndex(const_cast<SwNodeIndex*>(&rIdx)) + , m_nOrdNum(SAL_MAX_UINT32) +{ + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + if (RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()) + m_pNodeIndex = new SwNodeIndex(rIdx); + else if (pFormat->GetDoc()->getIDocumentLayoutAccess().GetCurrentViewShell()) + pFormat->CallSwClientNotify(sw::GetZOrderHint(m_nOrdNum)); + if (m_nOrdNum == SAL_MAX_UINT32) + { + m_nOrdNum = pFormat->GetDoc()->GetSpzFrameFormats()->size(); + m_nOrdNum += nArrPos; + } +} + +SwPosFlyFrame::~SwPosFlyFrame() +{ + const SwFormatAnchor& rAnchor = m_pFrameFormat->GetAnchor(); + if (RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()) + { + delete m_pNodeIndex; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/frmtool.cxx b/sw/source/core/layout/frmtool.cxx new file mode 100644 index 000000000..5a23118be --- /dev/null +++ b/sw/source/core/layout/frmtool.cxx @@ -0,0 +1,4040 @@ +/* -*- 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 <config_wasm_strip.h> + +#include <svx/svdpage.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <sal/log.hxx> +#include <o3tl/deleter.hxx> +#include <osl/diagnose.h> + +#include <drawdoc.hxx> +#include <fmtornt.hxx> +#include <fmthdft.hxx> +#include <fmtfsize.hxx> +#include <fmtsrnd.hxx> +#include <docary.hxx> +#include <lineinfo.hxx> +#include <swmodule.hxx> +#include <pagefrm.hxx> +#include <colfrm.hxx> +#include <fesh.hxx> +#include <viewimp.hxx> +#include <viewopt.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <ftnfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <flyfrms.hxx> +#include <layact.hxx> +#include <pagedesc.hxx> +#include <section.hxx> +#include <sectfrm.hxx> +#include <node2lay.hxx> +#include <ndole.hxx> +#include <hints.hxx> +#include "layhelp.hxx" +#include <laycache.hxx> +#include <rootfrm.hxx> +#include <paratr.hxx> +#include <redline.hxx> +#include <sortedobjs.hxx> +#include <objectformatter.hxx> +#include <calbck.hxx> +#include <ndtxt.hxx> +#include <undobj.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentTimerAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <frameformats.hxx> +#include <boost/circular_buffer.hpp> +#include <svx/sdr/attribute/sdrallfillattributeshelper.hxx> + +using namespace ::com::sun::star; + +namespace { + // FIXME: would likely better be a member of SwRootFrame instead of a global flag + bool isFlyCreationSuppressed = false; +} +namespace sw { + FlyCreationSuppressor::FlyCreationSuppressor(bool wasAlreadySuppressedAllowed) + : m_wasAlreadySuppressed(isFlyCreationSuppressed) + { + (void)wasAlreadySuppressedAllowed; + assert(wasAlreadySuppressedAllowed || !isFlyCreationSuppressed); + isFlyCreationSuppressed = true; + } + FlyCreationSuppressor::~FlyCreationSuppressor() + { + isFlyCreationSuppressed = m_wasAlreadySuppressed; + } +} + +bool bObjsDirect = true; +bool bSetCompletePaintOnInvalidate = false; + +sal_uInt8 StackHack::s_nCnt = 0; +bool StackHack::s_bLocked = false; + +SwFrameNotify::SwFrameNotify( SwFrame *pF ) : + mpFrame( pF ), + maFrame( pF->getFrameArea() ), + maPrt( pF->getFramePrintArea() ), + mbInvaKeep( false ), + mbValidSize( pF->isFrameAreaSizeValid() ) +{ + if ( pF->IsTextFrame() ) + { + mnFlyAnchorOfst = static_cast<SwTextFrame*>(pF)->GetBaseOffsetForFly( true ); + mnFlyAnchorOfstNoWrap = static_cast<SwTextFrame*>(pF)->GetBaseOffsetForFly( false ); + } + else + { + mnFlyAnchorOfst = 0; + mnFlyAnchorOfstNoWrap = 0; + } + + mbHadFollow = pF->IsContentFrame() && static_cast<SwContentFrame*>(pF)->GetFollow(); +} + +SwFrameNotify::~SwFrameNotify() +{ + suppress_fun_call_w_exception(ImplDestroy()); +} + +void SwFrameNotify::ImplDestroy() +{ + SwRectFnSet aRectFnSet(mpFrame); + const bool bAbsP = aRectFnSet.PosDiff(maFrame, mpFrame->getFrameArea()); + const bool bChgWidth = + aRectFnSet.GetWidth(maFrame) != aRectFnSet.GetWidth(mpFrame->getFrameArea()); + const bool bChgHeight = + aRectFnSet.GetHeight(maFrame)!=aRectFnSet.GetHeight(mpFrame->getFrameArea()); + const bool bChgFlyBasePos = mpFrame->IsTextFrame() && + ( ( mnFlyAnchorOfst != static_cast<SwTextFrame*>(mpFrame)->GetBaseOffsetForFly( true ) ) || + ( mnFlyAnchorOfstNoWrap != static_cast<SwTextFrame*>(mpFrame)->GetBaseOffsetForFly( false ) ) ); + + if ( mpFrame->IsFlowFrame() && !mpFrame->IsInFootnote() ) + { + SwFlowFrame *pFlow = SwFlowFrame::CastFlowFrame( mpFrame ); + + if ( !pFlow->IsFollow() ) + { + if ( !mpFrame->GetIndPrev() ) + { + if ( mbInvaKeep ) + { + SwFrame *pPre = mpFrame->FindPrev(); + if ( pPre && pPre->IsFlowFrame() ) + { + // 1. pPre wants to keep with me: + bool bInvalidPrePos = SwFlowFrame::CastFlowFrame(pPre)->IsKeep(pPre->GetAttrSet()->GetKeep(), pPre->GetBreakItem()) + && pPre->GetIndPrev(); + + // 2. pPre is a table and the last row wants to keep with me: + if ( !bInvalidPrePos && pPre->IsTabFrame() ) + { + SwTabFrame* pPreTab = static_cast<SwTabFrame*>(pPre); + if ( pPreTab->GetFormat()->GetDoc()->GetDocumentSettingManager().get(DocumentSettingId::TABLE_ROW_KEEP) ) + { + SwRowFrame* pLastRow = static_cast<SwRowFrame*>(pPreTab->GetLastLower()); + if ( pLastRow && pLastRow->ShouldRowKeepWithNext() ) + bInvalidPrePos = true; + } + } + + if ( bInvalidPrePos ) + pPre->InvalidatePos(); + } + } + } + else if ( !pFlow->HasFollow() ) + { + tools::Long nOldHeight = aRectFnSet.GetHeight(maFrame); + tools::Long nNewHeight = aRectFnSet.GetHeight(mpFrame->getFrameArea()); + if( (nOldHeight > nNewHeight) || (!nOldHeight && nNewHeight) ) + pFlow->CheckKeep(); + } + } + } + + if ( bAbsP ) + { + mpFrame->SetCompletePaint(); + + SwFrame* pNxt = mpFrame->GetIndNext(); + // #121888# - skip empty section frames + while ( pNxt && + pNxt->IsSctFrame() && !static_cast<SwSectionFrame*>(pNxt)->GetSection() ) + { + pNxt = pNxt->GetIndNext(); + } + + if ( pNxt ) + pNxt->InvalidatePos(); + else + { + // #104100# - correct condition for setting retouche + // flag for vertical layout. + if( mpFrame->IsRetoucheFrame() && + aRectFnSet.TopDist( maFrame, aRectFnSet.GetTop(mpFrame->getFrameArea()) ) > 0 ) + { + mpFrame->SetRetouche(); + } + + // A fresh follow frame does not have to be invalidated, because + // it is already formatted: + if ( mbHadFollow || !mpFrame->IsContentFrame() || !static_cast<SwContentFrame*>(mpFrame)->GetFollow() ) + { + if ( !mpFrame->IsTabFrame() || !static_cast<SwTabFrame*>(mpFrame)->GetFollow() ) + mpFrame->InvalidateNextPos(); + } + } + } + + //For each resize of the background graphics is a repaint necessary. + const bool bPrtWidth = + aRectFnSet.GetWidth(maPrt) != aRectFnSet.GetWidth(mpFrame->getFramePrintArea()); + const bool bPrtHeight = + aRectFnSet.GetHeight(maPrt)!=aRectFnSet.GetHeight(mpFrame->getFramePrintArea()); + if ( bPrtWidth || bPrtHeight ) + { + bool bUseNewFillProperties(false); + if (mpFrame->supportsFullDrawingLayerFillAttributeSet()) + { + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes(mpFrame->getSdrAllFillAttributesHelper()); + if(aFillAttributes && aFillAttributes->isUsed()) + { + bUseNewFillProperties = true; + // use SetCompletePaint if needed + if(aFillAttributes->needCompleteRepaint()) + { + mpFrame->SetCompletePaint(); + } + } + } + if (!bUseNewFillProperties) + { + const SvxGraphicPosition ePos = mpFrame->GetAttrSet()->GetBackground().GetGraphicPos(); + if(GPOS_NONE != ePos && GPOS_TILED != ePos) + mpFrame->SetCompletePaint(); + } + } + else + { + // #97597# - consider case that *only* margins between + // frame and printing area has changed. Then, frame has to be repainted, + // in order to force paint of the margin areas. + if ( !bAbsP && (bChgWidth || bChgHeight) ) + { + mpFrame->SetCompletePaint(); + } + } + + const bool bPrtP = aRectFnSet.PosDiff( maPrt, mpFrame->getFramePrintArea() ); + if ( bAbsP || bPrtP || bChgWidth || bChgHeight || + bPrtWidth || bPrtHeight || bChgFlyBasePos ) + { +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if( mpFrame->IsAccessibleFrame() ) + { + SwRootFrame *pRootFrame = mpFrame->getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && + pRootFrame->GetCurrShell() ) + { + pRootFrame->GetCurrShell()->Imp()->MoveAccessibleFrame( mpFrame, maFrame ); + } + } +#endif + + // Notification of anchored objects + if ( mpFrame->GetDrawObjs() ) + { + const SwSortedObjs &rObjs = *mpFrame->GetDrawObjs(); + SwPageFrame* pPageFrame = nullptr; + for (SwAnchoredObject* pObj : rObjs) + { + // OD 2004-03-31 #i26791# - no general distinction between + // Writer fly frames and drawing objects + bool bNotify = false; + bool bNotifySize = false; + SwContact* pContact = ::GetUserCall( pObj->GetDrawObj() ); + const bool bAnchoredAsChar = pContact->ObjAnchoredAsChar(); + if ( !bAnchoredAsChar ) + { + // Notify object, which aren't anchored as-character: + + // always notify objects, if frame position has changed + // or if the object is to-page|to-fly anchored. + if ( bAbsP || + pContact->ObjAnchoredAtPage() || + pContact->ObjAnchoredAtFly() ) + { + bNotify = true; + + // assure that to-fly anchored Writer fly frames are + // registered at the correct page frame, if frame + // position has changed. + if ( bAbsP && pContact->ObjAnchoredAtFly() && + pObj->DynCastFlyFrame() != nullptr ) + { + // determine to-fly anchored Writer fly frame + SwFlyFrame* pFlyFrame = static_cast<SwFlyFrame*>(pObj); + // determine page frame of to-fly anchored + // Writer fly frame + SwPageFrame* pFlyPageFrame = pFlyFrame->FindPageFrame(); + // determine page frame, if needed. + if ( !pPageFrame ) + { + pPageFrame = mpFrame->FindPageFrame(); + } + if ( pPageFrame != pFlyPageFrame ) + { + OSL_ENSURE( pFlyPageFrame, "~SwFrameNotify: Fly from Nowhere" ); + if( pFlyPageFrame ) + pFlyPageFrame->MoveFly( pFlyFrame, pPageFrame ); + else + pPageFrame->AppendFlyToPage( pFlyFrame ); + } + } + } + // otherwise the objects are notified in dependence to + // its positioning and alignment + else + { + const SwFormatVertOrient& rVert = + pContact->GetFormat()->GetVertOrient(); + if ( ( rVert.GetVertOrient() == text::VertOrientation::CENTER || + rVert.GetVertOrient() == text::VertOrientation::BOTTOM || + rVert.GetRelationOrient() == text::RelOrientation::PRINT_AREA ) && + ( bChgHeight || bPrtHeight ) ) + { + bNotify = true; + } + if ( !bNotify ) + { + const SwFormatHoriOrient& rHori = + pContact->GetFormat()->GetHoriOrient(); + if ( ( rHori.GetHoriOrient() != text::HoriOrientation::NONE || + rHori.GetRelationOrient()== text::RelOrientation::PRINT_AREA || + rHori.GetRelationOrient()== text::RelOrientation::FRAME ) && + ( bChgWidth || bPrtWidth || bChgFlyBasePos ) ) + { + bNotify = true; + } + } + } + } + else if ( bPrtWidth ) + { + // Notify as-character anchored objects, if printing area + // width has changed. + bNotify = true; + bNotifySize = true; + } + + // perform notification via the corresponding invalidations + if ( bNotify ) + { + if ( auto pFlyFrame = pObj->DynCastFlyFrame() ) + { + if ( bNotifySize ) + pFlyFrame->InvalidateSize_(); + // #115759# - no invalidation of + // position for as-character anchored objects. + if ( !bAnchoredAsChar ) + { + pFlyFrame->InvalidatePos_(); + } + pFlyFrame->Invalidate_(); + } + else if ( dynamic_cast<const SwAnchoredDrawObject*>( pObj) != nullptr ) + { + // #115759# - no invalidation of + // position for as-character anchored objects. + if ( !bAnchoredAsChar ) + { + pObj->InvalidateObjPos(); + } + } + else + { + OSL_FAIL( "<SwContentNotify::~SwContentNotify()> - unknown anchored object type." ); + } + } + } + } + } +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + else if( mpFrame->IsTextFrame() && mbValidSize != mpFrame->isFrameAreaSizeValid() ) + { + SwRootFrame *pRootFrame = mpFrame->getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && + pRootFrame->GetCurrShell() ) + { + pRootFrame->GetCurrShell()->Imp()->InvalidateAccessibleFrameContent( mpFrame ); + } + } +#endif + + // #i9046# Automatic frame width + SwFlyFrame* pFly = nullptr; + // #i35879# Do not trust the inf flags. pFrame does not + // necessarily have to have an upper! + if ( mpFrame->IsFlyFrame() || nullptr == ( pFly = mpFrame->ImplFindFlyFrame() )) + return; + + // #i61999# + // no invalidation of columned Writer fly frames, because automatic + // width doesn't make sense for such Writer fly frames. + if ( !pFly->Lower() || pFly->Lower()->IsColumnFrame() ) + return; + + const SwFormatFrameSize &rFrameSz = pFly->GetFormat()->GetFrameSize(); + + // This could be optimized. Basically the fly frame only has to + // be invalidated, if the first line of pFrame (if pFrame is a content + // frame, for other frame types it's the print area) has changed its + // size and pFrame was responsible for the current width of pFly. On + // the other hand, this is only rarely used and re-calculation of + // the fly frame does not cause too much trouble. So we keep it this + // way: + if ( SwFrameSize::Fixed != rFrameSz.GetWidthSizeType() ) + { + // #i50668#, #i50998# - invalidation of position + // of as-character anchored fly frames not needed and can cause + // layout loops + if ( dynamic_cast<const SwFlyInContentFrame*>( pFly) == nullptr ) + { + pFly->InvalidatePos(); + } + pFly->InvalidateSize(); + } +} + +SwLayNotify::SwLayNotify( SwLayoutFrame *pLayFrame ) : + SwFrameNotify( pLayFrame ), + m_bLowersComplete( false ) +{ +} + +// OD 2004-05-11 #i28701# - local method to invalidate the position of all +// frames inclusive its floating screen objects, which are lowers of the given +// layout frame +static void lcl_InvalidatePosOfLowers( SwLayoutFrame& _rLayoutFrame ) +{ + if( _rLayoutFrame.IsFlyFrame() && _rLayoutFrame.GetDrawObjs() ) + { + _rLayoutFrame.InvalidateObjs( false ); + } + + SwFrame* pLowerFrame = _rLayoutFrame.Lower(); + while ( pLowerFrame ) + { + pLowerFrame->InvalidatePos(); + if ( pLowerFrame->IsTextFrame() ) + { + static_cast<SwTextFrame*>(pLowerFrame)->Prepare( PrepareHint::FramePositionChanged ); + } + else if ( pLowerFrame->IsTabFrame() ) + { + pLowerFrame->InvalidatePrt(); + } + + pLowerFrame->InvalidateObjs( false ); + + pLowerFrame = pLowerFrame->GetNext(); + } +} + +void SwLayNotify::ImplDestroy() +{ + SwLayoutFrame *pLay = static_cast<SwLayoutFrame*>(mpFrame); + SwRectFnSet aRectFnSet(pLay); + bool bNotify = false; + if ( pLay->getFramePrintArea().SSize() != maPrt.SSize() ) + { + if ( !IsLowersComplete() ) + { + bool bInvaPercent; + + if ( pLay->IsRowFrame() ) + { + bInvaPercent = true; + tools::Long nNew = aRectFnSet.GetHeight(pLay->getFramePrintArea()); + if( nNew != aRectFnSet.GetHeight(maPrt) ) + static_cast<SwRowFrame*>(pLay)->AdjustCells( nNew, true); + if( aRectFnSet.GetWidth(pLay->getFramePrintArea()) + != aRectFnSet.GetWidth(maPrt) ) + static_cast<SwRowFrame*>(pLay)->AdjustCells( 0, false ); + } + else + { + //Proportional adoption of the internal. + //1. If the formatted is no Fly + //2. If he contains no columns + //3. If the Fly has a fixed height and the columns + // are next to be. + // Hoehe danebenliegen. + //4. Never at SectionFrames. + bool bLow; + if( pLay->IsFlyFrame() ) + { + if ( pLay->Lower() ) + { + bLow = !pLay->Lower()->IsColumnFrame() || + aRectFnSet.GetHeight(pLay->Lower()->getFrameArea()) + != aRectFnSet.GetHeight(pLay->getFramePrintArea()); + } + else + bLow = false; + } + else if( pLay->IsSctFrame() ) + { + if ( pLay->Lower() ) + { + if( pLay->Lower()->IsColumnFrame() && pLay->Lower()->GetNext() ) + bLow = pLay->Lower()->getFrameArea().Height() != pLay->getFramePrintArea().Height(); + else + bLow = pLay->getFramePrintArea().Width() != maPrt.Width(); + } + else + bLow = false; + } + else if( pLay->IsFooterFrame() && !pLay->HasFixSize() ) + bLow = pLay->getFramePrintArea().Width() != maPrt.Width(); + else + bLow = true; + bInvaPercent = bLow; + if ( bLow ) + { + pLay->ChgLowersProp( maPrt.SSize() ); + } + // If the PrtArea has been extended, it might be possible that the chain of parts + // can take another frame. As a result, the "possible right one" needs to be + // invalidated. This only pays off if this or its Uppers are moveable sections. + // A PrtArea has been extended if width or height are larger than before. + if ( (pLay->getFramePrintArea().Height() > maPrt.Height() || + pLay->getFramePrintArea().Width() > maPrt.Width()) && + (pLay->IsMoveable() || pLay->IsFlyFrame()) ) + { + SwFrame *pTmpFrame = pLay->Lower(); + if ( pTmpFrame && pTmpFrame->IsFlowFrame() ) + { + while ( pTmpFrame->GetNext() ) + pTmpFrame = pTmpFrame->GetNext(); + pTmpFrame->InvalidateNextPos(); + } + } + } + bNotify = true; + //EXPENSIVE!! But how we do it more elegant? + if( bInvaPercent ) + pLay->InvaPercentLowers( pLay->getFramePrintArea().Height() - maPrt.Height() ); + } + if ( pLay->IsTabFrame() ) + //So that _only_ the shadow is drawn while resizing. + static_cast<SwTabFrame*>(pLay)->SetComplete(); + else + { + const SwViewShell *pSh = pLay->getRootFrame()->GetCurrShell(); + if( !( pSh && pSh->GetViewOptions()->getBrowseMode() ) || + !(pLay->GetType() & (SwFrameType::Body | SwFrameType::Page)) ) + //Thereby the subordinates are retouched clean. + //Example problem: Take the Flys with the handles and downsize. + //Not for body and page, otherwise it flickers when loading HTML. + pLay->SetCompletePaint(); + } + } + //Notify Lower if the position has changed. + const bool bPrtPos = aRectFnSet.PosDiff( maPrt, pLay->getFramePrintArea() ); + const bool bPos = bPrtPos || aRectFnSet.PosDiff( maFrame, pLay->getFrameArea() ); + const bool bSize = pLay->getFrameArea().SSize() != maFrame.SSize(); + + if ( bPos && pLay->Lower() && !IsLowersComplete() ) + { + pLay->Lower()->InvalidatePos(); + SwFootnoteFrame* pFtnFrame = pLay->Lower()->IsFootnoteFrame() ? + static_cast<SwFootnoteFrame*>(pLay->Lower()) : nullptr; + SwFrame* pFtnLower = pFtnFrame ? pFtnFrame->Lower() : nullptr; + if (pFtnLower) + pFtnLower->InvalidatePos(); + } + + if ( bPrtPos ) + pLay->SetCompletePaint(); + + //Inform the Follower if the SSize has changed. + if ( bSize ) + { + if( pLay->GetNext() ) + { + if ( pLay->GetNext()->IsLayoutFrame() ) + pLay->GetNext()->InvalidatePos_(); + else + pLay->GetNext()->InvalidatePos(); + } + else if( pLay->IsSctFrame() ) + pLay->InvalidateNextPos(); + } + if ( !IsLowersComplete() && + !(pLay->GetType()&(SwFrameType::Fly|SwFrameType::Section) && + pLay->Lower() && pLay->Lower()->IsColumnFrame()) && + (bPos || bNotify) && + !(pLay->GetType() & (SwFrameType::Row|SwFrameType::Tab|SwFrameType::FtnCont|SwFrameType::Page|SwFrameType::Root))) + { + // #i44016# - force unlock of position of lower objects. + // #i43913# - no unlock of position of objects, + // if <pLay> is a cell frame, and its table frame resp. its parent table + // frame is locked. + // #i47458# - force unlock of position of lower objects, + // only if position of layout frame has changed. + bool bUnlockPosOfObjs( bPos ); + if ( bUnlockPosOfObjs && pLay->IsCellFrame() ) + { + SwTabFrame* pTabFrame( pLay->FindTabFrame() ); + if ( pTabFrame && + ( pTabFrame->IsJoinLocked() || + ( pTabFrame->IsFollow() && + pTabFrame->FindMaster()->IsJoinLocked() ) ) ) + { + bUnlockPosOfObjs = false; + } + } + // #i49383# - check for footnote frame, if unlock + // of position of lower objects is allowed. + else if ( bUnlockPosOfObjs && pLay->IsFootnoteFrame() ) + { + bUnlockPosOfObjs = static_cast<SwFootnoteFrame*>(pLay)->IsUnlockPosOfLowerObjs(); + } + // #i51303# - no unlock of object positions for sections + else if ( bUnlockPosOfObjs && pLay->IsSctFrame() ) + { + bUnlockPosOfObjs = false; + } + pLay->NotifyLowerObjs( bUnlockPosOfObjs ); + } + if ( bPos && pLay->IsFootnoteFrame() && pLay->Lower() ) + { + // OD 2004-05-11 #i28701# + ::lcl_InvalidatePosOfLowers( *pLay ); + } + if( ( bPos || bSize ) && pLay->IsFlyFrame() && static_cast<SwFlyFrame*>(pLay)->GetAnchorFrame() + && static_cast<SwFlyFrame*>(pLay)->GetAnchorFrame()->IsFlyFrame() ) + static_cast<SwFlyFrame*>(pLay)->AnchorFrame()->InvalidateSize(); +} + +SwLayNotify::~SwLayNotify() +{ + suppress_fun_call_w_exception(ImplDestroy()); +} + +SwFlyNotify::SwFlyNotify( SwFlyFrame *pFlyFrame ) : + SwLayNotify( pFlyFrame ), + // #115759# - keep correct page frame - the page frame + // the Writer fly frame is currently registered at. + m_pOldPage( pFlyFrame->GetPageFrame() ), + m_aFrameAndSpace( pFlyFrame->GetObjRectWithSpaces() ) +{ +} + +void SwFlyNotify::ImplDestroy() +{ + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(mpFrame); + if ( pFly->IsNotifyBack() ) + { + SwViewShell *pSh = pFly->getRootFrame()->GetCurrShell(); + SwViewShellImp *pImp = pSh ? pSh->Imp() : nullptr; + if ( !pImp || !pImp->IsAction() || !pImp->GetLayAction().IsAgain() ) + { + //If in the LayAction the IsAgain is set it can be + //that the old page is destroyed in the meantime! + ::Notify( pFly, m_pOldPage, m_aFrameAndSpace, &maPrt ); + // #i35640# - additional notify anchor text frame, + // if Writer fly frame has changed its page + if ( pFly->GetAnchorFrame()->IsTextFrame() && + pFly->GetPageFrame() != m_pOldPage ) + { + pFly->AnchorFrame()->Prepare( PrepareHint::FlyFrameLeave ); + } + } + pFly->ResetNotifyBack(); + } + + //Have the size or the position changed, + //so should the view know this. + SwRectFnSet aRectFnSet(pFly); + const bool bPosChgd = aRectFnSet.PosDiff( maFrame, pFly->getFrameArea() ); + const bool bFrameChgd = pFly->getFrameArea().SSize() != maFrame.SSize(); + const bool bPrtChgd = maPrt != pFly->getFramePrintArea(); + if ( bPosChgd || bFrameChgd || bPrtChgd ) + { + pFly->NotifyDrawObj(); + } + if ( bPosChgd && maFrame.Pos().X() != FAR_AWAY ) + { + // OD 2004-05-10 #i28701# - no direct move of lower Writer fly frames. + // reason: New positioning and alignment (e.g. to-paragraph anchored, + // but aligned at page) are introduced. + // <SwLayNotify::~SwLayNotify()> takes care of invalidation of lower + // floating screen objects by calling method <SwLayoutFrame::NotifyLowerObjs()>. + + if ( pFly->IsFlyAtContentFrame() ) + { + SwFrame *pNxt = pFly->AnchorFrame()->FindNext(); + while (pNxt) + { + pNxt->InvalidatePos(); + if (!pNxt->IsSctFrame()) + { + break; + } + // invalidating pos of a section frame doesn't have much + // effect, so try again with its lower + pNxt = static_cast<SwSectionFrame*>(pNxt)->Lower(); + } + } + + // #i26945# - notify anchor. + // Needed for negative positioned Writer fly frames + if ( pFly->GetAnchorFrame()->IsTextFrame() ) + { + pFly->AnchorFrame()->Prepare( PrepareHint::FlyFrameLeave ); + } + } + + // OD 2004-05-13 #i28701# + // #i45180# - no adjustment of layout process flags and + // further notifications/invalidations, if format is called by grow/shrink + if ( !pFly->ConsiderObjWrapInfluenceOnObjPos() ) + return; + if (pFly->IsFlyFreeFrame()) + { + if (static_cast<SwFlyFreeFrame*>(pFly)->IsNoMoveOnCheckClip()) + return; + } + + // #i54138# - suppress restart of the layout process + // on changed frame height. + // Note: It doesn't seem to be necessary and can cause layout loops. + if ( bPosChgd ) + { + // indicate a restart of the layout process + pFly->SetRestartLayoutProcess( true ); + } + else + { + // lock position + pFly->LockPosition(); + } + + if ( pFly->ConsiderForTextWrap() ) + return; + + // indicate that object has to be considered for text wrap + pFly->SetConsiderForTextWrap( true ); + // invalidate 'background' in order to allow its 'background' + // to wrap around it. + pFly->NotifyBackground( pFly->GetPageFrame(), + pFly->GetObjRectWithSpaces(), + PrepareHint::FlyFrameArrive ); + // invalidate position of anchor frame in order to force + // a re-format of the anchor frame, which also causes a + // re-format of the invalid previous frames of the anchor frame. + pFly->AnchorFrame()->InvalidatePos(); +} + +SwFlyNotify::~SwFlyNotify() +{ + suppress_fun_call_w_exception(ImplDestroy()); +} + +SwContentNotify::SwContentNotify( SwContentFrame *pContentFrame ) : + SwFrameNotify( pContentFrame ), + // OD 08.01.2004 #i11859# + mbChkHeightOfLastLine( false ), + mnHeightOfLastLine( 0 ), + // OD 2004-02-26 #i25029# + mbInvalidatePrevPrtArea( false ), + mbBordersJoinedWithPrev( false ) +{ + // OD 08.01.2004 #i11859# + if ( !pContentFrame->IsTextFrame() ) + return; + + SwTextFrame* pTextFrame = static_cast<SwTextFrame*>(pContentFrame); + if (!pTextFrame->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::OLD_LINE_SPACING)) + { + const SvxLineSpacingItem &rSpace = pTextFrame->GetAttrSet()->GetLineSpacing(); + if ( rSpace.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) + { + mbChkHeightOfLastLine = true; + mnHeightOfLastLine = pTextFrame->GetHeightOfLastLine(); + } + } +} + +void SwContentNotify::ImplDestroy() +{ + SwContentFrame *pCnt = static_cast<SwContentFrame*>(mpFrame); + if ( bSetCompletePaintOnInvalidate ) + pCnt->SetCompletePaint(); + + SwRectFnSet aRectFnSet(pCnt); + if ( pCnt->IsInTab() && ( aRectFnSet.PosDiff( pCnt->getFrameArea(), maFrame ) || + pCnt->getFrameArea().SSize() != maFrame.SSize())) + { + SwLayoutFrame* pCell = pCnt->GetUpper(); + while( !pCell->IsCellFrame() && pCell->GetUpper() ) + pCell = pCell->GetUpper(); + OSL_ENSURE( pCell->IsCellFrame(), "Where's my cell?" ); + if ( text::VertOrientation::NONE != pCell->GetFormat()->GetVertOrient().GetVertOrient() ) + pCell->InvalidatePrt(); //for the vertical align. + } + + // OD 2004-02-26 #i25029# + if ( mbInvalidatePrevPrtArea && mbBordersJoinedWithPrev && + pCnt->IsTextFrame() && + !pCnt->IsFollow() && !pCnt->GetIndPrev() ) + { + // determine previous frame + SwFrame* pPrevFrame = pCnt->FindPrev(); + // skip empty section frames and hidden text frames + { + while ( pPrevFrame && + ( ( pPrevFrame->IsSctFrame() && + !static_cast<SwSectionFrame*>(pPrevFrame)->GetSection() ) || + ( pPrevFrame->IsTextFrame() && + static_cast<SwTextFrame*>(pPrevFrame)->IsHiddenNow() ) ) ) + { + pPrevFrame = pPrevFrame->FindPrev(); + } + } + + // Invalidate printing area of found previous frame + if ( pPrevFrame ) + { + if ( pPrevFrame->IsSctFrame() ) + { + if ( pCnt->IsInSct() ) + { + // Note: found previous frame is a section frame and + // <pCnt> is also inside a section. + // Thus due to <mbBordersJoinedWithPrev>, + // <pCnt> had joined its borders/shadow with the + // last content of the found section. + // Invalidate printing area of last content in found section. + SwFrame* pLstContentOfSctFrame = + static_cast<SwSectionFrame*>(pPrevFrame)->FindLastContent(); + if ( pLstContentOfSctFrame ) + { + pLstContentOfSctFrame->InvalidatePrt(); + } + } + } + else + { + pPrevFrame->InvalidatePrt(); + } + } + } + + const bool bFirst = aRectFnSet.GetWidth(maFrame) == 0; + + if ( pCnt->IsNoTextFrame() ) + { + //Active PlugIn's or OLE-Objects should know something of the change + //thereby they move their window appropriate. + SwViewShell *pSh = pCnt->getRootFrame()->GetCurrShell(); + if ( pSh ) + { + SwOLENode *const pNd(static_cast<SwNoTextFrame*>(pCnt)->GetNode()->GetOLENode()); + if (nullptr != pNd && + (pNd->GetOLEObj().IsOleRef() || + pNd->IsOLESizeInvalid()) ) + { + const bool bNoTextFramePrtAreaChanged = + ( maPrt.SSize().Width() != 0 && + maPrt.SSize().Height() != 0 ) && + maPrt.SSize() != pCnt->getFramePrintArea().SSize(); + OSL_ENSURE( pCnt->IsInFly(), "OLE not in FlyFrame" ); + SwFlyFrame *pFly = pCnt->FindFlyFrame(); + svt::EmbeddedObjectRef& xObj = pNd->GetOLEObj().GetObject(); + SwFEShell *pFESh = nullptr; + for(SwViewShell& rCurrentShell : pSh->GetRingContainer()) + { if ( dynamic_cast<const SwCursorShell*>( &rCurrentShell) != nullptr ) + { + pFESh = static_cast<SwFEShell*>(&rCurrentShell); + // #108369#: Here used to be the condition if (!bFirst). + // I think this should mean "do not call CalcAndSetScale" + // if the frame is formatted for the first time. + // Unfortunately this is not valid anymore since the + // SwNoTextFrame already gets a width during CalcLowerPreps. + // Nevertheless, the indention of !bFirst seemed to be + // to assure that the OLE objects have already been notified + // if necessary before calling CalcAndSetScale. + // So I replaced !bFirst by !IsOLESizeInvalid. There is + // one additional problem specific to the word import: + // The layout is calculated _before_ calling PrtOLENotify, + // and the OLE objects are not invalidated during import. + // Therefore I added the condition !IsUpdateExpField, + // have a look at the occurrence of CalcLayout in + // uiview/view.cxx. + if ( !pNd->IsOLESizeInvalid() && + !pSh->GetDoc()->getIDocumentState().IsUpdateExpField() ) + pFESh->CalcAndSetScale( xObj, + &pFly->getFramePrintArea(), &pFly->getFrameArea(), + bNoTextFramePrtAreaChanged ); + } + } + + if ( pFESh && pNd->IsOLESizeInvalid() ) + { + pNd->SetOLESizeInvalid( false ); + pFESh->CalcAndSetScale( xObj ); // create client + } + } + // ditto animated graphics + if ( getFrameArea().HasArea() && static_cast<SwNoTextFrame*>(pCnt)->HasAnimation() ) + { + static_cast<SwNoTextFrame*>(pCnt)->StopAnimation(); + pSh->InvalidateWindows( getFrameArea() ); + } + } + } + + if ( bFirst ) + { + pCnt->SetRetouche(); //fix(13870) + + SwDoc& rDoc = pCnt->IsTextFrame() + ? static_cast<SwTextFrame*>(pCnt)->GetDoc() + : static_cast<SwNoTextFrame*>(pCnt)->GetNode()->GetDoc(); + if ( !rDoc.GetSpzFrameFormats()->empty() && + rDoc.DoesContainAtPageObjWithContentAnchor() && !rDoc.getIDocumentState().IsNewDoc() ) + { + // If certain import filters for foreign file format import + // AT_PAGE anchored objects, the corresponding page number is + // typically not known. In this case the content position is + // stored at which the anchored object is found in the + // imported document. + // When this content is formatted it is the time at which + // the page is known. Thus, this data can be corrected now. + + const SwPageFrame *pPage = nullptr; + SwFrameFormats *pTable = rDoc.GetSpzFrameFormats(); + + for ( size_t i = 0; i < pTable->size(); ++i ) + { + SwFrameFormat *pFormat = (*pTable)[i]; + const SwFormatAnchor &rAnch = pFormat->GetAnchor(); + if ( RndStdIds::FLY_AT_PAGE != rAnch.GetAnchorId() || + rAnch.GetContentAnchor() == nullptr ) + { + continue; + } + + if (FrameContainsNode(*pCnt, rAnch.GetContentAnchor()->nNode.GetIndex())) + { + OSL_FAIL( "<SwContentNotify::~SwContentNotify()> - to page anchored object with content position." ); + if ( !pPage ) + { + pPage = pCnt->FindPageFrame(); + } + SwFormatAnchor aAnch( rAnch ); + aAnch.SetAnchor( nullptr ); + aAnch.SetPageNum( pPage->GetPhyPageNum() ); + pFormat->SetFormatAttr( aAnch ); + if ( RES_DRAWFRMFMT != pFormat->Which() ) + { + pFormat->MakeFrames(); + } + } + } + } + } + + // OD 12.01.2004 #i11859# - invalidate printing area of following frame, + // if height of last line has changed. + if ( pCnt->IsTextFrame() && mbChkHeightOfLastLine ) + { + if ( mnHeightOfLastLine != static_cast<SwTextFrame*>(pCnt)->GetHeightOfLastLine() ) + { + pCnt->InvalidateNextPrtArea(); + } + } + + // #i44049# + if ( pCnt->IsTextFrame() && aRectFnSet.PosDiff( maFrame, pCnt->getFrameArea() ) ) + { + pCnt->InvalidateObjs(); + } + + // #i43255# - move code to invalidate at-character + // anchored objects due to a change of its anchor character from + // method <SwTextFrame::Format(..)>. + if ( !pCnt->IsTextFrame() ) + return; + + SwTextFrame* pMasterFrame = pCnt->IsFollow() + ? static_cast<SwTextFrame*>(pCnt)->FindMaster() + : static_cast<SwTextFrame*>(pCnt); + if ( pMasterFrame && !pMasterFrame->IsFlyLock() && + pMasterFrame->GetDrawObjs() ) + { + SwSortedObjs* pObjs = pMasterFrame->GetDrawObjs(); + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( pAnchoredObj->GetFrameFormat().GetAnchor().GetAnchorId() + == RndStdIds::FLY_AT_CHAR ) + { + pAnchoredObj->CheckCharRectAndTopOfLine( !pMasterFrame->IsEmpty() ); + } + } + } +} + +SwContentNotify::~SwContentNotify() +{ + suppress_fun_call_w_exception(ImplDestroy()); +} + +// note this *cannot* be static because it's a friend +void AppendObj(SwFrame *const pFrame, SwPageFrame *const pPage, SwFrameFormat *const pFormat, const SwFormatAnchor & rAnch) +{ + const bool bFlyAtFly = rAnch.GetAnchorId() == RndStdIds::FLY_AT_FLY; // LAYER_IMPL + //Is a frame or a SdrObject described? + const bool bSdrObj = RES_DRAWFRMFMT == pFormat->Which(); + // OD 23.06.2003 #108784# - append also drawing objects anchored + // as character. + const bool bDrawObjInContent = bSdrObj && + (rAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR); + + if( !(bFlyAtFly || + (rAnch.GetAnchorId() == RndStdIds::FLY_AT_PARA) || + (rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR) || + bDrawObjInContent) ) + return; + + SdrObject* pSdrObj = nullptr; + if ( bSdrObj && nullptr == (pSdrObj = pFormat->FindSdrObject()) ) + { + OSL_ENSURE( !bSdrObj, "DrawObject not found." ); + pFormat->GetDoc()->DelFrameFormat( pFormat ); + return; + } + if ( pSdrObj ) + { + if ( !pSdrObj->getSdrPageFromSdrObject() ) + { + pFormat->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0)-> + InsertObject(pSdrObj, pSdrObj->GetOrdNumDirect()); + } + + SwDrawContact* pNew = + static_cast<SwDrawContact*>(GetUserCall( pSdrObj )); + if ( !pNew->GetAnchorFrame() ) + { + pFrame->AppendDrawObj( *(pNew->GetAnchoredObj( nullptr )) ); + } + // OD 19.06.2003 #108784# - add 'virtual' drawing object, + // if necessary. But control objects have to be excluded. + else if ( !::CheckControlLayer( pSdrObj ) && + pNew->GetAnchorFrame() != pFrame && + !pNew->GetDrawObjectByAnchorFrame( *pFrame ) ) + { + SwDrawVirtObj* pDrawVirtObj = pNew->AddVirtObj(*pFrame); + pFrame->AppendDrawObj( *(pNew->GetAnchoredObj( pDrawVirtObj )) ); + + pDrawVirtObj->ActionChanged(); + } + } + else + { + SwFlyFrame *pFly; + if( bFlyAtFly ) + pFly = new SwFlyLayFrame( static_cast<SwFlyFrameFormat*>(pFormat), pFrame, pFrame ); + else + pFly = new SwFlyAtContentFrame( static_cast<SwFlyFrameFormat*>(pFormat), pFrame, pFrame ); + pFly->Lock(); + pFrame->AppendFly( pFly ); + pFly->Unlock(); + if ( pPage ) + ::RegistFlys( pPage, pFly ); + } +} + +static bool IsShown(SwNodeOffset const nIndex, + const SwFormatAnchor & rAnch, + std::vector<sw::Extent>::const_iterator const*const pIter, + std::vector<sw::Extent>::const_iterator const*const pEnd, + SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode) +{ + assert(!pIter || *pIter == *pEnd || (*pIter)->pNode->GetIndex() == nIndex); + SwPosition const& rAnchor(*rAnch.GetContentAnchor()); + if (rAnchor.nNode.GetIndex() != nIndex) + { + return false; + } + if (rAnch.GetAnchorId() == RndStdIds::FLY_AT_PARA) + { + return pIter == nullptr // not merged + || pIter != pEnd // at least one char visible in node + || !IsSelectFrameAnchoredAtPara(rAnchor, + SwPosition(const_cast<SwTextNode&>(*pFirstNode), 0), + SwPosition(const_cast<SwTextNode&>(*pLastNode), pLastNode->Len())); + } + if (pIter) + { + // note: frames are not sorted by anchor position. + assert(pEnd); + assert(pFirstNode); + assert(pLastNode); + assert(rAnch.GetAnchorId() != RndStdIds::FLY_AT_FLY); + if (*pIter == *pEnd && rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { // tdf#149595 special case - it *could* be shown if first == last + return !IsDestroyFrameAnchoredAtChar(*rAnch.GetContentAnchor(), + SwPosition(const_cast<SwTextNode&>(*pFirstNode), 0), + SwPosition(const_cast<SwTextNode&>(*pLastNode), pLastNode->Len())); + } + for (auto iter = *pIter; iter != *pEnd; ++iter) + { + assert(iter->nStart != iter->nEnd); // TODO possible? + assert(iter->pNode->GetIndex() == nIndex); + if (rAnchor.nContent.GetIndex() < iter->nStart) + { + return false; + } + if (rAnch.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + // if there is an extent then obviously the node was not + // deleted fully... + // show if start <= pos <= end + // *or* if first-node/0 *and* not StartOfSection + // *or* if last-node/Len *and* not EndOfSection + + // first determine the extent to compare to, then + // construct start/end positions for the deletion *before* the + // extent and compare once. + // the interesting corner cases are on the edge of the extent! + // no need to check for > the last extent because those + // are never visible. + if (rAnchor.nContent.GetIndex() <= iter->nEnd) + { + if (iter->nStart == 0) + { + return true; + } + else + { + SwPosition const start( + const_cast<SwTextNode&>( + iter == *pIter + ? *pFirstNode // simplification + : *iter->pNode), + iter == *pIter // first extent? + ? iter->pNode == pFirstNode + ? 0 // at start of 1st node + : pFirstNode->Len() // previous node; simplification but should get right result + : (iter-1)->nEnd); // previous extent + SwPosition const end(*iter->pNode, iter->nStart); + return !IsDestroyFrameAnchoredAtChar(rAnchor, start, end); + } + } + else if (iter == *pEnd - 1) // special case: after last extent + { + if (iter->nEnd == iter->pNode->Len()) + { + return true; // special case: end of node + } + else + { + SwPosition const start(*iter->pNode, iter->nEnd); + SwPosition const end( + const_cast<SwTextNode&>(*pLastNode), // simplification + iter->pNode == pLastNode + ? iter->pNode->Len() + : 0); + return !IsDestroyFrameAnchoredAtChar(rAnchor, start, end); + } + } + } + else + { + assert(rAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR); + // for AS_CHAR obviously must be < + if (rAnchor.nContent.GetIndex() < iter->nEnd) + { + return true; + } + } + } + return false; + } + else + { + return true; + } +} + +void RemoveHiddenObjsOfNode(SwTextNode const& rNode, + std::vector<sw::Extent>::const_iterator const*const pIter, + std::vector<sw::Extent>::const_iterator const*const pEnd, + SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode) +{ + std::vector<SwFrameFormat*> const & rFlys(rNode.GetAnchoredFlys()); + for (SwFrameFormat * pFrameFormat : rFlys) + { + SwFormatAnchor const& rAnchor = pFrameFormat->GetAnchor(); + if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR + || rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR) + { + assert(rAnchor.GetContentAnchor()->nNode.GetIndex() == rNode.GetIndex()); + if (!IsShown(rNode.GetIndex(), rAnchor, pIter, pEnd, pFirstNode, pLastNode)) + { + pFrameFormat->DelFrames(); + } + } + } +} + +void AppendObjsOfNode(SwFrameFormats const*const pTable, SwNodeOffset const nIndex, + SwFrame *const pFrame, SwPageFrame *const pPage, SwDoc *const pDoc, + std::vector<sw::Extent>::const_iterator const*const pIter, + std::vector<sw::Extent>::const_iterator const*const pEnd, + SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode) +{ +#if OSL_DEBUG_LEVEL > 0 + std::vector<SwFrameFormat*> checkFormats; + for ( size_t i = 0; i < pTable->size(); ++i ) + { + SwFrameFormat *pFormat = (*pTable)[i]; + const SwFormatAnchor &rAnch = pFormat->GetAnchor(); + if ( rAnch.GetContentAnchor() && + IsShown(nIndex, rAnch, pIter, pEnd, pFirstNode, pLastNode)) + { + checkFormats.push_back( pFormat ); + } + } +#else + (void)pTable; +#endif + + SwNode const& rNode(*pDoc->GetNodes()[nIndex]); + std::vector<SwFrameFormat*> const & rFlys(rNode.GetAnchoredFlys()); + for (size_t it = 0; it != rFlys.size(); ) + { + SwFrameFormat *const pFormat = rFlys[it]; + const SwFormatAnchor &rAnch = pFormat->GetAnchor(); + if ( rAnch.GetContentAnchor() && + IsShown(nIndex, rAnch, pIter, pEnd, pFirstNode, pLastNode)) + { +#if OSL_DEBUG_LEVEL > 0 + std::vector<SwFrameFormat*>::iterator checkPos = std::find( checkFormats.begin(), checkFormats.end(), pFormat ); + assert( checkPos != checkFormats.end()); + checkFormats.erase( checkPos ); +#endif + AppendObj(pFrame, pPage, pFormat, rAnch); + } + ++it; + } + +#if OSL_DEBUG_LEVEL > 0 + assert( checkFormats.empty()); +#endif +} + + +void AppendObjs(const SwFrameFormats *const pTable, SwNodeOffset const nIndex, + SwFrame *const pFrame, SwPageFrame *const pPage, SwDoc *const pDoc) +{ + if (pFrame->IsTextFrame()) + { + SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(pFrame)); + if (sw::MergedPara const*const pMerged = pTextFrame->GetMergedPara()) + { + std::vector<sw::Extent>::const_iterator iterFirst(pMerged->extents.begin()); + std::vector<sw::Extent>::const_iterator iter(iterFirst); + SwTextNode const* pNode(pMerged->pFirstNode); + for ( ; ; ++iter) + { + if (iter == pMerged->extents.end() + || iter->pNode != pNode) + { + AppendObjsOfNode(pTable, pNode->GetIndex(), pFrame, pPage, pDoc, + &iterFirst, &iter, pMerged->pFirstNode, pMerged->pLastNode); + SwNodeOffset const until = iter == pMerged->extents.end() + ? pMerged->pLastNode->GetIndex() + 1 + : iter->pNode->GetIndex(); + for (SwNodeOffset i = pNode->GetIndex() + 1; i < until; ++i) + { + // let's show at-para flys on nodes that contain start/end of + // redline too, even if there's no text there + SwNode const*const pTmp(pNode->GetNodes()[i]); + if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst) + { + AppendObjsOfNode(pTable, pTmp->GetIndex(), pFrame, pPage, pDoc, &iter, &iter, pMerged->pFirstNode, pMerged->pLastNode); + } + } + if (iter == pMerged->extents.end()) + { + break; + } + pNode = iter->pNode; + iterFirst = iter; + } + } + } + else + { + return AppendObjsOfNode(pTable, nIndex, pFrame, pPage, pDoc, nullptr, nullptr, nullptr, nullptr); + } + } + else + { + return AppendObjsOfNode(pTable, nIndex, pFrame, pPage, pDoc, nullptr, nullptr, nullptr, nullptr); + } +} + +bool IsAnchoredObjShown(SwTextFrame const& rFrame, SwFormatAnchor const& rAnchor) +{ + assert(rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA || + rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR || + rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR); + bool ret(true); + if (auto const pMergedPara = rFrame.GetMergedPara()) + { + ret = false; + auto const pAnchor(rAnchor.GetContentAnchor()); + auto iterFirst(pMergedPara->extents.cbegin()); + if (iterFirst == pMergedPara->extents.end() + && (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA + || rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR)) + { + ret = (&pAnchor->nNode.GetNode() == pMergedPara->pFirstNode + && (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA + || pAnchor->nContent == 0)) + || (&pAnchor->nNode.GetNode() == pMergedPara->pLastNode + && (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA + || pAnchor->nContent == pMergedPara->pLastNode->Len())); + } + auto iter(iterFirst); + SwTextNode const* pNode(pMergedPara->pFirstNode); + for ( ; ; ++iter) + { + if (iter == pMergedPara->extents.end() + || iter->pNode != pNode) + { + assert(pNode->GetRedlineMergeFlag() != SwNode::Merge::Hidden); + if (pNode == &pAnchor->nNode.GetNode()) + { + ret = IsShown(pNode->GetIndex(), rAnchor, &iterFirst, &iter, + pMergedPara->pFirstNode, pMergedPara->pLastNode); + break; + } + if (iter == pMergedPara->extents.end()) + { + break; + } + pNode = iter->pNode; + if (pAnchor->nNode.GetIndex() < pNode->GetIndex()) + { + break; + } + iterFirst = iter; + } + } + } + return ret; +} + +void AppendAllObjs(const SwFrameFormats* pTable, const SwFrame* pSib) +{ + //Connecting of all Objects, which are described in the SpzTable with the + //layout. + + boost::circular_buffer<SwFrameFormat*> vFormatsToConnect(pTable->size()); + for(const auto& pFormat : *pTable) + { + const auto& rAnch = pFormat->GetAnchor(); + // Formats can still remain, because we neither use character bound + // frames nor objects which are anchored to character bounds. + if ((rAnch.GetAnchorId() != RndStdIds::FLY_AT_PAGE) && (rAnch.GetAnchorId() != RndStdIds::FLY_AS_CHAR)) + { + auto pContentAnchor = rAnch.GetContentAnchor(); + // formats in header/footer have no dependencies + if(pContentAnchor && pFormat->GetDoc()->IsInHeaderFooter(pContentAnchor->nNode)) + pFormat->MakeFrames(); + else + vFormatsToConnect.push_back(pFormat); + } + } + const SwRootFrame* pRoot = pSib ? pSib->getRootFrame() : nullptr; + const SwFrameFormat* pFirstRequeued(nullptr); + while(!vFormatsToConnect.empty()) + { + auto& pFormat = vFormatsToConnect.front(); + bool isConnected(false); + pFormat->CallSwClientNotify(sw::GetObjectConnectedHint(isConnected, pRoot)); + if(!isConnected) + { + pFormat->MakeFrames(); + pFormat->CallSwClientNotify(sw::GetObjectConnectedHint(isConnected, pRoot)); + } + // do this *before* push_back! the circular_buffer can be "full"! + vFormatsToConnect.pop_front(); + if (!isConnected) + { + if(pFirstRequeued == pFormat) + // If nothing happens anymore we can stop. + break; + if(!pFirstRequeued) + pFirstRequeued = pFormat; + assert(!vFormatsToConnect.full()); + vFormatsToConnect.push_back(pFormat); + } + else + { + pFirstRequeued = nullptr; + } + } +} + +namespace sw { + +void RecreateStartTextFrames(SwTextNode & rNode) +{ + std::vector<SwTextFrame*> frames; + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rNode); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->HasMergedParas()) + { + frames.push_back(pFrame); + } + } + auto eMode(sw::FrameMode::Existing); + for (SwTextFrame * pFrame : frames) + { + // SplitNode could have moved the original frame to the start node + // & created a new one on end, or could have created new frame on + // start node... grab start node's frame and recreate MergedPara. + SwTextNode & rFirstNode(pFrame->GetMergedPara() + ? *pFrame->GetMergedPara()->pFirstNode + : rNode); + assert(rFirstNode.GetIndex() <= rNode.GetIndex()); + // clear old one first to avoid DelFrames confusing updates & asserts... + pFrame->SetMergedPara(nullptr); + pFrame->SetMergedPara(sw::CheckParaRedlineMerge( + *pFrame, rFirstNode, eMode)); + eMode = sw::FrameMode::New; // Existing is not idempotent! + // note: this may or may not delete frames on the end node + } +} + +} // namespace sw + +/** local method to set 'working' position for newly inserted frames + + OD 12.08.2003 #i17969# +*/ +static void lcl_SetPos( SwFrame& _rNewFrame, + const SwLayoutFrame& _rLayFrame ) +{ + SwRectFnSet aRectFnSet(&_rLayFrame); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(_rNewFrame); + aRectFnSet.SetPos( aFrm, aRectFnSet.GetPos(_rLayFrame.getFrameArea()) ); + + // move position by one SwTwip in text flow direction in order to get + // notifications for a new calculated position after its formatting. + if ( aRectFnSet.IsVert() ) + { + aFrm.Pos().AdjustX( -1 ); + } + else + { + aFrm.Pos().AdjustY(1 ); + } +} + +void InsertCnt_( SwLayoutFrame *pLay, SwDoc *pDoc, + SwNodeOffset nIndex, bool bPages, SwNodeOffset nEndIndex, + SwFrame *pPrv, sw::FrameMode const eMode ) +{ + pDoc->getIDocumentTimerAccess().BlockIdling(); + SwRootFrame* pLayout = pLay->getRootFrame(); + const bool bOldCallbackActionEnabled = pLayout && pLayout->IsCallbackActionEnabled(); + if( bOldCallbackActionEnabled ) + pLayout->SetCallbackActionEnabled( false ); + + //In the generation of the Layout bPages=true will be handed over. + //Then will be new pages generated all x paragraphs already times in advance. + //On breaks and/or pagedescriptorchanges the corresponding will be generated + //immediately. + //The advantage is, that on one hand already a nearly realistic number of + //pages are created, but above all there are no almost endless long chain + //of paragraphs, which must be moved expensively until it reaches a tolerable + //reduced level. + //We'd like to think that 20 Paragraphs fit on one page. + //So that it does not become in extreme situations so violent we calculate depending + //on the node something to it. + //If in the DocStatistic a usable given pagenumber + //(Will be cared for while writing), so it will be presumed that this will be + //number of pages. + const bool bStartPercent = bPages && !nEndIndex; + + SwPageFrame *pPage = pLay->FindPageFrame(); + const SwFrameFormats *pTable = pDoc->GetSpzFrameFormats(); + SwFrame *pFrame = nullptr; + std::unique_ptr<SwActualSection> pActualSection; + std::unique_ptr<SwLayHelper> pPageMaker; + + //If the layout will be created (bPages == true) we do head on the progress + //Flys and DrawObjects are not connected immediately, this + //happens only at the end of the function. + if ( bPages ) + { + // Attention: the SwLayHelper class uses references to the content-, + // page-, layout-frame etc. and may change them! + pPageMaker.reset(new SwLayHelper( pDoc, pFrame, pPrv, pPage, pLay, + pActualSection, nIndex, SwNodeOffset(0) == nEndIndex )); + if( bStartPercent ) + { + const sal_uLong nPageCount = pPageMaker->CalcPageCount(); + if( nPageCount ) + bObjsDirect = false; + } + } + else + pPageMaker = nullptr; + + if( pLay->IsInSct() && + ( pLay->IsSctFrame() || pLay->GetUpper() ) ) // Hereby will newbies + // be intercepted, of which flags could not determined yet, + // for e.g. while inserting a table + { + SwSectionFrame* pSct = pLay->FindSctFrame(); + // If content will be inserted in a footnote, which in a column area, + // the column area it is not allowed to be broken up. + // Only if in the inner of the footnote lies an area, is this a candidate + // for pActualSection. + // The same applies for areas in tables, if inside the table will be + // something inserted, it's only allowed to break up areas, which + // lies in the inside also. + if( ( !pLay->IsInFootnote() || pSct->IsInFootnote() ) && + ( !pLay->IsInTab() || pSct->IsInTab() ) ) + { + pActualSection.reset(new SwActualSection(nullptr, pSct, pSct->GetSection()->GetFormat()->GetSectionNode())); + // tdf#132236 for SwUndoDelete: find outer sections whose start + // nodes aren't contained in the range but whose end nodes are, + // because section frames may need to be created for them + SwActualSection * pUpperSection(pActualSection.get()); + while (pUpperSection->GetSectionNode()->EndOfSectionIndex() < nEndIndex) + { + SwStartNode *const pStart(pUpperSection->GetSectionNode()->StartOfSectionNode()); + if (!pStart->IsSectionNode()) + { + break; + } + // note: these don't have a section frame, check it in EndNode case! + auto const pTmp(new SwActualSection(nullptr, nullptr, static_cast<SwSectionNode*>(pStart))); + pUpperSection->SetUpper(pTmp); + pUpperSection = pTmp; + } + OSL_ENSURE( !pLay->Lower() || !pLay->Lower()->IsColumnFrame(), + "InsertCnt_: Wrong Call" ); + } + } + + //If a section is "open", the pActualSection points to an SwActualSection. + //If the page breaks, for "open" sections a follow will created. + //For nested sections (which have, however, not a nested layout), + //the SwActualSection class has a member, which points to an upper(section). + //When the "inner" section finishes, the upper will used instead. + + // Do not consider the end node. The caller (Section/MakeFrames()) has to + // ensure that the end of this range is positioned before EndIndex! + for ( ; nEndIndex == SwNodeOffset(0) || nIndex < nEndIndex; ++nIndex) + { + SwNode *pNd = pDoc->GetNodes()[nIndex]; + if ( pNd->IsContentNode() ) + { + SwContentNode* pNode = static_cast<SwContentNode*>(pNd); + if (pLayout->HasMergedParas() && !pNd->IsCreateFrameWhenHidingRedlines()) + { + if (pNd->IsTextNode() + && pNd->GetRedlineMergeFlag() == SwNode::Merge::NonFirst) + { // must have a frame already + assert(static_cast<SwTextFrame*>(pNode->getLayoutFrame(pLayout))->GetMergedPara()); + } + continue; // skip it + } + pFrame = pNode->IsTextNode() + ? sw::MakeTextFrame(*pNode->GetTextNode(), pLay, eMode) + : pNode->MakeFrame(pLay); + if( pPageMaker ) + pPageMaker->CheckInsert( nIndex ); + + pFrame->InsertBehind( pLay, pPrv ); + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if ( pFrame->IsTextFrame() ) + { + SwViewShell* pViewShell( pFrame->getRootFrame()->GetCurrShell() ); + // no notification, if <SwViewShell> is in construction + if ( pViewShell && !pViewShell->IsInConstructor() && + pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() && + pFrame->FindPageFrame() != nullptr) + { + auto pNext = pFrame->FindNextCnt( true ); + auto pPrev = pFrame->FindPrevCnt(); + pViewShell->InvalidateAccessibleParaFlowRelation( + pNext ? pNext->DynCastTextFrame() : nullptr, + pPrev ? pPrev->DynCastTextFrame() : nullptr ); + // #i68958# + // The information flags of the text frame are validated + // in methods <FindNextCnt(..)> and <FindPrevCnt(..)>. + // The information flags have to be invalidated, because + // it is possible, that the one of its upper frames + // isn't inserted into the layout. + pFrame->InvalidateInfFlags(); + } + } +#endif + // OD 12.08.2003 #i17969# - consider horizontal/vertical layout + // for setting position at newly inserted frame + lcl_SetPos( *pFrame, *pLay ); + pPrv = pFrame; + + if ( !pTable->empty() && bObjsDirect && !isFlyCreationSuppressed ) + AppendObjs( pTable, nIndex, pFrame, pPage, pDoc ); + } + else if ( pNd->IsTableNode() ) + { //Should we have encountered a table? + SwTableNode *pTableNode = static_cast<SwTableNode*>(pNd); + if (pLayout->IsHideRedlines()) + { + // in the problematic case, there can be only 1 redline... + SwPosition const tmp(*pNd); + SwRangeRedline const*const pRedline( + pDoc->getIDocumentRedlineAccess().GetRedline(tmp, nullptr)); + // pathology: redline that starts on a TableNode; cannot + // be created in UI but by import filters... + if (pRedline + && pRedline->GetType() == RedlineType::Delete + && &pRedline->Start()->nNode.GetNode() == pNd) + { + SAL_WARN("sw.pageframe", "skipping table frame creation on bizarre redline"); + while (true) + { + pTableNode->GetNodes()[nIndex]->SetRedlineMergeFlag(SwNode::Merge::Hidden); + if (nIndex == pTableNode->EndOfSectionIndex()) + { + break; + } + ++nIndex; + } + continue; + } + } + if (pLayout->HasMergedParas() && !pNd->IsCreateFrameWhenHidingRedlines()) + { + assert(pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden); + nIndex = pTableNode->EndOfSectionIndex(); + continue; // skip it + } + + pFrame = pTableNode->MakeFrame( pLay ); + + // skip tables deleted with track changes + if ( !static_cast<SwTabFrame*>(pFrame)->Lower() ) + { + nIndex = pTableNode->EndOfSectionIndex(); + continue; // skip it + } + + // #108116# loading may produce table structures that GCLines + // needs to clean up. To keep table formulas correct, change + // all table formulas to internal (BOXPTR) representation. + SwTableFormulaUpdate aMsgHint( &pTableNode->GetTable() ); + aMsgHint.m_eFlags = TBL_BOXPTR; + pDoc->getIDocumentFieldsAccess().UpdateTableFields( &aMsgHint ); + pTableNode->GetTable().GCLines(); + + if( pPageMaker ) + pPageMaker->CheckInsert( nIndex ); + + pFrame->InsertBehind( pLay, pPrv ); + if (pPage) // would null in SwCellFrame ctor + { // tdf#134931 call ResetTurbo(); not sure if Paste() would be + pFrame->InvalidatePage(pPage); // better than InsertBehind()? + } + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + { + SwViewShell* pViewShell( pFrame->getRootFrame()->GetCurrShell() ); + // no notification, if <SwViewShell> is in construction + if ( pViewShell && !pViewShell->IsInConstructor() && + pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() && + pFrame->FindPageFrame() != nullptr) + { + auto pNext = pFrame->FindNextCnt( true ); + auto pPrev = pFrame->FindPrevCnt(); + pViewShell->InvalidateAccessibleParaFlowRelation( + pNext ? pNext->DynCastTextFrame() : nullptr, + pPrev ? pPrev->DynCastTextFrame() : nullptr ); + } + } +#endif + if ( bObjsDirect && !pTable->empty() ) + static_cast<SwTabFrame*>(pFrame)->RegistFlys(); + // OD 12.08.2003 #i17969# - consider horizontal/vertical layout + // for setting position at newly inserted frame + lcl_SetPos( *pFrame, *pLay ); + + pPrv = pFrame; + //Set the index to the endnode of the table section. + nIndex = pTableNode->EndOfSectionIndex(); + + SwTabFrame* pTmpFrame = static_cast<SwTabFrame*>(pFrame); + while ( pTmpFrame ) + { + pTmpFrame->CheckDirChange(); + pTmpFrame = pTmpFrame->IsFollow() ? pTmpFrame->FindMaster() : nullptr; + } + + } + else if ( pNd->IsSectionNode() ) + { + if (pLayout->HasMergedParas() && !pNd->IsCreateFrameWhenHidingRedlines()) + { + assert(pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden); + continue; // skip it + } + SwSectionNode *pNode = static_cast<SwSectionNode*>(pNd); + if( pNode->GetSection().CalcHiddenFlag() ) + // is hidden, skip the area + nIndex = pNode->EndOfSectionIndex(); + else + { + pFrame = pNode->MakeFrame( pLay ); + pActualSection.reset( new SwActualSection( pActualSection.release(), + static_cast<SwSectionFrame*>(pFrame), pNode ) ); + if ( pActualSection->GetUpper() ) + { + //Insert behind the Upper, the "Follow" of the Upper will be + //generated at the EndNode. + SwSectionFrame *pTmp = pActualSection->GetUpper()->GetSectionFrame(); + pFrame->InsertBehind( pTmp->GetUpper(), pTmp ); + // OD 25.03.2003 #108339# - direct initialization of section + // after insertion in the layout + static_cast<SwSectionFrame*>(pFrame)->Init(); + } + else + { + pFrame->InsertBehind( pLay, pPrv ); + // OD 25.03.2003 #108339# - direct initialization of section + // after insertion in the layout + static_cast<SwSectionFrame*>(pFrame)->Init(); + + // #i33963# + // Do not trust the IsInFootnote flag. If we are currently + // building up a table, the upper of pPrv may be a cell + // frame, but the cell frame does not have an upper yet. + if( pPrv && nullptr != pPrv->ImplFindFootnoteFrame() ) + { + if( pPrv->IsSctFrame() ) + pPrv = static_cast<SwSectionFrame*>(pPrv)->ContainsContent(); + if( pPrv && pPrv->IsTextFrame() ) + static_cast<SwTextFrame*>(pPrv)->Prepare( PrepareHint::QuoVadis, nullptr, false ); + } + } + + if (nIndex + 1 == nEndIndex + // tdf#136452 may also be needed at end of section + || pNode->EndOfSectionIndex() - 1 == nEndIndex) + { // tdf#131684 tdf#132236 fix upper of frame moved in + // SwUndoDelete; can't be done there unfortunately + // because empty section frames are deleted here + SwFrame *const pNext( + // if there's a parent section, it has been split + // into 2 SwSectionFrame already :( + ( pFrame->GetNext() + && pFrame->GetNext()->IsSctFrame() + && pActualSection->GetUpper() + && pActualSection->GetUpper()->GetSectionNode() == + static_cast<SwSectionFrame const*>(pFrame->GetNext())->GetSection()->GetFormat()->GetSectionNode()) + ? static_cast<SwSectionFrame *>(pFrame->GetNext())->ContainsContent() + : pFrame->GetNext()); + if (pNext + && pNext->IsTextFrame() + && static_cast<SwTextFrame*>(pNext)->GetTextNodeFirst() == pDoc->GetNodes()[nEndIndex] + && (pNext->GetUpper() == pFrame->GetUpper() + || pFrame->GetNext()->IsSctFrame())) // checked above + { + pNext->Cut(); + pNext->InvalidateInfFlags(); // mbInfSct changed + // could have columns + SwSectionFrame *const pSection(static_cast<SwSectionFrame*>(pFrame)); + assert(!pSection->Lower() || pSection->Lower()->IsLayoutFrame()); + SwLayoutFrame *const pParent(pSection->Lower() ? pSection->GetNextLayoutLeaf() : pSection); + assert(!pParent->Lower()); + // paste invalidates, section could have indent... + pNext->Paste(pParent, nullptr); + } + } + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for next paragraph will change + // and relation CONTENT_FLOWS_TO for previous paragraph will change. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + { + SwViewShell* pViewShell( pFrame->getRootFrame()->GetCurrShell() ); + // no notification, if <SwViewShell> is in construction + if ( pViewShell && !pViewShell->IsInConstructor() && + pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() && + pFrame->FindPageFrame() != nullptr) + { + auto pNext = pFrame->FindNextCnt( true ); + auto pPrev = pFrame->FindPrevCnt(); + pViewShell->InvalidateAccessibleParaFlowRelation( + pNext ? pNext->DynCastTextFrame() : nullptr, + pPrev ? pPrev->DynCastTextFrame() : nullptr ); + } + } +#endif + pFrame->CheckDirChange(); + + // OD 12.08.2003 #i17969# - consider horizontal/vertical layout + // for setting position at newly inserted frame + lcl_SetPos( *pFrame, *pLay ); + + // OD 20.11.2002 #105405# - no page, no invalidate. + if ( pPage ) + { + // OD 18.09.2002 #100522# + // invalidate page in order to force format and paint of + // inserted section frame + pFrame->InvalidatePage( pPage ); + + // FME 10.11.2003 #112243# + // Invalidate fly content flag: + if ( pFrame->IsInFly() ) + pPage->InvalidateFlyContent(); + + // OD 14.11.2002 #104684# - invalidate page content in order to + // force format and paint of section content. + pPage->InvalidateContent(); + } + + pLay = static_cast<SwLayoutFrame*>(pFrame); + if ( pLay->Lower() && pLay->Lower()->IsLayoutFrame() ) + pLay = pLay->GetNextLayoutLeaf(); + pPrv = nullptr; + } + } + else if ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsSectionNode() ) + { + if (pLayout->HasMergedParas() && !pNd->IsCreateFrameWhenHidingRedlines()) + { + assert(pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden); + continue; // skip it + } + if (pLayout->HasMergedParas() && !pNd->StartOfSectionNode()->IsCreateFrameWhenHidingRedlines()) + { // tdf#135014 section break in fieldmark (start inside, end outside) + assert(pNd->StartOfSectionNode()->GetRedlineMergeFlag() == SwNode::Merge::Hidden); + continue; // skip it + } + assert(pActualSection && "Section end without section start?"); + assert(pActualSection->GetSectionNode() == pNd->StartOfSectionNode()); + + //Close the section, where appropriate activate the surrounding + //section again. + pActualSection.reset(pActualSection->GetUpper()); + pLay = pLay->FindSctFrame(); + if ( pActualSection ) + { + //Could be, that the last SectionFrame remains empty. + //Then now is the time to remove them. + if ( !pLay->ContainsContent() ) + { + SwFrame *pTmpFrame = pLay; + pLay = pTmpFrame->GetUpper(); + pPrv = pTmpFrame->GetPrev(); + pTmpFrame->RemoveFromLayout(); + SwFrame::DestroyFrame(pTmpFrame); + } + else + { + pPrv = pLay; + pLay = pLay->GetUpper(); + } + + // new section frame + pFrame = pActualSection->GetSectionNode()->MakeFrame( pLay ); + pFrame->InsertBehind( pLay, pPrv ); + static_cast<SwSectionFrame*>(pFrame)->Init(); + + // OD 12.08.2003 #i17969# - consider horizontal/vertical layout + // for setting position at newly inserted frame + lcl_SetPos( *pFrame, *pLay ); + + SwSectionFrame* pOuterSectionFrame = pActualSection->GetSectionFrame(); + + // a follow has to be appended to the new section frame + SwSectionFrame* pFollow = pOuterSectionFrame ? pOuterSectionFrame->GetFollow() : nullptr; + if ( pFollow ) + { + pOuterSectionFrame->SetFollow( nullptr ); + pOuterSectionFrame->InvalidateSize(); + static_cast<SwSectionFrame*>(pFrame)->SetFollow( pFollow ); + } + + // We don't want to leave empty parts back. + if (pOuterSectionFrame && + ! pOuterSectionFrame->IsColLocked() && + ! pOuterSectionFrame->ContainsContent() ) + { + pOuterSectionFrame->DelEmpty( true ); + SwFrame::DestroyFrame(pOuterSectionFrame); + } + pActualSection->SetSectionFrame( static_cast<SwSectionFrame*>(pFrame) ); + + pLay = static_cast<SwLayoutFrame*>(pFrame); + if ( pLay->Lower() && pLay->Lower()->IsLayoutFrame() ) + pLay = pLay->GetNextLayoutLeaf(); + pPrv = nullptr; + } + else + { + //Nothing more with sections, it goes on right behind + //the SectionFrame. + pPrv = pLay; + pLay = pLay->GetUpper(); + } + } + else if( pNd->IsStartNode() && + SwFlyStartNode == static_cast<SwStartNode*>(pNd)->GetStartNodeType() ) + { + if (pLayout->HasMergedParas() && !pNd->IsCreateFrameWhenHidingRedlines()) + { + assert(pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden); + assert(false); // actually a fly-section can't be deleted? + continue; // skip it + } + if ( !pTable->empty() && bObjsDirect && !isFlyCreationSuppressed ) + { + SwFlyFrame* pFly = pLay->FindFlyFrame(); + if( pFly ) + AppendObjs( pTable, nIndex, pFly, pPage, pDoc ); + } + } + else + { + assert(!pLayout->HasMergedParas() + || pNd->GetRedlineMergeFlag() != SwNode::Merge::Hidden); + // Neither Content nor table nor section, so we are done. + break; + } + } + + if ( pActualSection ) + { + // Might happen that an empty (Follow-)Section is left over. + if ( !(pLay = pActualSection->GetSectionFrame())->ContainsContent() ) + { + pLay->RemoveFromLayout(); + SwFrame::DestroyFrame(pLay); + } + pActualSection.reset(); + } + + if ( bPages ) // let the Flys connect to each other + { + if ( !isFlyCreationSuppressed ) + AppendAllObjs( pTable, pLayout ); + bObjsDirect = true; + } + + if( pPageMaker ) + { + pPageMaker->CheckFlyCache( pPage ); + pPageMaker.reset(); + if( pDoc->GetLayoutCache() ) + { +#ifdef DBG_UTIL + pDoc->GetLayoutCache()->CompareLayout( *pDoc ); +#endif + pDoc->GetLayoutCache()->ClearImpl(); + } + } + + pDoc->getIDocumentTimerAccess().UnblockIdling(); + if( bOldCallbackActionEnabled ) + pLayout->SetCallbackActionEnabled( bOldCallbackActionEnabled ); +} + +void MakeFrames( SwDoc *pDoc, const SwNodeIndex &rSttIdx, + const SwNodeIndex &rEndIdx ) +{ + bObjsDirect = false; + + SwNodeIndex aTmp( rSttIdx ); + SwNodeOffset nEndIdx = rEndIdx.GetIndex(); + // TODO for multiple layouts there should be a loop here + SwNode* pNd = pDoc->GetNodes().FindPrvNxtFrameNode( aTmp, + pDoc->GetNodes()[ nEndIdx-1 ], + pDoc->getIDocumentLayoutAccess().GetCurrentLayout()); + if ( pNd ) + { + bool bApres = aTmp < rSttIdx; + SwNode2Layout aNode2Layout( *pNd, rSttIdx.GetIndex() ); + sw::FrameMode eMode = sw::FrameMode::Existing; + ::std::vector<SwFrame*> frames; + while (SwFrame* pFrame = aNode2Layout.NextFrame()) + { // tdf#150500 new frames may be created that end up merged on pNd + // so copy the currently existing ones; they shouldn't get deleted + frames.push_back(pFrame); + } + for (SwFrame *const pFrame : frames) + { + SwLayoutFrame *pUpper = pFrame->GetUpper(); + SwFootnoteFrame* pFootnoteFrame = pUpper->FindFootnoteFrame(); + bool bOldLock, bOldFootnote; + if( pFootnoteFrame ) + { + bOldFootnote = pFootnoteFrame->IsColLocked(); + pFootnoteFrame->ColLock(); + } + else + bOldFootnote = true; + SwSectionFrame* pSct = pUpper->FindSctFrame(); + // Inside of footnotes only those areas are interesting that are inside of them. But + // not the ones (e.g. column areas) in which are the footnote containers positioned. + // #109767# Table frame is in section, insert section in cell frame. + if( pSct && ((pFootnoteFrame && !pSct->IsInFootnote()) || pUpper->IsCellFrame()) ) + pSct = nullptr; + if( pSct ) + { // to prevent pTmp->MoveFwd from destroying the SectionFrame + bOldLock = pSct->IsColLocked(); + pSct->ColLock(); + } + else + bOldLock = true; + + // If pFrame cannot be moved, it is not possible to move it to the next page. This applies + // also for frames (in the first column of a frame pFrame is moveable) and column + // sections of tables (also here pFrame is moveable). + bool bMoveNext = nEndIdx - rSttIdx.GetIndex() > SwNodeOffset(120); + bool bAllowMove = !pFrame->IsInFly() && pFrame->IsMoveable() && + (!pFrame->IsInTab() || pFrame->IsTabFrame() ); + if ( bMoveNext && bAllowMove ) + { + SwFrame *pMove = pFrame; + SwFrame *pPrev = pFrame->GetPrev(); + SwFlowFrame *pTmp = SwFlowFrame::CastFlowFrame( pMove ); + assert(pTmp); + + if ( bApres ) + { + // The rest of this page should be empty. Thus, the following one has to move to + // the next page (it might also be located in the following column). + assert(!pTmp->HasFollow() && "prev. node's frame is not last"); + pPrev = pFrame; + // If the surrounding SectionFrame has a "next" one, + // so this one needs to be moved as well. + pMove = pFrame->GetIndNext(); + SwColumnFrame* pCol = static_cast<SwColumnFrame*>(pFrame->FindColFrame()); + if( pCol ) + pCol = static_cast<SwColumnFrame*>(pCol->GetNext()); + do + { + if( pCol && !pMove ) + { // No successor so far, look into the next column + pMove = pCol->ContainsAny(); + if( pCol->GetNext() ) + pCol = static_cast<SwColumnFrame*>(pCol->GetNext()); + else if( pCol->IsInSct() ) + { // If there is no following column but we are in a column frame, + // there might be (page) columns outside of it. + pCol = static_cast<SwColumnFrame*>(pCol->FindSctFrame()->FindColFrame()); + if( pCol ) + pCol = static_cast<SwColumnFrame*>(pCol->GetNext()); + } + else + pCol = nullptr; + } + // skip invalid SectionFrames + while( pMove && pMove->IsSctFrame() && + !static_cast<SwSectionFrame*>(pMove)->GetSection() ) + pMove = pMove->GetNext(); + } while( !pMove && pCol ); + + if( pMove ) + { + if ( pMove->IsContentFrame() ) + pTmp = static_cast<SwContentFrame*>(pMove); + else if ( pMove->IsTabFrame() ) + pTmp = static_cast<SwTabFrame*>(pMove); + else if ( pMove->IsSctFrame() ) + { + pMove = static_cast<SwSectionFrame*>(pMove)->ContainsAny(); + if( pMove ) + pTmp = SwFlowFrame::CastFlowFrame( pMove ); + else + pTmp = nullptr; + } + } + else + pTmp = nullptr; + } + else + { + assert(!pTmp->IsFollow() && "next node's frame is not master"); + // move the _content_ of a section frame + if( pMove->IsSctFrame() ) + { + while( pMove && pMove->IsSctFrame() && + !static_cast<SwSectionFrame*>(pMove)->GetSection() ) + pMove = pMove->GetNext(); + if( pMove && pMove->IsSctFrame() ) + pMove = static_cast<SwSectionFrame*>(pMove)->ContainsAny(); + if( pMove ) + pTmp = SwFlowFrame::CastFlowFrame( pMove ); + else + pTmp = nullptr; + } + } + + if( pTmp ) + { + SwFrame* pOldUp = pTmp->GetFrame().GetUpper(); + // MoveFwd==true means that we are still on the same page. + // But since we want to move if possible! + bool bTmpOldLock = pTmp->IsJoinLocked(); + pTmp->LockJoin(); + while( pTmp->MoveFwd( true, false, true ) ) + { + if( pOldUp == pTmp->GetFrame().GetUpper() ) + break; + pOldUp = pTmp->GetFrame().GetUpper(); + } + if( !bTmpOldLock ) + pTmp->UnlockJoin(); + } + ::InsertCnt_( pUpper, pDoc, rSttIdx.GetIndex(), + pFrame->IsInDocBody(), nEndIdx, pPrev, eMode ); + } + else + { + bool bSplit; + SwFrame* pPrv = bApres ? pFrame : pFrame->GetPrev(); + // If the section frame is inserted into another one, it must be split. + if( pSct && rSttIdx.GetNode().IsSectionNode() ) + { + bSplit = pSct->SplitSect( pFrame, bApres ); + if( !bSplit && !bApres ) + { + pUpper = pSct->GetUpper(); + pPrv = pSct->GetPrev(); + } + } + else + bSplit = false; + + ::InsertCnt_( pUpper, pDoc, rSttIdx.GetIndex(), false, + nEndIdx, pPrv, eMode ); + // OD 23.06.2003 #108784# - correction: append objects doesn't + // depend on value of <bAllowMove> + if( !isFlyCreationSuppressed ) + { + const SwFrameFormats *pTable = pDoc->GetSpzFrameFormats(); + if( !pTable->empty() ) + AppendAllObjs( pTable, pUpper ); + } + + // If nothing was added (e.g. a hidden section), the split must be reversed. + if( bSplit && pSct && pSct->GetNext() + && pSct->GetNext()->IsSctFrame() ) + pSct->MergeNext( static_cast<SwSectionFrame*>(pSct->GetNext()) ); + if( pFrame->IsInFly() ) + pFrame->FindFlyFrame()->Invalidate_(); + if( pFrame->IsInTab() ) + pFrame->InvalidateSize(); + } + + SwPageFrame *pPage = pUpper->FindPageFrame(); + SwFrame::CheckPageDescs( pPage, false ); + if( !bOldFootnote ) + pFootnoteFrame->ColUnlock(); + if( !bOldLock ) + { + pSct->ColUnlock(); + // pSct might be empty (e.g. when inserting linked section containing further + // sections) and can be destroyed in such cases. + if( !pSct->ContainsContent() ) + { + pSct->DelEmpty( true ); + pUpper->getRootFrame()->RemoveFromList( pSct ); + SwFrame::DestroyFrame(pSct); + } + } + eMode = sw::FrameMode::New; // use Existing only once! + } + } + + bObjsDirect = true; +} + +SwBorderAttrs::SwBorderAttrs(const sw::BorderCacheOwner* pOwner, const SwFrame* pConstructor) + : SwCacheObj(pOwner) + , m_rAttrSet(pConstructor->IsContentFrame() + ? pConstructor->IsTextFrame() + ? static_cast<const SwTextFrame*>(pConstructor)->GetTextNodeForParaProps()->GetSwAttrSet() + : static_cast<const SwNoTextFrame*>(pConstructor)->GetNode()->GetSwAttrSet() + : static_cast<const SwLayoutFrame*>(pConstructor)->GetFormat()->GetAttrSet()) + , m_rUL(m_rAttrSet.GetULSpace()) + // #i96772# + // LRSpaceItem is copied due to the possibility that it is adjusted - see below + , m_rLR(m_rAttrSet.GetLRSpace().Clone()) + , m_rBox(m_rAttrSet.GetBox()) + , m_rShadow(m_rAttrSet.GetShadow()) + , m_aFrameSize(m_rAttrSet.GetFrameSize().GetSize()) + , m_bIsLine(false) + , m_bJoinedWithPrev(false) + , m_bJoinedWithNext(false) + , m_nTopLine(0) + , m_nBottomLine(0) + , m_nLeftLine(0) + , m_nRightLine(0) + , m_nTop(0) + , m_nBottom(0) + , m_nGetTopLine(0) + , m_nGetBottomLine(0) + , m_nLineSpacing(0) +{ + // #i96772# + const SwTextFrame* pTextFrame = pConstructor->DynCastTextFrame(); + if ( pTextFrame ) + { + pTextFrame->GetTextNodeForParaProps()->ClearLRSpaceItemDueToListLevelIndents( m_rLR ); + } + else if ( pConstructor->IsNoTextFrame() ) + { + m_rLR = std::make_shared<SvxLRSpaceItem>(RES_LR_SPACE); + } + + // Caution: The USHORTs for the cached values are not initialized by intention! + + // everything needs to be calculated at least once: + m_bTopLine = m_bBottomLine = m_bLeftLine = m_bRightLine = + m_bTop = m_bBottom = m_bLine = true; + + // except this one: calculate line spacing before cell border only for text frames + m_bLineSpacing = bool(pTextFrame); + + m_bCacheGetLine = m_bCachedGetTopLine = m_bCachedGetBottomLine = false; + // OD 21.05.2003 #108789# - init cache status for values <m_bJoinedWithPrev> + // and <m_bJoinedWithNext>, which aren't initialized by default. + m_bCachedJoinedWithPrev = false; + m_bCachedJoinedWithNext = false; +} + +SwBorderAttrs::~SwBorderAttrs() +{ + const_cast<sw::BorderCacheOwner*>(static_cast<sw::BorderCacheOwner const *>(m_pOwner))->m_bInCache = false; +} + +/* All calc methods calculate a safety distance in addition to the values given by the attributes. + * This safety distance is only added when working with borders and/or shadows to prevent that + * e.g. borders are painted over. + */ + +void SwBorderAttrs::CalcTop_() +{ + m_nTop = CalcTopLine() + m_rUL.GetUpper(); + + if (m_rLR) + { + bool bGutterAtTop = m_rAttrSet.GetDoc()->getIDocumentSettingAccess().get( + DocumentSettingId::GUTTER_AT_TOP); + if (bGutterAtTop) + { + // Decrease the print area: the top space is the sum of top and gutter margins. + m_nTop += m_rLR->GetGutterMargin(); + } + } + + m_bTop = false; +} + +void SwBorderAttrs::CalcBottom_() +{ + m_nBottom = CalcBottomLine() + m_rUL.GetLower(); + m_bBottom = false; +} + +tools::Long SwBorderAttrs::CalcRight( const SwFrame* pCaller ) const +{ + tools::Long nRight=0; + + if (!pCaller->IsTextFrame() || !static_cast<const SwTextFrame*>(pCaller)->GetDoc().GetDocumentSettingManager().get(DocumentSettingId::INVERT_BORDER_SPACING)) { + // OD 23.01.2003 #106895# - for cell frame in R2L text direction the left + // and right border are painted on the right respectively left. + if ( pCaller->IsCellFrame() && pCaller->IsRightToLeft() ) + nRight = CalcLeftLine(); + else + nRight = CalcRightLine(); + + } + // for paragraphs, "left" is "before text" and "right" is "after text" + if ( pCaller->IsTextFrame() && pCaller->IsRightToLeft() ) + nRight += m_rLR->GetLeft(); + else + nRight += m_rLR->GetRight(); + + // correction: retrieve left margin for numbering in R2L-layout + if ( pCaller->IsTextFrame() && pCaller->IsRightToLeft() ) + { + nRight += static_cast<const SwTextFrame*>(pCaller)->GetTextNodeForParaProps()->GetLeftMarginWithNum(); + } + + if (pCaller->IsPageFrame() && m_rLR) + { + const auto pPageFrame = static_cast<const SwPageFrame*>(pCaller); + bool bGutterAtTop = pPageFrame->GetFormat()->getIDocumentSettingAccess().get( + DocumentSettingId::GUTTER_AT_TOP); + if (!bGutterAtTop) + { + bool bRtlGutter = pPageFrame->GetAttrSet()->GetItem<SfxBoolItem>(RES_RTL_GUTTER)->GetValue(); + tools::Long nGutterMargin = bRtlGutter ? m_rLR->GetGutterMargin() : m_rLR->GetRightGutterMargin(); + // Decrease the print area: the right space is the sum of right and right gutter + // margins. + nRight += nGutterMargin; + } + } + + return nRight; +} + +/// Tries to detect if this paragraph has a floating table attached. +static bool lcl_hasTabFrame(const SwTextFrame* pTextFrame) +{ + if (pTextFrame->GetDrawObjs()) + { + const SwSortedObjs* pSortedObjs = pTextFrame->GetDrawObjs(); + if (pSortedObjs->size() > 0) + { + SwAnchoredObject* pObject = (*pSortedObjs)[0]; + if (auto pFly = pObject->DynCastFlyFrame()) + { + if (pFly->Lower() && pFly->Lower()->IsTabFrame()) + return true; + } + } + } + return false; +} + +tools::Long SwBorderAttrs::CalcLeft( const SwFrame *pCaller ) const +{ + tools::Long nLeft=0; + + if (!pCaller->IsTextFrame() || !static_cast<const SwTextFrame*>(pCaller)->GetDoc().GetDocumentSettingManager().get(DocumentSettingId::INVERT_BORDER_SPACING)) + { + // OD 23.01.2003 #106895# - for cell frame in R2L text direction the left + // and right border are painted on the right respectively left. + if ( pCaller->IsCellFrame() && pCaller->IsRightToLeft() ) + nLeft = CalcRightLine(); + else + nLeft = CalcLeftLine(); + } + + // for paragraphs, "left" is "before text" and "right" is "after text" + if ( pCaller->IsTextFrame() && pCaller->IsRightToLeft() ) + nLeft += m_rLR->GetRight(); + else + { + bool bIgnoreMargin = false; + if (pCaller->IsTextFrame()) + { + const SwTextFrame* pTextFrame = static_cast<const SwTextFrame*>(pCaller); + if (pTextFrame->GetDoc().GetDocumentSettingManager().get(DocumentSettingId::FLOATTABLE_NOMARGINS)) + { + // If this is explicitly requested, ignore the margins next to the floating table. + if (lcl_hasTabFrame(pTextFrame)) + bIgnoreMargin = true; + // TODO here we only handle the first two paragraphs, would be nice to generalize this. + else if (pTextFrame->FindPrev() && pTextFrame->FindPrev()->IsTextFrame() && lcl_hasTabFrame(static_cast<const SwTextFrame*>(pTextFrame->FindPrev()))) + bIgnoreMargin = true; + } + } + if (!bIgnoreMargin) + nLeft += m_rLR->GetLeft(); + } + + // correction: do not retrieve left margin for numbering in R2L-layout + if ( pCaller->IsTextFrame() && !pCaller->IsRightToLeft() ) + { + nLeft += static_cast<const SwTextFrame*>(pCaller)->GetTextNodeForParaProps()->GetLeftMarginWithNum(); + } + + if (pCaller->IsPageFrame() && m_rLR) + { + const auto pPageFrame = static_cast<const SwPageFrame*>(pCaller); + bool bGutterAtTop = pPageFrame->GetFormat()->getIDocumentSettingAccess().get( + DocumentSettingId::GUTTER_AT_TOP); + if (!bGutterAtTop) + { + bool bRtlGutter = pPageFrame->GetAttrSet()->GetItem<SfxBoolItem>(RES_RTL_GUTTER)->GetValue(); + tools::Long nGutterMargin = bRtlGutter ? m_rLR->GetRightGutterMargin() : m_rLR->GetGutterMargin(); + // Decrease the print area: the left space is the sum of left and gutter margins. + nLeft += nGutterMargin; + } + } + + return nLeft; +} + +/* Calculated values for borders and shadows. + * It might be that a distance is wanted even without lines. This will be + * considered here and not by the attribute (e.g. bBorderDist for cells). + */ + +void SwBorderAttrs::CalcTopLine_() +{ + m_nTopLine = m_rBox.CalcLineSpace( SvxBoxItemLine::TOP, /*bEvenIfNoLine*/true ); + m_nTopLine = m_nTopLine + m_rShadow.CalcShadowSpace(SvxShadowItemSide::TOP); + m_bTopLine = false; +} + +void SwBorderAttrs::CalcBottomLine_() +{ + m_nBottomLine = m_rBox.CalcLineSpace( SvxBoxItemLine::BOTTOM, true ); + m_nBottomLine = m_nBottomLine + m_rShadow.CalcShadowSpace(SvxShadowItemSide::BOTTOM); + m_bBottomLine = false; +} + +void SwBorderAttrs::CalcLeftLine_() +{ + m_nLeftLine = m_rBox.CalcLineSpace( SvxBoxItemLine::LEFT, true); + m_nLeftLine = m_nLeftLine + m_rShadow.CalcShadowSpace(SvxShadowItemSide::LEFT); + m_bLeftLine = false; +} + +void SwBorderAttrs::CalcRightLine_() +{ + m_nRightLine = m_rBox.CalcLineSpace( SvxBoxItemLine::RIGHT, true ); + m_nRightLine = m_nRightLine + m_rShadow.CalcShadowSpace(SvxShadowItemSide::RIGHT); + m_bRightLine = false; +} + +void SwBorderAttrs::IsLine_() +{ + m_bIsLine = m_rBox.GetTop() || m_rBox.GetBottom() || + m_rBox.GetLeft()|| m_rBox.GetRight(); + m_bLine = false; +} + +/* The borders of neighboring paragraphs are condensed by following algorithm: + * + * 1. No top border if the predecessor has the same top border and (3) applies. + * In addition, the paragraph needs to have a border at least one side (left/right/bottom). + * 2. No bottom border if the successor has the same bottom border and (3) applies. + * In addition, the paragraph needs to have a border at least one side (left/right/top). + * 3. The borders on the left and right side are identical between the current and the + * pre-/succeeding paragraph. + */ + +static bool CmpLines( const editeng::SvxBorderLine *pL1, const editeng::SvxBorderLine *pL2 ) +{ + return ( ((pL1 && pL2) && (*pL1 == *pL2)) || (!pL1 && !pL2) ); +} + +// OD 21.05.2003 #108789# - change name of 1st parameter - "rAttrs" -> "rCmpAttrs" +// OD 21.05.2003 #108789# - compare <CalcRight()> and <rCmpAttrs.CalcRight()> +// instead of only the right LR-spacing, because R2L-layout has to be +// considered. +bool SwBorderAttrs::CmpLeftRight( const SwBorderAttrs &rCmpAttrs, + const SwFrame *pCaller, + const SwFrame *pCmp ) const +{ + return ( CmpLines( rCmpAttrs.GetBox().GetLeft(), GetBox().GetLeft() ) && + CmpLines( rCmpAttrs.GetBox().GetRight(),GetBox().GetRight() ) && + CalcLeft( pCaller ) == rCmpAttrs.CalcLeft( pCmp ) && + // OD 21.05.2003 #108789# - compare <CalcRight> with <rCmpAttrs.CalcRight>. + CalcRight( pCaller ) == rCmpAttrs.CalcRight( pCmp ) ); +} + +bool SwBorderAttrs::JoinWithCmp( const SwFrame& _rCallerFrame, + const SwFrame& _rCmpFrame ) const +{ + bool bReturnVal = false; + + SwBorderAttrAccess aCmpAccess( SwFrame::GetCache(), &_rCmpFrame ); + const SwBorderAttrs &rCmpAttrs = *aCmpAccess.Get(); + if ( m_rShadow == rCmpAttrs.GetShadow() && + CmpLines( m_rBox.GetTop(), rCmpAttrs.GetBox().GetTop() ) && + CmpLines( m_rBox.GetBottom(), rCmpAttrs.GetBox().GetBottom() ) && + CmpLeftRight( rCmpAttrs, &_rCallerFrame, &_rCmpFrame ) + ) + { + bReturnVal = true; + } + + return bReturnVal; +} + +// OD 21.05.2003 #108789# - method to determine, if borders are joined with +// previous frame. Calculated value saved in cached value <m_bJoinedWithPrev> +// OD 2004-02-26 #i25029# - add 2nd parameter <_pPrevFrame> +void SwBorderAttrs::CalcJoinedWithPrev( const SwFrame& _rFrame, + const SwFrame* _pPrevFrame ) +{ + // set default + m_bJoinedWithPrev = false; + + if ( _rFrame.IsTextFrame() ) + { + // text frame can potentially join with previous text frame, if + // corresponding attribute set is set at previous text frame. + // OD 2004-02-26 #i25029# - If parameter <_pPrevFrame> is set, take this + // one as previous frame. + const SwFrame* pPrevFrame = _pPrevFrame ? _pPrevFrame : _rFrame.GetPrev(); + // OD 2004-02-13 #i25029# - skip hidden text frames. + while ( pPrevFrame && pPrevFrame->IsTextFrame() && + static_cast<const SwTextFrame*>(pPrevFrame)->IsHiddenNow() ) + { + pPrevFrame = pPrevFrame->GetPrev(); + } + if ( pPrevFrame && pPrevFrame->IsTextFrame() && + pPrevFrame->GetAttrSet()->GetParaConnectBorder().GetValue() + ) + { + m_bJoinedWithPrev = JoinWithCmp( _rFrame, *pPrevFrame ); + } + } + + // valid cache status, if demanded + // OD 2004-02-26 #i25029# - Do not validate cache, if parameter <_pPrevFrame> + // is set. + m_bCachedJoinedWithPrev = m_bCacheGetLine && !_pPrevFrame; +} + +// OD 21.05.2003 #108789# - method to determine, if borders are joined with +// next frame. Calculated value saved in cached value <m_bJoinedWithNext> +void SwBorderAttrs::CalcJoinedWithNext( const SwFrame& _rFrame ) +{ + // set default + m_bJoinedWithNext = false; + + if ( _rFrame.IsTextFrame() ) + { + // text frame can potentially join with next text frame, if + // corresponding attribute set is set at current text frame. + // OD 2004-02-13 #i25029# - get next frame, but skip hidden text frames. + const SwFrame* pNextFrame = _rFrame.GetNext(); + while ( pNextFrame && pNextFrame->IsTextFrame() && + static_cast<const SwTextFrame*>(pNextFrame)->IsHiddenNow() ) + { + pNextFrame = pNextFrame->GetNext(); + } + if ( pNextFrame && pNextFrame->IsTextFrame() && + _rFrame.GetAttrSet()->GetParaConnectBorder().GetValue() + ) + { + m_bJoinedWithNext = JoinWithCmp( _rFrame, *pNextFrame ); + } + } + + // valid cache status, if demanded + m_bCachedJoinedWithNext = m_bCacheGetLine; +} + +// OD 21.05.2003 #108789# - accessor for cached values <m_bJoinedWithPrev> +// OD 2004-02-26 #i25029# - add 2nd parameter <_pPrevFrame>, which is passed to +// method <_CalcJoindWithPrev(..)>. +bool SwBorderAttrs::JoinedWithPrev( const SwFrame& _rFrame, + const SwFrame* _pPrevFrame ) const +{ + if ( !m_bCachedJoinedWithPrev || _pPrevFrame ) + { + // OD 2004-02-26 #i25029# - pass <_pPrevFrame> as 2nd parameter + const_cast<SwBorderAttrs*>(this)->CalcJoinedWithPrev( _rFrame, _pPrevFrame ); + } + + return m_bJoinedWithPrev; +} + +bool SwBorderAttrs::JoinedWithNext( const SwFrame& _rFrame ) const +{ + if ( !m_bCachedJoinedWithNext ) + { + const_cast<SwBorderAttrs*>(this)->CalcJoinedWithNext( _rFrame ); + } + + return m_bJoinedWithNext; +} + +// OD 2004-02-26 #i25029# - added 2nd parameter <_pPrevFrame>, which is passed to +// method <JoinedWithPrev> +void SwBorderAttrs::GetTopLine_( const SwFrame& _rFrame, + const SwFrame* _pPrevFrame ) +{ + sal_uInt16 nRet = CalcTopLine(); + + // OD 21.05.2003 #108789# - use new method <JoinWithPrev()> + // OD 2004-02-26 #i25029# - add 2nd parameter + if ( JoinedWithPrev( _rFrame, _pPrevFrame ) ) + { + nRet = 0; + } + + m_bCachedGetTopLine = m_bCacheGetLine; + + m_nGetTopLine = nRet; +} + +void SwBorderAttrs::GetBottomLine_( const SwFrame& _rFrame ) +{ + sal_uInt16 nRet = CalcBottomLine(); + + // OD 21.05.2003 #108789# - use new method <JoinWithPrev()> + if ( JoinedWithNext( _rFrame ) ) + { + nRet = 0; + } + + m_bCachedGetBottomLine = m_bCacheGetLine; + + m_nGetBottomLine = nRet; +} + +void SwBorderAttrs::CalcLineSpacing_() +{ + // tdf#125300 compatibility option AddParaLineSpacingToTableCells needs also line spacing + const SvxLineSpacingItem &rSpace = m_rAttrSet.GetLineSpacing(); + if ( rSpace.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop && rSpace.GetPropLineSpace() > 100 ) + { + sal_Int32 nFontSize = m_rAttrSet.Get(RES_CHRATR_FONTSIZE).GetHeight(); + m_nLineSpacing = nFontSize * (rSpace.GetPropLineSpace() - 100) * 1.15 / 100; + } + m_bLineSpacing = false; +} + +static sw::BorderCacheOwner const* GetBorderCacheOwner(SwFrame const& rFrame) +{ + return rFrame.IsContentFrame() + ? static_cast<sw::BorderCacheOwner const*>(rFrame.IsTextFrame() + // sw_redlinehide: presumably this caches the border attrs at the model level and can be shared across different layouts so we want the ParaProps node here + ? static_cast<const SwTextFrame&>(rFrame).GetTextNodeForParaProps() + : static_cast<const SwNoTextFrame&>(rFrame).GetNode()) + : static_cast<sw::BorderCacheOwner const*>(static_cast<const SwLayoutFrame&>(rFrame).GetFormat()); +} + +SwBorderAttrAccess::SwBorderAttrAccess( SwCache &rCach, const SwFrame *pFrame ) : + SwCacheAccess( rCach, + static_cast<void const *>(GetBorderCacheOwner(*pFrame)), + GetBorderCacheOwner(*pFrame)->IsInCache()), + m_pConstructor( pFrame ) +{ +} + +SwCacheObj *SwBorderAttrAccess::NewObj() +{ + const_cast<sw::BorderCacheOwner *>(static_cast<sw::BorderCacheOwner const *>(m_pOwner))->m_bInCache = true; + return new SwBorderAttrs( static_cast<sw::BorderCacheOwner const *>(m_pOwner), m_pConstructor ); +} + +SwBorderAttrs *SwBorderAttrAccess::Get() +{ + return static_cast<SwBorderAttrs*>(SwCacheAccess::Get()); +} + +SwOrderIter::SwOrderIter( const SwPageFrame *pPg ) : + m_pPage( pPg ), + m_pCurrent( nullptr ) +{ +} + +void SwOrderIter::Top() +{ + m_pCurrent = nullptr; + if ( !m_pPage->GetSortedObjs() ) + return; + + const SwSortedObjs *pObjs = m_pPage->GetSortedObjs(); + if ( !pObjs->size() ) + return; + + sal_uInt32 nTopOrd = 0; + (*pObjs)[0]->GetDrawObj()->GetOrdNum(); // force updating + for (SwAnchoredObject* i : *pObjs) + { + const SdrObject* pObj = i->GetDrawObj(); + if ( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) == nullptr ) + continue; + sal_uInt32 nTmp = pObj->GetOrdNumDirect(); + if ( nTmp >= nTopOrd ) + { + nTopOrd = nTmp; + m_pCurrent = pObj; + } + } +} + +const SdrObject *SwOrderIter::Bottom() +{ + m_pCurrent = nullptr; + if ( m_pPage->GetSortedObjs() ) + { + sal_uInt32 nBotOrd = USHRT_MAX; + const SwSortedObjs *pObjs = m_pPage->GetSortedObjs(); + if ( pObjs->size() ) + { + (*pObjs)[0]->GetDrawObj()->GetOrdNum(); // force updating + for (SwAnchoredObject* i : *pObjs) + { + const SdrObject* pObj = i->GetDrawObj(); + if ( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) == nullptr ) + continue; + sal_uInt32 nTmp = pObj->GetOrdNumDirect(); + if ( nTmp < nBotOrd ) + { + nBotOrd = nTmp; + m_pCurrent = pObj; + } + } + } + } + return m_pCurrent; +} + +const SdrObject *SwOrderIter::Next() +{ + const sal_uInt32 nCurOrd = m_pCurrent ? m_pCurrent->GetOrdNumDirect() : 0; + m_pCurrent = nullptr; + if ( m_pPage->GetSortedObjs() ) + { + sal_uInt32 nOrd = USHRT_MAX; + const SwSortedObjs *pObjs = m_pPage->GetSortedObjs(); + if ( pObjs->size() ) + { + (*pObjs)[0]->GetDrawObj()->GetOrdNum(); // force updating + for (SwAnchoredObject* i : *pObjs) + { + const SdrObject* pObj = i->GetDrawObj(); + if ( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) == nullptr ) + continue; + sal_uInt32 nTmp = pObj->GetOrdNumDirect(); + if ( nTmp > nCurOrd && nTmp < nOrd ) + { + nOrd = nTmp; + m_pCurrent = pObj; + } + } + } + } + return m_pCurrent; +} + +void SwOrderIter::Prev() +{ + const sal_uInt32 nCurOrd = m_pCurrent ? m_pCurrent->GetOrdNumDirect() : 0; + m_pCurrent = nullptr; + if ( !m_pPage->GetSortedObjs() ) + return; + + const SwSortedObjs *pObjs = m_pPage->GetSortedObjs(); + if ( !pObjs->size() ) + return; + + sal_uInt32 nOrd = 0; + (*pObjs)[0]->GetDrawObj()->GetOrdNum(); // force updating + for (SwAnchoredObject* i : *pObjs) + { + const SdrObject* pObj = i->GetDrawObj(); + if ( dynamic_cast<const SwVirtFlyDrawObj*>( pObj) == nullptr ) + continue; + sal_uInt32 nTmp = pObj->GetOrdNumDirect(); + if ( nTmp < nCurOrd && nTmp >= nOrd ) + { + nOrd = nTmp; + m_pCurrent = pObj; + } + } +} + +/// Keep and restore the substructure of a layout frame for an action. +// New algorithm: +// Do not look at each neighbor one by one to set all pointers correctly. +// It is sufficient to detach a part of a chain and check if another chain needs to be added +// when attaching it again. Only the pointers necessary for the chain connection need to be +// adjusted. The correction happens in RestoreContent(). In between all access is restricted. +// During this action, the Flys are detached from the page. + +// #115759# - 'remove' also drawing object from page and +// at-fly anchored objects from page +static void lcl_RemoveObjsFromPage( SwFrame* _pFrame ) +{ + OSL_ENSURE( _pFrame->GetDrawObjs(), "no DrawObjs in lcl_RemoveObjsFromPage." ); + SwSortedObjs &rObjs = *_pFrame->GetDrawObjs(); + for (SwAnchoredObject* pObj : rObjs) + { + // #115759# - reset member, at which the anchored + // object orients its vertical position + pObj->ClearVertPosOrientFrame(); + // #i43913# + pObj->ResetLayoutProcessBools(); + // #115759# - remove also lower objects of as-character + // anchored Writer fly frames from page + if ( auto pFlyFrame = pObj->DynCastFlyFrame() ) + { + // #115759# - remove also direct lowers of Writer + // fly frame from page + if ( pFlyFrame->GetDrawObjs() ) + { + ::lcl_RemoveObjsFromPage( pFlyFrame ); + } + + SwContentFrame* pCnt = pFlyFrame->ContainsContent(); + while ( pCnt ) + { + if ( pCnt->GetDrawObjs() ) + ::lcl_RemoveObjsFromPage( pCnt ); + pCnt = pCnt->GetNextContentFrame(); + } + if ( pFlyFrame->IsFlyFreeFrame() ) + { + // #i28701# - use new method <GetPageFrame()> + if (SwPageFrame *pPg = pFlyFrame->GetPageFrame()) + pPg->RemoveFlyFromPage(pFlyFrame); + } + } + // #115759# - remove also drawing objects from page + else if ( auto pDrawObj = dynamic_cast<SwAnchoredDrawObject*>( pObj) ) + { + if (pObj->GetFrameFormat().GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) + { + if (SwPageFrame *pPg = pObj->GetPageFrame()) + pPg->RemoveDrawObjFromPage( *pDrawObj ); + } + } + } +} + +SwFrame *SaveContent( SwLayoutFrame *pLay, SwFrame *pStart ) +{ + if( pLay->IsSctFrame() && pLay->Lower() && pLay->Lower()->IsColumnFrame() ) + sw_RemoveFootnotes( static_cast<SwColumnFrame*>(pLay->Lower()), true, true ); + + SwFrame *pSav = pLay->ContainsAny(); + if ( nullptr == pSav ) + return nullptr; + + if( pSav->IsInFootnote() && !pLay->IsInFootnote() ) + { + do + pSav = pSav->FindNext(); + while( pSav && pSav->IsInFootnote() ); + if( !pSav || !pLay->IsAnLower( pSav ) ) + return nullptr; + } + + // Tables should be saved as a whole, exception: + // The contents of a section or a cell inside a table should be saved + if ( pSav->IsInTab() && !( ( pLay->IsSctFrame() || pLay->IsCellFrame() ) && pLay->IsInTab() ) ) + while ( !pSav->IsTabFrame() ) + pSav = pSav->GetUpper(); + + if( pSav->IsInSct() ) + { // search the upmost section inside of pLay + SwFrame* pSect = pLay->FindSctFrame(); + SwFrame *pTmp = pSav; + do + { + pSav = pTmp; + pTmp = (pSav && pSav->GetUpper()) ? pSav->GetUpper()->FindSctFrame() : nullptr; + } while ( pTmp != pSect ); + } + + SwFrame *pFloat = pSav; + if( !pStart ) + pStart = pSav; + bool bGo = pStart == pSav; + do + { + if( bGo ) + pFloat->GetUpper()->m_pLower = nullptr; // detach the chain part + + // search the end of the chain part, remove Flys on the way + do + { + if( bGo ) + { + if ( pFloat->IsContentFrame() ) + { + if ( pFloat->GetDrawObjs() ) + ::lcl_RemoveObjsFromPage( static_cast<SwContentFrame*>(pFloat) ); + } + else if ( pFloat->IsTabFrame() || pFloat->IsSctFrame() ) + { + SwContentFrame *pCnt = static_cast<SwLayoutFrame*>(pFloat)->ContainsContent(); + if( pCnt ) + { + do + { if ( pCnt->GetDrawObjs() ) + ::lcl_RemoveObjsFromPage( pCnt ); + pCnt = pCnt->GetNextContentFrame(); + } while ( pCnt && static_cast<SwLayoutFrame*>(pFloat)->IsAnLower( pCnt ) ); + } + } + else { + OSL_ENSURE( !pFloat, "new FloatFrame?" ); + } + } + if ( pFloat->GetNext() ) + { + if( bGo ) + pFloat->mpUpper = nullptr; + pFloat = pFloat->GetNext(); + if( !bGo && pFloat == pStart ) + { + bGo = true; + pFloat->mpPrev->mpNext = nullptr; + pFloat->mpPrev = nullptr; + } + } + else + break; + + } while ( pFloat ); + + // search next chain part and connect both chains + SwFrame *pTmp = pFloat->FindNext(); + if( bGo ) + pFloat->mpUpper = nullptr; + + if( !pLay->IsInFootnote() ) + while( pTmp && pTmp->IsInFootnote() ) + pTmp = pTmp->FindNext(); + + if ( !pLay->IsAnLower( pTmp ) ) + pTmp = nullptr; + + if ( pTmp && bGo ) + { + pFloat->mpNext = pTmp; // connect both chains + pFloat->mpNext->mpPrev = pFloat; + } + pFloat = pTmp; + bGo = bGo || ( pStart == pFloat ); + } while ( pFloat ); + + return bGo ? pStart : nullptr; +} + +// #115759# - add also drawing objects to page and at-fly +// anchored objects to page +static void lcl_AddObjsToPage( SwFrame* _pFrame, SwPageFrame* _pPage ) +{ + OSL_ENSURE( _pFrame->GetDrawObjs(), "no DrawObjs in lcl_AddObjsToPage." ); + SwSortedObjs &rObjs = *_pFrame->GetDrawObjs(); + for (SwAnchoredObject* pObj : rObjs) + { + // #115759# - unlock position of anchored object + // in order to get the object's position calculated. + pObj->UnlockPosition(); + // #115759# - add also lower objects of as-character + // anchored Writer fly frames from page + if ( auto pFlyFrame = pObj->DynCastFlyFrame() ) + { + if (pFlyFrame->IsFlyFreeFrame()) + { + _pPage->AppendFlyToPage( pFlyFrame ); + } + pFlyFrame->InvalidatePos_(); + pFlyFrame->InvalidateSize_(); + pFlyFrame->InvalidatePage( _pPage ); + + // #115759# - add also at-fly anchored objects + // to page + if ( pFlyFrame->GetDrawObjs() ) + { + ::lcl_AddObjsToPage( pFlyFrame, _pPage ); + } + + SwContentFrame *pCnt = pFlyFrame->ContainsContent(); + while ( pCnt ) + { + if ( pCnt->GetDrawObjs() ) + ::lcl_AddObjsToPage( pCnt, _pPage ); + pCnt = pCnt->GetNextContentFrame(); + } + } + // #115759# - remove also drawing objects from page + else if ( dynamic_cast<const SwAnchoredDrawObject*>( pObj) != nullptr ) + { + if (pObj->GetFrameFormat().GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) + { + pObj->InvalidateObjPos(); + _pPage->AppendDrawObjToPage( + *static_cast<SwAnchoredDrawObject*>(pObj) ); + } + } + } +} + +void RestoreContent( SwFrame *pSav, SwLayoutFrame *pParent, SwFrame *pSibling ) +{ + OSL_ENSURE( pSav && pParent, "no Save or Parent provided for RestoreContent." ); + SwRectFnSet aRectFnSet(pParent); + + // If there are already FlowFrames below the new parent, so add the chain (starting with pSav) + // after the last one. The parts are inserted and invalidated if needed. + // On the way, the Flys of the ContentFrames are registered at the page. + + SwPageFrame *pPage = pParent->FindPageFrame(); + + if ( pPage ) + pPage->InvalidatePage( pPage ); + + // determine predecessor and establish connection or initialize + pSav->mpPrev = pSibling; + SwFrame* pNxt; + if ( pSibling ) + { + pNxt = pSibling->mpNext; + pSibling->mpNext = pSav; + pSibling->InvalidatePrt_(); + pSibling->InvalidatePage( pPage ); + SwFlowFrame *pFlowFrame = dynamic_cast<SwFlowFrame*>(pSibling); + if (pFlowFrame && pFlowFrame->GetFollow()) + pSibling->Prepare( PrepareHint::Clear, nullptr, false ); + } + else + { pNxt = pParent->m_pLower; + pParent->m_pLower = pSav; + pSav->mpUpper = pParent; // set here already, so that it is explicit when invalidating + + if ( pSav->IsContentFrame() ) + static_cast<SwContentFrame*>(pSav)->InvalidatePage( pPage ); + else + { // pSav might be an empty SectFrame + SwContentFrame* pCnt = pParent->ContainsContent(); + if( pCnt ) + pCnt->InvalidatePage( pPage ); + } + } + + // the parent needs to grow appropriately + SwTwips nGrowVal = 0; + SwFrame* pLast; + do + { pSav->mpUpper = pParent; + nGrowVal += aRectFnSet.GetHeight(pSav->getFrameArea()); + pSav->InvalidateAll_(); + + // register Flys, if TextFrames than also invalidate appropriately + if ( pSav->IsContentFrame() ) + { + if ( pSav->IsTextFrame() && + static_cast<SwTextFrame*>(pSav)->GetCacheIdx() != USHRT_MAX ) + static_cast<SwTextFrame*>(pSav)->Init(); // I am its friend + + if ( pPage && pSav->GetDrawObjs() ) + ::lcl_AddObjsToPage( static_cast<SwContentFrame*>(pSav), pPage ); + } + else + { SwContentFrame *pBlub = static_cast<SwLayoutFrame*>(pSav)->ContainsContent(); + if( pBlub ) + { + do + { if ( pPage && pBlub->GetDrawObjs() ) + ::lcl_AddObjsToPage( pBlub, pPage ); + if( pBlub->IsTextFrame() && static_cast<SwTextFrame*>(pBlub)->HasFootnote() && + static_cast<SwTextFrame*>(pBlub)->GetCacheIdx() != USHRT_MAX ) + static_cast<SwTextFrame*>(pBlub)->Init(); // I am its friend + pBlub = pBlub->GetNextContentFrame(); + } while ( pBlub && static_cast<SwLayoutFrame*>(pSav)->IsAnLower( pBlub )); + } + } + pLast = pSav; + pSav = pSav->GetNext(); + + } while ( pSav ); + + if( pNxt ) + { + pLast->mpNext = pNxt; + pNxt->mpPrev = pLast; + } + + pParent->Grow( nGrowVal ); +} + +namespace sw { + +bool IsRightPageByNumber(SwRootFrame const& rLayout, sal_uInt16 const nPageNum) +{ + assert(rLayout.GetLower()); + // unfortunately can only get SwPageDesc, not SwFormatPageDesc here... + auto const nFirstVirtPageNum(rLayout.GetLower()->GetVirtPageNum()); + bool const isFirstPageOfLayoutOdd(nFirstVirtPageNum % 2 == 1); + return ((nPageNum % 2) == 1) == isFirstPageOfLayoutOdd; +} + +} // namespace sw + +SwPageFrame * InsertNewPage( SwPageDesc &rDesc, SwFrame *pUpper, + bool const isRightPage, bool const bFirst, bool bInsertEmpty, + bool const bFootnote, + SwFrame *pSibling, + bool const bVeryFirstPage ) +{ + assert(pUpper); + assert(pUpper->IsRootFrame()); + assert(!pSibling || static_cast<SwLayoutFrame const*>(pUpper)->Lower() != pSibling); // currently no insert before 1st page + SwPageFrame *pRet; + SwDoc *pDoc = static_cast<SwLayoutFrame*>(pUpper)->GetFormat()->GetDoc(); + if (bFirst) + { + if (rDesc.IsFirstShared()) + { + // We need to fallback to left or right page format, decide it now. + // FIXME: is this still needed? + if (isRightPage) + { + rDesc.GetFirstMaster().SetFormatAttr( rDesc.GetMaster().GetHeader() ); + rDesc.GetFirstMaster().SetFormatAttr( rDesc.GetMaster().GetFooter() ); + // fdo#60250 copy margins for mirrored pages + rDesc.GetFirstMaster().SetFormatAttr( rDesc.GetMaster().GetLRSpace() ); + } + else + { + rDesc.GetFirstLeft().SetFormatAttr( rDesc.GetLeft().GetHeader() ); + rDesc.GetFirstLeft().SetFormatAttr( rDesc.GetLeft().GetFooter() ); + rDesc.GetFirstLeft().SetFormatAttr( rDesc.GetLeft().GetLRSpace() ); + } + } + } + SwFrameFormat *pFormat(isRightPage ? rDesc.GetRightFormat(bFirst) : rDesc.GetLeftFormat(bFirst)); + // If there is no FrameFormat for this page, add an empty page + if ( !pFormat ) + { + pFormat = isRightPage ? rDesc.GetLeftFormat(bVeryFirstPage) : rDesc.GetRightFormat(bVeryFirstPage); + OSL_ENSURE( pFormat, "Descriptor without any format?!" ); + bInsertEmpty = !bInsertEmpty; + } + if( bInsertEmpty ) + { + SwPageDesc *pTmpDesc = pSibling && pSibling->GetPrev() ? + static_cast<SwPageFrame*>(pSibling->GetPrev())->GetPageDesc() : &rDesc; + pRet = new SwPageFrame( pDoc->GetEmptyPageFormat(), pUpper, pTmpDesc ); + SAL_INFO( "sw.pageframe", "InsertNewPage - insert empty p: " << pRet << " d: " << pTmpDesc ); + pRet->Paste( pUpper, pSibling ); + pRet->PreparePage( bFootnote ); + } + pRet = new SwPageFrame( pFormat, pUpper, &rDesc ); + SAL_INFO( "sw.pageframe", "InsertNewPage p: " << pRet << " d: " << &rDesc << " f: " << pFormat ); + pRet->Paste( pUpper, pSibling ); + pRet->PreparePage( bFootnote ); + if ( pRet->GetNext() ) + SwRootFrame::AssertPageFlys( pRet ); + return pRet; +} + +/* The following two methods search the layout structure recursively and + * register all Flys at the page that have a Frame in this structure as an anchor. + */ + +static void lcl_Regist( SwPageFrame *pPage, const SwFrame *pAnch ) +{ + SwSortedObjs *pObjs = const_cast<SwSortedObjs*>(pAnch->GetDrawObjs()); + for (SwAnchoredObject* pObj : *pObjs) + { + if (SwFlyFrame* pFly = pObj->DynCastFlyFrame()) + { + // register (not if already known) + // #i28701# - use new method <GetPageFrame()> + SwPageFrame *pPg = pFly->IsFlyFreeFrame() + ? pFly->GetPageFrame() : pFly->FindPageFrame(); + if ( pPg != pPage ) + { + if ( pPg ) + pPg->RemoveFlyFromPage( pFly ); + pPage->AppendFlyToPage( pFly ); + } + ::RegistFlys( pPage, pFly ); + } + else + { + // #i87493# + if ( pPage != pObj->GetPageFrame() ) + { + // #i28701# + if (SwPageFrame *pPg = pObj->GetPageFrame()) + pPg->RemoveDrawObjFromPage( *pObj ); + pPage->AppendDrawObjToPage( *pObj ); + } + } + + const SwFlyFrame* pFly = pAnch->FindFlyFrame(); + if ( pFly && + pObj->GetDrawObj()->GetOrdNum() < pFly->GetVirtDrawObj()->GetOrdNum() && + pObj->GetDrawObj()->getSdrPageFromSdrObject() ) + { + //#i119945# set pFly's OrdNum to pObj's. So when pFly is removed by Undo, the original OrdNum will not be changed. + pObj->DrawObj()->getSdrPageFromSdrObject()->SetObjectOrdNum( pFly->GetVirtDrawObj()->GetOrdNumDirect(), + pObj->GetDrawObj()->GetOrdNumDirect() ); + } + } +} + +void RegistFlys( SwPageFrame *pPage, const SwLayoutFrame *pLay ) +{ + if ( pLay->GetDrawObjs() ) + ::lcl_Regist( pPage, pLay ); + const SwFrame *pFrame = pLay->Lower(); + while ( pFrame ) + { + if ( pFrame->IsLayoutFrame() ) + ::RegistFlys( pPage, static_cast<const SwLayoutFrame*>(pFrame) ); + else if ( pFrame->GetDrawObjs() ) + ::lcl_Regist( pPage, pFrame ); + pFrame = pFrame->GetNext(); + } +} + +/// Notify the background based on the difference between old and new rectangle +void Notify( SwFlyFrame *pFly, SwPageFrame *pOld, const SwRect &rOld, + const SwRect* pOldPrt ) +{ + const SwRect aFrame( pFly->GetObjRectWithSpaces() ); + if ( rOld.Pos() != aFrame.Pos() ) + { // changed position, invalidate old and new area + if ( rOld.HasArea() && + rOld.Left()+pFly->GetFormat()->GetLRSpace().GetLeft() < FAR_AWAY ) + { + pFly->NotifyBackground( pOld, rOld, PrepareHint::FlyFrameLeave ); + } + pFly->NotifyBackground( pFly->FindPageFrame(), aFrame, PrepareHint::FlyFrameArrive ); + } + else if ( rOld.SSize() != aFrame.SSize() ) + { // changed size, invalidate the area that was left or is now overlapped + // For simplicity, we purposely invalidate a Twip even if not needed. + + SwViewShell *pSh = pFly->getRootFrame()->GetCurrShell(); + if( pSh && rOld.HasArea() ) + pSh->InvalidateWindows( rOld ); + + // #i51941# - consider case that fly frame isn't + // registered at the old page <pOld> + SwPageFrame* pPageFrame = pFly->FindPageFrame(); + if ( pOld != pPageFrame ) + { + pFly->NotifyBackground( pPageFrame, aFrame, PrepareHint::FlyFrameArrive ); + } + + if ( rOld.Left() != aFrame.Left() ) + { + SwRect aTmp( rOld ); + aTmp.Union( aFrame ); + aTmp.Left( std::min(aFrame.Left(), rOld.Left()) ); + aTmp.Right( std::max(aFrame.Left(), rOld.Left()) ); + pFly->NotifyBackground( pOld, aTmp, PrepareHint::FlyFrameSizeChanged ); + } + SwTwips nOld = rOld.Right(); + SwTwips nNew = aFrame.Right(); + if ( nOld != nNew ) + { + SwRect aTmp( rOld ); + aTmp.Union( aFrame ); + aTmp.Left( std::min(nNew, nOld) ); + aTmp.Right( std::max(nNew, nOld) ); + pFly->NotifyBackground( pOld, aTmp, PrepareHint::FlyFrameSizeChanged ); + } + if ( rOld.Top() != aFrame.Top() ) + { + SwRect aTmp( rOld ); + aTmp.Union( aFrame ); + aTmp.Top( std::min(aFrame.Top(), rOld.Top()) ); + aTmp.Bottom( std::max(aFrame.Top(), rOld.Top()) ); + pFly->NotifyBackground( pOld, aTmp, PrepareHint::FlyFrameSizeChanged ); + } + nOld = rOld.Bottom(); + nNew = aFrame.Bottom(); + if ( nOld != nNew ) + { + SwRect aTmp( rOld ); + aTmp.Union( aFrame ); + aTmp.Top( std::min(nNew, nOld) ); + aTmp.Bottom( std::max(nNew, nOld) ); + pFly->NotifyBackground( pOld, aTmp, PrepareHint::FlyFrameSizeChanged ); + } + } + else if(pOldPrt && *pOldPrt != pFly->getFramePrintArea()) + { + bool bNotifyBackground(pFly->GetFormat()->GetSurround().IsContour()); + + if(!bNotifyBackground && + pFly->IsFlyFreeFrame() && + static_cast< const SwFlyFreeFrame* >(pFly)->supportsAutoContour()) + { + // RotateFlyFrame3: Also notify for FlyFrames which allow AutoContour + bNotifyBackground = true; + } + + if(bNotifyBackground) + { + // #i24097# + pFly->NotifyBackground( pFly->FindPageFrame(), aFrame, PrepareHint::FlyFrameArrive ); + } + } +} + +static void lcl_CheckFlowBack( SwFrame* pFrame, const SwRect &rRect ) +{ + SwTwips nBottom = rRect.Bottom(); + while( pFrame ) + { + if( pFrame->IsLayoutFrame() ) + { + if( rRect.Overlaps( pFrame->getFrameArea() ) ) + lcl_CheckFlowBack( static_cast<SwLayoutFrame*>(pFrame)->Lower(), rRect ); + } + else if( !pFrame->GetNext() && nBottom > pFrame->getFrameArea().Bottom() ) + { + if( pFrame->IsContentFrame() && static_cast<SwContentFrame*>(pFrame)->HasFollow() ) + pFrame->InvalidateSize(); + else + pFrame->InvalidateNextPos(); + } + pFrame = pFrame->GetNext(); + } +} + +static void lcl_NotifyContent( const SdrObject *pThis, SwContentFrame *pCnt, + const SwRect &rRect, const PrepareHint eHint ) +{ + if ( !pCnt->IsTextFrame() ) + return; + + SwRect aCntPrt( pCnt->getFramePrintArea() ); + aCntPrt.Pos() += pCnt->getFrameArea().Pos(); + if ( eHint == PrepareHint::FlyFrameAttributesChanged ) + { + // #i35640# - use given rectangle <rRect> instead + // of current bound rectangle + if ( aCntPrt.Overlaps( rRect ) ) + pCnt->Prepare( PrepareHint::FlyFrameAttributesChanged ); + } + // #i23129# - only invalidate, if the text frame + // printing area overlaps with the given rectangle. + else if ( aCntPrt.Overlaps( rRect ) ) + pCnt->Prepare( eHint, static_cast<void*>(&aCntPrt.Intersection_( rRect )) ); + if ( !pCnt->GetDrawObjs() ) + return; + + const SwSortedObjs &rObjs = *pCnt->GetDrawObjs(); + for (SwAnchoredObject* pObj : rObjs) + { + if ( auto pFly = pObj->DynCastFlyFrame() ) + { + if ( pFly->IsFlyInContentFrame() ) + { + SwContentFrame *pContent = pFly->ContainsContent(); + while ( pContent ) + { + ::lcl_NotifyContent( pThis, pContent, rRect, eHint ); + pContent = pContent->GetNextContentFrame(); + } + } + } + } +} + +void Notify_Background( const SdrObject* pObj, + SwPageFrame* pPage, + const SwRect& rRect, + const PrepareHint eHint, + const bool bInva ) +{ + // If the frame was positioned correctly for the first time, do not inform the old area + if ( eHint == PrepareHint::FlyFrameLeave && rRect.Top() == FAR_AWAY ) + return; + + SwLayoutFrame* pArea; + SwFlyFrame *pFlyFrame = nullptr; + SwFrame* pAnchor; + if( auto pVirtFlyDrawObj = dynamic_cast<const SwVirtFlyDrawObj*>( pObj) ) + { + pFlyFrame = const_cast<SwVirtFlyDrawObj*>(pVirtFlyDrawObj)->GetFlyFrame(); + pAnchor = pFlyFrame->AnchorFrame(); + } + else + { + pFlyFrame = nullptr; + pAnchor = const_cast<SwFrame*>( + GetUserCall(pObj)->GetAnchoredObj( pObj )->GetAnchorFrame() ); + } + if( PrepareHint::FlyFrameLeave != eHint && pAnchor->IsInFly() ) + pArea = pAnchor->FindFlyFrame(); + else + pArea = pPage; + SwContentFrame *pCnt = nullptr; + if ( pArea ) + { + if( PrepareHint::FlyFrameArrive != eHint ) + lcl_CheckFlowBack( pArea, rRect ); + + // Only the Flys following this anchor are reacting. Thus, those do not + // need to be processed. + // An exception is LEAVE, since the Fly might come "from above". + // If the anchor is positioned on the previous page, the whole page + // needs to be processed (47722). + // OD 2004-05-13 #i28701# - If the wrapping style has to be considered + // on the object positioning, the complete area has to be processed, + // because content frames before the anchor frame also have to consider + // the object for the text wrapping. + // #i3317# - The complete area has always been + // processed. + { + pCnt = pArea->ContainsContent(); + } + } + SwFrame *pLastTab = nullptr; + + bool isValidTableBeforeAnchor(false); + while ( pCnt && pArea && pArea->IsAnLower( pCnt ) ) + { + ::lcl_NotifyContent( pObj, pCnt, rRect, eHint ); + if ( pCnt->IsInTab() ) + { + SwTabFrame *pTab = pCnt->FindTabFrame(); + if ( pTab != pLastTab ) + { + pLastTab = pTab; + isValidTableBeforeAnchor = false; + if (PrepareHint::FlyFrameArrive == eHint + && pFlyFrame // TODO: do it for draw objects too? + && pTab->IsFollow() // table starts on previous page? + // "through" means they will actually overlap anyway + && css::text::WrapTextMode_THROUGH != pFlyFrame->GetFormat()->GetSurround().GetSurround() + // if it's anchored in footer it can't move to other page + && !pAnchor->FindFooterOrHeader()) + { + SwFrame * pTmp(pAnchor->GetPrev()); + while (pTmp) + { + if (pTmp == pTab) + { + // tdf#99460 the table shouldn't be moved by the fly + isValidTableBeforeAnchor = true; + break; + } + pTmp = pTmp->GetPrev(); + } + } + // #i40606# - use <GetLastBoundRect()> + // instead of <GetCurrentBoundRect()>, because a recalculation + // of the bounding rectangle isn't intended here. + if (!isValidTableBeforeAnchor + && (pTab->getFrameArea().Overlaps(SwRect(pObj->GetLastBoundRect())) || + pTab->getFrameArea().Overlaps(rRect))) + { + if ( !pFlyFrame || !pFlyFrame->IsLowerOf( pTab ) ) + pTab->InvalidatePrt(); + } + } + SwLayoutFrame* pCell = pCnt->GetUpper(); + // #i40606# - use <GetLastBoundRect()> + // instead of <GetCurrentBoundRect()>, because a recalculation + // of the bounding rectangle isn't intended here. + if (!isValidTableBeforeAnchor && pCell->IsCellFrame() && + ( pCell->getFrameArea().Overlaps( SwRect(pObj->GetLastBoundRect()) ) || + pCell->getFrameArea().Overlaps( rRect ) ) ) + { + const SwFormatVertOrient &rOri = pCell->GetFormat()->GetVertOrient(); + if ( text::VertOrientation::NONE != rOri.GetVertOrient() ) + pCell->InvalidatePrt(); + } + } + pCnt = pCnt->GetNextContentFrame(); + } + // #128702# - make code robust + if ( pPage && pPage->GetSortedObjs() ) + { + pObj->GetOrdNum(); + const SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if ( pAnchoredObj->DynCastFlyFrame() != nullptr ) + { + if( pAnchoredObj->GetDrawObj() == pObj ) + continue; + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pAnchoredObj); + if ( pFly->getFrameArea().Top() == FAR_AWAY ) + continue; + + if ( !pFlyFrame || + (!pFly->IsLowerOf( pFlyFrame ) && + pFly->GetVirtDrawObj()->GetOrdNumDirect() < pObj->GetOrdNumDirect())) + { + pCnt = pFly->ContainsContent(); + while ( pCnt ) + { + ::lcl_NotifyContent( pObj, pCnt, rRect, eHint ); + pCnt = pCnt->GetNextContentFrame(); + } + } + if( pFly->IsFlyLayFrame() ) + { + if( pFly->Lower() && pFly->Lower()->IsColumnFrame() && + pFly->getFrameArea().Bottom() >= rRect.Top() && + pFly->getFrameArea().Top() <= rRect.Bottom() && + pFly->getFrameArea().Right() >= rRect.Left() && + pFly->getFrameArea().Left() <= rRect.Right() ) + { + pFly->InvalidateSize(); + } + } + // Flys above myself might sidestep if they have an automatic + // alignment. This happens independently of my attributes since + // this might have been changed as well. + else if ( pFly->IsFlyAtContentFrame() && + pObj->GetOrdNumDirect() < + pFly->GetVirtDrawObj()->GetOrdNumDirect() && + pFlyFrame && !pFly->IsLowerOf( pFlyFrame ) ) + { + const SwFormatHoriOrient &rH = pFly->GetFormat()->GetHoriOrient(); + if ( text::HoriOrientation::NONE != rH.GetHoriOrient() && + text::HoriOrientation::CENTER != rH.GetHoriOrient() && + ( !pFly->IsAutoPos() || text::RelOrientation::CHAR != rH.GetRelationOrient() ) && + (pFly->getFrameArea().Bottom() >= rRect.Top() && + pFly->getFrameArea().Top() <= rRect.Bottom()) ) + pFly->InvalidatePos(); + } + } + } + } + if ( pFlyFrame && pAnchor->GetUpper() && pAnchor->IsInTab() )//MA_FLY_HEIGHT + pAnchor->GetUpper()->InvalidateSize(); + + // #i82258# - make code robust + SwViewShell* pSh = nullptr; + if ( bInva && pPage && + nullptr != (pSh = pPage->getRootFrame()->GetCurrShell()) ) + { + pSh->InvalidateWindows( rRect ); + } +} + +/// Provides the Upper of an anchor in paragraph-bound objects. If the latter +/// is a chained border or a footnote, the "virtual" Upper might be returned. +const SwFrame* GetVirtualUpper( const SwFrame* pFrame, const Point& rPos ) +{ + if( pFrame->IsTextFrame() ) + { + pFrame = pFrame->GetUpper(); + if( !pFrame->getFrameArea().Contains( rPos ) ) + { + if( pFrame->IsFootnoteFrame() ) + { + const SwFootnoteFrame* pTmp = static_cast<const SwFootnoteFrame*>(pFrame)->GetFollow(); + while( pTmp ) + { + if( pTmp->getFrameArea().Contains( rPos ) ) + return pTmp; + pTmp = pTmp->GetFollow(); + } + } + else + { + SwFlyFrame* pTmp = const_cast<SwFlyFrame*>(pFrame->FindFlyFrame()); + while( pTmp ) + { + if( pTmp->getFrameArea().Contains( rPos ) ) + return pTmp; + pTmp = pTmp->GetNextLink(); + } + } + } + } + return pFrame; +} + +bool Is_Lower_Of(const SwFrame *pCurrFrame, const SdrObject* pObj) +{ + Point aPos; + const SwFrame* pFrame; + if (const SwVirtFlyDrawObj *pFlyDrawObj = dynamic_cast<const SwVirtFlyDrawObj*>(pObj)) + { + const SwFlyFrame* pFly = pFlyDrawObj->GetFlyFrame(); + pFrame = pFly->GetAnchorFrame(); + aPos = pFly->getFrameArea().Pos(); + } + else + { + pFrame = static_cast<SwDrawContact*>(GetUserCall(pObj))->GetAnchorFrame(pObj); + aPos = pObj->GetCurrentBoundRect().TopLeft(); + } + OSL_ENSURE( pFrame, "8-( Fly is lost in Space." ); + pFrame = GetVirtualUpper( pFrame, aPos ); + do + { if ( pFrame == pCurrFrame ) + return true; + if( pFrame->IsFlyFrame() ) + { + aPos = pFrame->getFrameArea().Pos(); + pFrame = GetVirtualUpper( static_cast<const SwFlyFrame*>(pFrame)->GetAnchorFrame(), aPos ); + } + else + pFrame = pFrame->GetUpper(); + } while ( pFrame ); + return false; +} + +/// provides the area of a frame in that no Fly from another area can overlap +const SwFrame *FindContext( const SwFrame *pFrame, SwFrameType nAdditionalContextType ) +{ + const SwFrameType nTyp = SwFrameType::Root | SwFrameType::Header | SwFrameType::Footer | SwFrameType::FtnCont | + SwFrameType::Ftn | SwFrameType::Fly | + SwFrameType::Tab | SwFrameType::Row | SwFrameType::Cell | + nAdditionalContextType; + do + { if ( pFrame->GetType() & nTyp ) + break; + pFrame = pFrame->GetUpper(); + } while( pFrame ); + return pFrame; +} + +bool IsFrameInSameContext( const SwFrame *pInnerFrame, const SwFrame *pFrame ) +{ + const SwFrame *pContext = FindContext( pInnerFrame, SwFrameType::None ); + + const SwFrameType nTyp = SwFrameType::Root | SwFrameType::Header | SwFrameType::Footer | SwFrameType::FtnCont | + SwFrameType::Ftn | SwFrameType::Fly | + SwFrameType::Tab | SwFrameType::Row | SwFrameType::Cell; + do + { if ( pFrame->GetType() & nTyp ) + { + if( pFrame == pContext ) + return true; + if( pFrame->IsCellFrame() ) + return false; + } + if( pFrame->IsFlyFrame() ) + { + Point aPos( pFrame->getFrameArea().Pos() ); + pFrame = GetVirtualUpper( static_cast<const SwFlyFrame*>(pFrame)->GetAnchorFrame(), aPos ); + } + else + pFrame = pFrame->GetUpper(); + } while( pFrame ); + + return false; +} + +static SwTwips lcl_CalcCellRstHeight( SwLayoutFrame *pCell ) +{ + SwFrame *pLow = pCell->Lower(); + if ( pLow && (pLow->IsContentFrame() || pLow->IsSctFrame()) ) + { + tools::Long nHeight = 0, nFlyAdd = 0; + do + { + tools::Long nLow = pLow->getFrameArea().Height(); + if( pLow->IsTextFrame() && static_cast<SwTextFrame*>(pLow)->IsUndersized() ) + nLow += static_cast<SwTextFrame*>(pLow)->GetParHeight()-pLow->getFramePrintArea().Height(); + else if( pLow->IsSctFrame() && static_cast<SwSectionFrame*>(pLow)->IsUndersized() ) + nLow += static_cast<SwSectionFrame*>(pLow)->Undersize(); + nFlyAdd = std::max( tools::Long(0), nFlyAdd - nLow ); + nFlyAdd = std::max( nFlyAdd, ::CalcHeightWithFlys( pLow ) ); + nHeight += nLow; + pLow = pLow->GetNext(); + } while ( pLow ); + if ( nFlyAdd ) + nHeight += nFlyAdd; + + // The border cannot be calculated based on PrtArea and Frame, since both can be invalid. + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pCell ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + nHeight += rAttrs.CalcTop() + rAttrs.CalcBottom(); + + return pCell->getFrameArea().Height() - nHeight; + } + else + { + tools::Long nRstHeight = 0; + while (pLow && pLow->IsLayoutFrame()) + { + nRstHeight += ::CalcRowRstHeight(static_cast<SwLayoutFrame*>(pLow)); + pLow = pLow->GetNext(); + } + return nRstHeight; + } +} + +SwTwips CalcRowRstHeight( SwLayoutFrame *pRow ) +{ + SwFrame *pLow = pRow->Lower(); + if (!(pLow && pLow->IsLayoutFrame())) + { + return 0; + } + SwTwips nRstHeight = LONG_MAX; + while (pLow && pLow->IsLayoutFrame()) + { + nRstHeight = std::min(nRstHeight, ::lcl_CalcCellRstHeight(static_cast<SwLayoutFrame*>(pLow))); + pLow = pLow->GetNext(); + } + return nRstHeight; +} + +const SwFrame* FindPage( const SwRect &rRect, const SwFrame *pPage ) +{ + if ( !rRect.Overlaps( pPage->getFrameArea() ) ) + { + const SwRootFrame* pRootFrame = static_cast<const SwRootFrame*>(pPage->GetUpper()); + const SwFrame* pTmpPage = pRootFrame ? pRootFrame->GetPageAtPos( rRect.TopLeft(), &rRect.SSize(), true ) : nullptr; + if ( pTmpPage ) + pPage = pTmpPage; + } + + return pPage; +} + +namespace { + +class SwFrameHolder : private SfxListener +{ + SwFrame* m_pFrame; + bool m_bSet; + virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; +public: + SwFrameHolder() + : m_pFrame(nullptr) + , m_bSet(false) + { + } + void SetFrame( SwFrame* pHold ); + SwFrame* GetFrame() { return m_pFrame; } + void Reset(); + bool IsSet() const { return m_bSet; } +}; + +} + +void SwFrameHolder::SetFrame( SwFrame* pHold ) +{ + m_bSet = true; + if (m_pFrame != pHold) + { + if (m_pFrame) + EndListening(*m_pFrame); + StartListening(*pHold); + m_pFrame = pHold; + } +} + +void SwFrameHolder::Reset() +{ + if (m_pFrame) + EndListening(*m_pFrame); + m_bSet = false; + m_pFrame = nullptr; +} + +void SwFrameHolder::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + if (rHint.GetId() == SfxHintId::Dying && &rBC == m_pFrame) + { + m_pFrame = nullptr; + } +} + +SwFrame* GetFrameOfModify(SwRootFrame const*const pLayout, sw::BroadcastingModify const& rMod, + SwFrameType const nFrameType, SwPosition const*const pPos, + std::pair<Point, bool> const*const pViewPosAndCalcFrame) +{ + SwFrame *pMinFrame = nullptr, *pTmpFrame; + SwFrameHolder aHolder; + SwRect aCalcRect; + bool bClientIterChanged = false; + + SwIterator<SwFrame, sw::BroadcastingModify, sw::IteratorMode::UnwrapMulti> aIter(rMod); + do { + pMinFrame = nullptr; + aHolder.Reset(); + sal_uInt64 nMinDist = 0; + bClientIterChanged = false; + + for( pTmpFrame = aIter.First(); pTmpFrame; pTmpFrame = aIter.Next() ) + { + if( pTmpFrame->GetType() & nFrameType && + ( !pLayout || pLayout == pTmpFrame->getRootFrame() ) && + (!pTmpFrame->IsFlowFrame() || + !SwFlowFrame::CastFlowFrame( pTmpFrame )->IsFollow() )) + { + if (pViewPosAndCalcFrame) + { + // watch for Frame being deleted + if ( pMinFrame ) + aHolder.SetFrame( pMinFrame ); + else + aHolder.Reset(); + + if (pViewPosAndCalcFrame->second) + { + // tdf#108118 prevent recursion + DisableCallbackAction a(*pTmpFrame->getRootFrame()); + // - format parent Writer + // fly frame, if it isn't been formatted yet. + // Note: The Writer fly frame could be the frame itself. + SwFlyFrame* pFlyFrame( pTmpFrame->FindFlyFrame() ); + if ( pFlyFrame && + pFlyFrame->getFrameArea().Pos().X() == FAR_AWAY && + pFlyFrame->getFrameArea().Pos().Y() == FAR_AWAY ) + { + SwObjectFormatter::FormatObj( *pFlyFrame ); + } + pTmpFrame->Calc(pLayout ? pLayout->GetCurrShell()->GetOut() : nullptr); + } + + // aIter.IsChanged checks if the current pTmpFrame has been deleted while + // it is the current iterator + // FrameHolder watches for deletion of the current pMinFrame + if( aIter.IsChanged() || ( aHolder.IsSet() && !aHolder.GetFrame() ) ) + { + // restart iteration + bClientIterChanged = true; + break; + } + + // for Flys go via the parent if the Fly is not yet "formatted" + if (!pViewPosAndCalcFrame->second && + pTmpFrame->GetType() & SwFrameType::Fly && + static_cast<SwFlyFrame*>(pTmpFrame)->GetAnchorFrame() && + FAR_AWAY == pTmpFrame->getFrameArea().Pos().getX() && + FAR_AWAY == pTmpFrame->getFrameArea().Pos().getY() ) + aCalcRect = static_cast<SwFlyFrame*>(pTmpFrame)->GetAnchorFrame()->getFrameArea(); + else + aCalcRect = pTmpFrame->getFrameArea(); + + if (aCalcRect.Contains(pViewPosAndCalcFrame->first)) + { + pMinFrame = pTmpFrame; + break; + } + + // Point not in rectangle. Compare distances: + const Point aCalcRectCenter = aCalcRect.Center(); + const Point aDiff = aCalcRectCenter - pViewPosAndCalcFrame->first; + const sal_uInt64 nCurrentDist = sal_Int64(aDiff.getX()) * sal_Int64(aDiff.getX()) + sal_Int64(aDiff.getY()) * sal_Int64(aDiff.getY()); // opt: no sqrt + if ( !pMinFrame || nCurrentDist < nMinDist ) + { + pMinFrame = pTmpFrame; + nMinDist = nCurrentDist; + } + } + else + { + // if no pViewPosAndCalcFrame is provided, take the first one + pMinFrame = pTmpFrame; + break; + } + } + } + } while( bClientIterChanged ); + + if( pPos && pMinFrame && pMinFrame->IsTextFrame() ) + return static_cast<SwTextFrame*>(pMinFrame)->GetFrameAtPos( *pPos ); + + return pMinFrame; +} + +bool IsExtraData( const SwDoc *pDoc ) +{ + const SwLineNumberInfo &rInf = pDoc->GetLineNumberInfo(); + if (rInf.IsPaintLineNumbers() || + rInf.IsCountInFlys() || + (static_cast<sal_Int16>(SW_MOD()->GetRedlineMarkPos()) != text::HoriOrientation::NONE && + !pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty())) + { + return true; + } + + const SwEditShell* pSh = pDoc->GetEditShell(); + const SwViewOption* pViewOptions = pSh ? pSh->GetViewOptions() : nullptr; + return pViewOptions && pViewOptions->IsShowOutlineContentVisibilityButton(); +} + +// OD 22.09.2003 #110978# +SwRect SwPageFrame::PrtWithoutHeaderAndFooter() const +{ + SwRect aPrtWithoutHeaderFooter( getFramePrintArea() ); + aPrtWithoutHeaderFooter.Pos() += getFrameArea().Pos(); + + const SwFrame* pLowerFrame = Lower(); + while ( pLowerFrame ) + { + // Note: independent on text direction page header and page footer are + // always at top respectively at bottom of the page frame. + if ( pLowerFrame->IsHeaderFrame() ) + { + aPrtWithoutHeaderFooter.AddTop( pLowerFrame->getFrameArea().Height() ); + } + if ( pLowerFrame->IsFooterFrame() ) + { + aPrtWithoutHeaderFooter.AddBottom( - pLowerFrame->getFrameArea().Height() ); + } + + pLowerFrame = pLowerFrame->GetNext(); + } + + return aPrtWithoutHeaderFooter; +} + +/** method to determine the spacing values of a frame + + OD 2004-03-10 #i28701# + OD 2009-08-28 #i102458# + Add output parameter <obIsLineSpacingProportional> +*/ +void GetSpacingValuesOfFrame( const SwFrame& rFrame, + SwTwips& onLowerSpacing, + SwTwips& onLineSpacing, + bool& obIsLineSpacingProportional, + bool bIdenticalStyles ) +{ + if ( !rFrame.IsFlowFrame() ) + { + onLowerSpacing = 0; + onLineSpacing = 0; + } + else + { + const SvxULSpaceItem& rULSpace = rFrame.GetAttrSet()->GetULSpace(); + // check contextual spacing if the style of actual and next paragraphs are identical + if (bIdenticalStyles) + onLowerSpacing = (rULSpace.GetContext() ? 0 : rULSpace.GetLower()); + else + onLowerSpacing = rULSpace.GetLower(); + + onLineSpacing = 0; + obIsLineSpacingProportional = false; + if ( rFrame.IsTextFrame() ) + { + onLineSpacing = static_cast<const SwTextFrame&>(rFrame).GetLineSpace(); + obIsLineSpacingProportional = + onLineSpacing != 0 && + static_cast<const SwTextFrame&>(rFrame).GetLineSpace( true ) == 0; + } + + OSL_ENSURE( onLowerSpacing >= 0 && onLineSpacing >= 0, + "<GetSpacingValuesOfFrame(..)> - spacing values aren't positive!" ); + } +} + +/// get the content of the table cell, skipping content from nested tables +const SwContentFrame* GetCellContent( const SwLayoutFrame& rCell ) +{ + const SwContentFrame* pContent = rCell.ContainsContent(); + const SwTabFrame* pTab = rCell.FindTabFrame(); + + while ( pContent && rCell.IsAnLower( pContent ) ) + { + const SwTabFrame* pTmpTab = pContent->FindTabFrame(); + if ( pTmpTab != pTab ) + { + SwFrame const*const pTmp = pTmpTab->FindLastContentOrTable(); + if (pTmp) + { + pContent = pTmp->FindNextCnt(); + } + else + { + pContent = nullptr; + } + } + else + break; + } + return pContent; +} + +SwDeletionChecker::SwDeletionChecker(const SwFrame* pFrame) + : mpFrame( pFrame ) + , mpRegIn( pFrame + ? pFrame->IsTextFrame() + // sw_redlinehide: GetDep() may be a member of SwTextFrame! + ? static_cast<SwTextFrame const*>(pFrame)->GetTextNodeFirst() + : const_cast<SwFrame*>(pFrame)->GetDep() + : nullptr ) +{ +} + +/// Can be used to check if a frame has been deleted +bool SwDeletionChecker::HasBeenDeleted() const +{ + if ( !mpFrame || !mpRegIn ) + return false; + + SwIterator<SwFrame, sw::BroadcastingModify, sw::IteratorMode::UnwrapMulti> aIter(*mpRegIn); + SwFrame* pLast = aIter.First(); + while ( pLast ) + { + if ( pLast == mpFrame ) + return false; + pLast = aIter.Next(); + } + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/ftnfrm.cxx b/sw/source/core/layout/ftnfrm.cxx new file mode 100644 index 000000000..652436eb4 --- /dev/null +++ b/sw/source/core/layout/ftnfrm.cxx @@ -0,0 +1,2987 @@ +/* -*- 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 <txtftn.hxx> +#include <fmtftn.hxx> +#include <ftnidx.hxx> +#include <pagefrm.hxx> +#include <colfrm.hxx> +#include <rootfrm.hxx> +#include <frmtool.hxx> +#include <ftnfrm.hxx> +#include <txtfrm.hxx> +#include <tabfrm.hxx> +#include <pagedesc.hxx> +#include <ftninfo.hxx> +#include <sectfrm.hxx> +#include <objectformatter.hxx> +#include <viewopt.hxx> +#include <calbck.hxx> +#include <ndindex.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <IDocumentSettingAccess.hxx> + +#define ENDNOTE 0x80000000 + +/// Search the position of an attribute in the FootnoteArray at the document, +/// because all footnotes are located there, ordered by their index. +static sal_uLong lcl_FindFootnotePos( const SwDoc *pDoc, const SwTextFootnote *pAttr ) +{ + const SwFootnoteIdxs &rFootnoteIdxs = pDoc->GetFootnoteIdxs(); + + SwTextFootnote* pBla = const_cast<SwTextFootnote*>(pAttr); + SwFootnoteIdxs::const_iterator it = rFootnoteIdxs.find( pBla ); + if ( it != rFootnoteIdxs.end() ) + { + sal_uLong nRet = it - rFootnoteIdxs.begin(); + if( pAttr->GetFootnote().IsEndNote() ) + return nRet + ENDNOTE; + return nRet; + } + OSL_ENSURE( !pDoc, "FootnotePos not found." ); + return 0; +} + +bool SwFootnoteFrame::operator<( const SwTextFootnote* pTextFootnote ) const +{ + const SwDoc* pDoc = GetFormat()->GetDoc(); + OSL_ENSURE( pDoc, "SwFootnoteFrame: Missing doc!" ); + return lcl_FindFootnotePos( pDoc, GetAttr() ) < + lcl_FindFootnotePos( pDoc, pTextFootnote ); +} + +/* +|* +|* bool lcl_NextFootnoteBoss( SwFootnoteBossFrame* pBoss, SwPageFrame* pPage) +|* sets pBoss on the next SwFootnoteBossFrame, which can either be a column +|* or a page (without columns). If the page changes meanwhile, +|* pPage contains the new page and this function returns true. +|* +|*/ + +static bool lcl_NextFootnoteBoss( SwFootnoteBossFrame* &rpBoss, SwPageFrame* &rpPage, + bool bDontLeave ) +{ + if( rpBoss->IsColumnFrame() ) + { + if( rpBoss->GetNext() ) + { + rpBoss = static_cast<SwFootnoteBossFrame*>(rpBoss->GetNext()); //next column + return false; + } + if( rpBoss->IsInSct() ) + { + SwSectionFrame* pSct = rpBoss->FindSctFrame()->GetFollow(); + if( pSct ) + { + OSL_ENSURE( pSct->Lower() && pSct->Lower()->IsColumnFrame(), + "Where's the column?" ); + rpBoss = static_cast<SwColumnFrame*>(pSct->Lower()); + SwPageFrame* pOld = rpPage; + rpPage = pSct->FindPageFrame(); + return pOld != rpPage; + } + else if( bDontLeave ) + { + rpPage = nullptr; + rpBoss = nullptr; + return false; + } + } + } + rpPage = static_cast<SwPageFrame*>(rpPage->GetNext()); // next page + rpBoss = rpPage; + if( rpPage ) + { + SwLayoutFrame* pBody = rpPage->FindBodyCont(); + if( pBody && pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + rpBoss = static_cast<SwFootnoteBossFrame*>(pBody->Lower()); // first column + } + return true; +} + +/// @returns column number if pBoss is a column, otherwise 0. +static sal_uInt16 lcl_ColumnNum( const SwFrame* pBoss ) +{ + sal_uInt16 nRet = 0; + if( !pBoss->IsColumnFrame() ) + return 0; + const SwFrame* pCol; + if( pBoss->IsInSct() ) + { + pCol = pBoss->GetUpper()->FindColFrame(); + if( pBoss->GetNext() || pBoss->GetPrev() ) + { + while( pBoss ) + { + ++nRet; // Section columns + pBoss = pBoss->GetPrev(); + } + } + } + else + pCol = pBoss; + while( pCol ) + { + nRet += 256; // Page columns + pCol = pCol->GetPrev(); + } + return nRet; +} + +SwFootnoteContFrame::SwFootnoteContFrame( SwFrameFormat *pFormat, SwFrame* pSib ): + SwLayoutFrame( pFormat, pSib ) +{ + mnFrameType = SwFrameType::FtnCont; +} + +SwFootnoteFrame* SwFootnoteContFrame::AddChained(bool bAppend, SwFrame* pThis, bool bDefaultFormat) +{ + SwFootnoteFrame *pOld = pThis->FindFootnoteFrame(); + SwFrameFormat *pFormat = pOld->GetFormat(); + if (bDefaultFormat) + pFormat = pFormat->GetDoc()->GetDfltFrameFormat(); + + SwFootnoteFrame *pNew = new SwFootnoteFrame(pFormat, pOld, pOld->GetRef(), pOld->GetAttr()); + + if (bAppend) + { + if (pOld->GetFollow()) + { + pNew->SetFollow(pOld->GetFollow()); + pOld->GetFollow()->SetMaster(pNew); + } + pOld->SetFollow(pNew); + pNew->SetMaster(pOld); + } + else + { + if (pOld->GetMaster()) + { + pNew->SetMaster(pOld->GetMaster()); + pOld->GetMaster()->SetFollow(pNew); + } + pNew->SetFollow(pOld); + pOld->SetMaster(pNew); + } + + return pNew; +} + +// lcl_Undersize(..) walks over a SwFrame and its contents +// and returns the sum of all requested TextFrame magnifications. + +static tools::Long lcl_Undersize( const SwFrame* pFrame ) +{ + tools::Long nRet = 0; + SwRectFnSet aRectFnSet(pFrame); + if( pFrame->IsTextFrame() ) + { + if( static_cast<const SwTextFrame*>(pFrame)->IsUndersized() ) + { + // Does this TextFrame would like to be a little bit bigger? + nRet = static_cast<const SwTextFrame*>(pFrame)->GetParHeight() - + aRectFnSet.GetHeight(pFrame->getFramePrintArea()); + if( nRet < 0 ) + nRet = 0; + } + } + else if( pFrame->IsLayoutFrame() ) + { + const SwFrame* pNxt = static_cast<const SwLayoutFrame*>(pFrame)->Lower(); + while( pNxt ) + { + nRet += lcl_Undersize( pNxt ); + pNxt = pNxt->GetNext(); + } + } + return nRet; +} + +namespace sw { + +SwTwips FootnoteSeparatorHeight(SwPageFootnoteInfo const& rInf) +{ + return rInf.GetTopDist() + rInf.GetBottomDist() + rInf.GetLineWidth(); +} + +} // namespace sw + +/// "format" the frame (Fixsize is not set here). +void SwFootnoteContFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs * ) +{ + // calculate total border, only one distance to the top + const SwPageFrame* pPage = FindPageFrame(); + const SwPageFootnoteInfo &rInf = pPage->GetPageDesc()->GetFootnoteInfo(); + const SwTwips nBorder = sw::FootnoteSeparatorHeight(rInf); + SwRectFnSet aRectFnSet(this); + + if ( !isFramePrintAreaValid() ) + { + setFramePrintAreaValid(true); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + + aRectFnSet.SetTop( aPrt, nBorder ); + aRectFnSet.SetWidth( aPrt, aRectFnSet.GetWidth(getFrameArea()) ); + aRectFnSet.SetHeight(aPrt, aRectFnSet.GetHeight(getFrameArea()) - nBorder ); + + if( aRectFnSet.GetHeight(aPrt) < 0 && !pPage->IsFootnotePage() ) + { + setFrameAreaSizeValid(false); + } + } + + if ( isFrameAreaSizeValid() ) + return; + + bool bGrow = pPage->IsFootnotePage(); + if( bGrow ) + { + const SwViewShell *pSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + bGrow = false; + } + if( bGrow ) + Grow( LONG_MAX ); + else + { + // VarSize is determined based on the content plus the borders + SwTwips nRemaining = 0; + SwFrame *pFrame = m_pLower; + while ( pFrame ) + { // lcl_Undersize(..) respects (recursively) TextFrames, which + // would like to be bigger. They are created especially in + // columnized borders, if these do not have their maximum + // size yet. + nRemaining += aRectFnSet.GetHeight(pFrame->getFrameArea()) + lcl_Undersize( pFrame ); + pFrame = pFrame->GetNext(); + } + // add the own border + nRemaining += nBorder; + + SwTwips nDiff; + if( IsInSct() ) + { + nDiff = -aRectFnSet.BottomDist( getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper()) ); + if( nDiff > 0 ) + { + if( nDiff > aRectFnSet.GetHeight(getFrameArea()) ) + { + nDiff = aRectFnSet.GetHeight(getFrameArea()); + } + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, -nDiff ); + aRectFnSet.AddHeight( aFrm, -nDiff ); + } + } + nDiff = aRectFnSet.GetHeight(getFrameArea()) - nRemaining; + if ( nDiff > 0 ) + Shrink( nDiff ); + else if ( nDiff < 0 ) + { + Grow( -nDiff ); + // It may happen that there is less space available, + // than what the border needs - the size of the PrtArea + // will then be negative. + SwTwips nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + if( nPrtHeight < 0 ) + { + const SwTwips nTmpDiff = std::max( SwTwips(aRectFnSet.GetTop(getFramePrintArea())), -nPrtHeight ); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SubTop( aPrt, nTmpDiff ); + } + } + } + + setFrameAreaSizeValid(true); +} + +SwTwips SwFootnoteContFrame::GrowFrame( SwTwips nDist, bool bTst, bool ) +{ + // No check if FixSize since FootnoteContainer are variable up to their max. height. + // If the max. height is LONG_MAX, take as much space as needed. + // If the page is a special footnote page, take also as much as possible. + assert(GetUpper() && GetUpper()->IsFootnoteBossFrame()); + + SwRectFnSet aRectFnSet(this); + if( aRectFnSet.GetHeight(getFrameArea()) > 0 && + nDist > ( LONG_MAX - aRectFnSet.GetHeight(getFrameArea()) ) ) + nDist = LONG_MAX - aRectFnSet.GetHeight(getFrameArea()); + + SwFootnoteBossFrame *pBoss = static_cast<SwFootnoteBossFrame*>(GetUpper()); + if( IsInSct() ) + { + SwSectionFrame* pSect = FindSctFrame(); + OSL_ENSURE( pSect, "GrowFrame: Missing SectFrame" ); + // In a section, which has to maximize, a footnotecontainer is allowed + // to grow, when the section can't grow anymore. + if( !bTst && !pSect->IsColLocked() && + pSect->ToMaximize( false ) && pSect->Growable() ) + { + pSect->InvalidateSize(); + return 0; + } + } + const SwViewShell *pSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + SwPageFrame *pPage = pBoss->FindPageFrame(); + if ( bBrowseMode || !pPage->IsFootnotePage() ) + { + if ( pBoss->GetMaxFootnoteHeight() != LONG_MAX ) + { + nDist = std::min( nDist, + SwTwips(pBoss->GetMaxFootnoteHeight() - aRectFnSet.GetHeight(getFrameArea())) ); + if ( nDist <= 0 ) + return 0; + } + // FootnoteBoss also influences the max value + if( !IsInSct() ) + { + const SwTwips nMax = pBoss->GetVarSpace(); + if ( nDist > nMax ) + nDist = nMax; + if ( nDist <= 0 ) + return 0; + } + } + else if( nDist > aRectFnSet.GetHeight(GetPrev()->getFrameArea()) ) + // do not use more space than the body has + nDist = aRectFnSet.GetHeight(GetPrev()->getFrameArea()); + + tools::Long nAvail = 0; + if ( bBrowseMode ) + { + nAvail = GetUpper()->getFramePrintArea().Height(); + const SwFrame *pAvail = GetUpper()->Lower(); + do + { nAvail -= pAvail->getFrameArea().Height(); + pAvail = pAvail->GetNext(); + } while ( pAvail ); + if ( nAvail > nDist ) + nAvail = nDist; + } + + if ( !bTst ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, aRectFnSet.GetHeight(aFrm) + nDist ); + + if( IsVertical() && !IsVertLR() ) + { + aFrm.Pos().AdjustX( -nDist ); + } + } + tools::Long nGrow = nDist - nAvail, + nReal = 0; + if ( nGrow > 0 ) + { + SwNeighbourAdjust nAdjust = pBoss->NeighbourhoodAdjustment(); + if( SwNeighbourAdjust::OnlyAdjust == nAdjust ) + nReal = AdjustNeighbourhood( nGrow, bTst ); + else + { + if( SwNeighbourAdjust::GrowAdjust == nAdjust ) + { + SwFrame* pFootnote = Lower(); + if( pFootnote ) + { + while( pFootnote->GetNext() ) + pFootnote = pFootnote->GetNext(); + if( static_cast<SwFootnoteFrame*>(pFootnote)->GetAttr()->GetFootnote().IsEndNote() ) + { + nReal = AdjustNeighbourhood( nGrow, bTst ); + nAdjust = SwNeighbourAdjust::GrowShrink; // no more AdjustNeighbourhood + } + } + } + nReal += pBoss->Grow( nGrow - nReal, bTst ); + if( ( SwNeighbourAdjust::GrowAdjust == nAdjust || SwNeighbourAdjust::AdjustGrow == nAdjust ) + && nReal < nGrow ) + nReal += AdjustNeighbourhood( nGrow - nReal, bTst ); + } + } + + nReal += nAvail; + + if ( !bTst ) + { + if ( nReal != nDist ) + { + nDist -= nReal; + + // We can only respect the boundless wish so much + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.AddHeight( -nDist ); + + if( IsVertical() && !IsVertLR() ) + { + aFrm.Pos().AdjustX(nDist ); + } + } + + // growing happens upwards, so successors to not need to be invalidated + if( nReal ) + { + InvalidateSize_(); + InvalidatePos_(); + InvalidatePage( pPage ); + } + } + return nReal; +} + +SwTwips SwFootnoteContFrame::ShrinkFrame( SwTwips nDiff, bool bTst, bool bInfo ) +{ + SwPageFrame *pPage = FindPageFrame(); + bool bShrink = false; + if ( pPage ) + { + if( !pPage->IsFootnotePage() ) + bShrink = true; + else + { + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + bShrink = true; + } + } + if( bShrink ) + { + SwTwips nRet = SwLayoutFrame::ShrinkFrame( nDiff, bTst, bInfo ); + if( IsInSct() && !bTst ) + FindSctFrame()->InvalidateNextPos(); + if ( !bTst && nRet ) + { + InvalidatePos_(); + InvalidatePage( pPage ); + } + return nRet; + } + return 0; +} + +SwFootnoteFrame::SwFootnoteFrame( SwFrameFormat *pFormat, SwFrame* pSib, SwContentFrame *pCnt, SwTextFootnote *pAt ): + SwLayoutFrame( pFormat, pSib ), + mpFollow( nullptr ), + mpMaster( nullptr ), + mpReference( pCnt ), + mpAttribute( pAt ), + mbBackMoveLocked( false ), + // #i49383# + mbUnlockPosOfLowerObjs( true ) +{ + mnFrameType = SwFrameType::Ftn; +} + +void SwFootnoteFrame::InvalidateNxtFootnoteCnts( SwPageFrame const *pPage ) +{ + if ( !GetNext() ) + return; + + SwFrame *pCnt = static_cast<SwLayoutFrame*>(GetNext())->ContainsAny(); + if( !pCnt ) + return; + + pCnt->InvalidatePage( pPage ); + pCnt->InvalidatePrt_(); + do + { pCnt->InvalidatePos_(); + if( pCnt->IsSctFrame() ) + { + SwFrame* pTmp = static_cast<SwSectionFrame*>(pCnt)->ContainsAny(); + if( pTmp ) + pTmp->InvalidatePos_(); + } + pCnt->GetUpper()->InvalidateSize_(); + pCnt = pCnt->FindNext(); + } while ( pCnt && GetUpper()->IsAnLower( pCnt ) ); +} + +bool SwFootnoteFrame::IsDeleteForbidden() const +{ + if (SwLayoutFrame::IsDeleteForbidden()) + return true; + // needs to be in sync with the ::Cut logic + const SwLayoutFrame *pUp = GetUpper(); + if (pUp) + { + if (GetPrev()) + return false; + + // The last footnote takes its container along if it + // is deleted. Cut would put pUp->Lower() to the value + // of GetNext(), so if there is no GetNext then + // Cut would delete pUp. If that condition is true + // here then check if the container is delete-forbidden + return !GetNext() && pUp->IsDeleteForbidden(); + } + return false; +} + +void SwFootnoteFrame::Cut() +{ + if ( GetNext() ) + GetNext()->InvalidatePos(); + else if ( GetPrev() ) + GetPrev()->SetRetouche(); + + // first move then shrink Upper + SwLayoutFrame *pUp = GetUpper(); + + // correct chaining + SwFootnoteFrame *pFootnote = this; + if ( pFootnote->GetFollow() ) + pFootnote->GetFollow()->SetMaster( pFootnote->GetMaster() ); + if ( pFootnote->GetMaster() ) + pFootnote->GetMaster()->SetFollow( pFootnote->GetFollow() ); + pFootnote->SetFollow( nullptr ); + pFootnote->SetMaster( nullptr ); + + // cut all connections + RemoveFromLayout(); + + if ( !pUp ) + return; + + // The last footnote takes its container along + if (!pUp->Lower()) + { + SwPageFrame *pPage = pUp->FindPageFrame(); + if ( pPage ) + { + SwLayoutFrame *pBody = pPage->FindBodyCont(); + if( pBody && !pBody->ContainsContent() ) + pPage->getRootFrame()->SetSuperfluous(); + } + SwSectionFrame* pSect = pUp->FindSctFrame(); + pUp->Cut(); + SwFrame::DestroyFrame(pUp); + // If the last footnote container was removed from a column + // section without a Follow, then this section can be shrunk. + if( pSect && !pSect->ToMaximize( false ) && !pSect->IsColLocked() ) + pSect->InvalidateSize_(); + } + else + { if ( getFrameArea().Height() ) + pUp->Shrink( getFrameArea().Height() ); + pUp->SetCompletePaint(); + pUp->InvalidatePage(); + } +} + +void SwFootnoteFrame::Paste( SwFrame* pParent, SwFrame* pSibling ) +{ + OSL_ENSURE( pParent, "no parent in Paste." ); + OSL_ENSURE( pParent->IsLayoutFrame(), "Parent is ContentFrame." ); + OSL_ENSURE( pParent != this, "I am my own parent." ); + OSL_ENSURE( pSibling != this, "I am my own sibling." ); + OSL_ENSURE( !GetPrev() && !GetNext() && !GetUpper(), + "I am still somewhere registered." ); + + // insert into tree structure + InsertBefore( static_cast<SwLayoutFrame*>(pParent), pSibling ); + + SwRectFnSet aRectFnSet(this); + if( aRectFnSet.GetWidth(getFrameArea())!=aRectFnSet.GetWidth(pParent->getFramePrintArea()) ) + InvalidateSize_(); + InvalidatePos_(); + if (SwFrame *const pContent = ContainsContent()) + { // tdf#139687 invalidate possibly stale top margin (computed from previous frame) + pContent->InvalidatePrt_(); + } + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + if (SwFootnoteFrame *const pNext = static_cast<SwFootnoteFrame *>(GetNext())) + { + pNext->InvalidatePos_(); + if (SwFrame *const pContent = pNext->ContainsContent()) + { // tdf#139687 invalidate possibly stale top margin (computed from previous frame) + pContent->InvalidatePrt_(); + } + } + if( aRectFnSet.GetHeight(getFrameArea()) ) + pParent->Grow( aRectFnSet.GetHeight(getFrameArea()) ); + + // If the predecessor is the master and/or the successor is the Follow, + // then take their content and destroy them. + if ( GetPrev() && GetPrev() == GetMaster() ) + { + OSL_ENSURE( SwFlowFrame::CastFlowFrame( GetPrev()->GetLower() ), + "Footnote without content?" ); + SwFlowFrame::CastFlowFrame( GetPrev()->GetLower())-> + MoveSubTree( this, GetLower() ); + SwFrame *pDel = GetPrev(); + assert(pDel != this); + pDel->Cut(); + SwFrame::DestroyFrame(pDel); + } + if ( GetNext() && GetNext() == GetFollow() ) + { + OSL_ENSURE( SwFlowFrame::CastFlowFrame( GetNext()->GetLower() ), + "Footnote without content?" ); + SwFlowFrame::CastFlowFrame( GetNext()->GetLower() )->MoveSubTree( this ); + SwFrame *pDel = GetNext(); + assert(pDel != this); + pDel->Cut(); + SwFrame::DestroyFrame(pDel); + } +#if OSL_DEBUG_LEVEL > 0 + SwDoc *pDoc = GetFormat()->GetDoc(); + if ( GetPrev() ) + { + OSL_ENSURE( lcl_FindFootnotePos( pDoc, static_cast<SwFootnoteFrame*>(GetPrev())->GetAttr() ) <= + lcl_FindFootnotePos( pDoc, GetAttr() ), "Prev is not FootnotePrev" ); + } + if ( GetNext() ) + { + OSL_ENSURE( lcl_FindFootnotePos( pDoc, GetAttr() ) <= + lcl_FindFootnotePos( pDoc, static_cast<SwFootnoteFrame*>(GetNext())->GetAttr() ), + "Next is not FootnoteNext" ); + } +#endif + InvalidateNxtFootnoteCnts( pPage ); +} + +/// Return the next layout leaf in that the frame can be moved. +/// New pages will only be created if specified by the parameter. +SwLayoutFrame *SwFrame::GetNextFootnoteLeaf( MakePageType eMakePage ) +{ + SwFootnoteBossFrame *pOldBoss = FindFootnoteBossFrame(); + SwPageFrame* pOldPage = pOldBoss->FindPageFrame(); + SwPageFrame* pPage; + SwFootnoteBossFrame *pBoss = pOldBoss->IsColumnFrame() ? + static_cast<SwFootnoteBossFrame*>(pOldBoss->GetNext()) : nullptr; // next column, if existing + if( pBoss ) + pPage = nullptr; + else + { + if( pOldBoss->GetUpper()->IsSctFrame() ) + { // this can only be in a column area + SwLayoutFrame* pNxt = pOldBoss->GetNextSctLeaf( eMakePage ); + if( pNxt ) + { + OSL_ENSURE( pNxt->IsColBodyFrame(), "GetNextFootnoteLeaf: Funny Leaf" ); + pBoss = static_cast<SwFootnoteBossFrame*>(pNxt->GetUpper()); + pPage = pBoss->FindPageFrame(); + } + else + return nullptr; + } + else + { + // next page + pPage = static_cast<SwPageFrame*>(pOldPage->GetNext()); + // skip empty pages + if( pPage && pPage->IsEmptyPage() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + pBoss = pPage; + } + } + // What do we have until here? + // pBoss != NULL, pPage==NULL => pBoss is the next column on the same page + // pBoss != NULL, pPage!=NULL => pBoss and pPage are the following page (empty pages skipped) + // pBoss == NULL => pPage == NULL, so there are no following pages + + // If the footnote has already a Follow we do not need to search. + // However, if there are unwanted empty columns/pages between Footnote and Follow, + // create another Follow on the next best column/page and the rest will sort itself out. + SwFootnoteFrame *pFootnote = FindFootnoteFrame(); + if ( pFootnote && pFootnote->GetFollow() ) + { + SwFootnoteBossFrame* pTmpBoss = pFootnote->GetFollow()->FindFootnoteBossFrame(); + // Following cases will be handled: + // 1. both "FootnoteBoss"es are neighboring columns/pages + // 2. the new one is the first column of a neighboring page + // 3. the new one is the first column in a section of the next page + while( pTmpBoss != pBoss && pTmpBoss && !pTmpBoss->GetPrev() ) + pTmpBoss = pTmpBoss->GetUpper()->FindFootnoteBossFrame(); + if( pTmpBoss == pBoss ) + return pFootnote->GetFollow(); + } + + // If no pBoss could be found or it is a "wrong" page, we need a new page. + if ( !pBoss || ( pPage && pPage->IsEndNotePage() && !pOldPage->IsEndNotePage() ) ) + { + if ( eMakePage == MAKEPAGE_APPEND || eMakePage == MAKEPAGE_INSERT ) + { + pBoss = InsertPage( pOldPage, pOldPage->IsFootnotePage() ); + static_cast<SwPageFrame*>(pBoss)->SetEndNotePage( pOldPage->IsEndNotePage() ); + } + else + return nullptr; + } + if( pBoss->IsPageFrame() ) + { + // If this page has columns, then go to the first one + SwLayoutFrame* pLay = pBoss->FindBodyCont(); + if( pLay && pLay->Lower() && pLay->Lower()->IsColumnFrame() ) + pBoss = static_cast<SwFootnoteBossFrame*>(pLay->Lower()); + } + // found column/page - add myself + SwFootnoteContFrame *pCont = pBoss->FindFootnoteCont(); + if ( !pCont && pBoss->GetMaxFootnoteHeight() && + ( eMakePage == MAKEPAGE_APPEND || eMakePage == MAKEPAGE_INSERT ) ) + pCont = pBoss->MakeFootnoteCont(); + return pCont; +} + +/// Get the preceding layout leaf in that the frame can be moved. +SwLayoutFrame *SwFrame::GetPrevFootnoteLeaf( MakePageType eMakeFootnote ) +{ + // The predecessor of a footnote is (if possible) + // the master of the chain of the footnote. + SwFootnoteFrame *pFootnote = FindFootnoteFrame(); + SwLayoutFrame *pRet = pFootnote->GetMaster(); + + SwFootnoteBossFrame* pOldBoss = FindFootnoteBossFrame(); + SwPageFrame *pOldPage = pOldBoss->FindPageFrame(); + + if ( !pOldBoss->GetPrev() && !pOldPage->GetPrev() ) + return pRet; // there is neither a predecessor column nor page + + if ( !pRet ) + { + bool bEndn = pFootnote->GetAttr()->GetFootnote().IsEndNote(); + SwFrame* pTmpRef = nullptr; + const IDocumentSettingAccess& rSettings + = pFootnote->GetAttrSet()->GetDoc()->getIDocumentSettingAccess(); + if( bEndn && pFootnote->IsInSct() ) + { + SwSectionFrame* pSect = pFootnote->FindSctFrame(); + if( pSect->IsEndnAtEnd() ) + // Endnotes at the end of the section. + pTmpRef = pSect->FindLastContent( SwFindMode::LastCnt ); + } + else if (bEndn && rSettings.get(DocumentSettingId::CONTINUOUS_ENDNOTES)) + { + // Endnotes at the end of the document. + SwPageFrame* pPage = getRootFrame()->GetLastPage(); + assert(pPage); + SwFrame* pPrevPage = pPage->GetPrev(); + if (pPrevPage) + { + // Have a last but one page, use that since we try to get a preceding frame. + assert(pPrevPage->IsPageFrame()); + pPage = static_cast<SwPageFrame*>(pPrevPage); + } + pTmpRef = pPage->FindLastBodyContent(); + } + if( !pTmpRef ) + // Endnotes on a separate page. + pTmpRef = pFootnote->GetRef(); + SwFootnoteBossFrame* pStop = pTmpRef->FindFootnoteBossFrame( !bEndn ); + + const sal_uInt16 nNum = pStop->GetPhyPageNum(); + + // Do not leave the corresponding page if the footnote should + // be shown at the document ending or the footnote is an endnote. + const bool bEndNote = pOldPage->IsEndNotePage(); + const bool bFootnoteEndDoc = pOldPage->IsFootnotePage(); + SwFootnoteBossFrame* pNxtBoss = pOldBoss; + SwSectionFrame *pSect = pNxtBoss->GetUpper()->IsSctFrame() ? + static_cast<SwSectionFrame*>(pNxtBoss->GetUpper()) : nullptr; + + do + { + if( pNxtBoss->IsColumnFrame() && pNxtBoss->GetPrev() ) + pNxtBoss = static_cast<SwFootnoteBossFrame*>(pNxtBoss->GetPrev()); // one column backwards + else // one page backwards + { + SwLayoutFrame* pBody = nullptr; + if( pSect ) + { + if( pSect->IsFootnoteLock() ) + { + if( pNxtBoss == pOldBoss ) + return nullptr; + pStop = pNxtBoss; + } + else + { + pSect = pSect->FindMaster(); + if( !pSect || !pSect->Lower() ) + return nullptr; + OSL_ENSURE( pSect->Lower()->IsColumnFrame(), + "GetPrevFootnoteLeaf: Where's the column?" ); + pNxtBoss = static_cast<SwFootnoteBossFrame*>(pSect->Lower()); + pBody = pSect; + } + } + else + { + SwPageFrame* pPage = static_cast<SwPageFrame*>(pNxtBoss->FindPageFrame()->GetPrev()); + if( !pPage || pPage->GetPhyPageNum() < nNum || + bEndNote != pPage->IsEndNotePage() || bFootnoteEndDoc != pPage->IsFootnotePage() ) + return nullptr; // no further pages found + pNxtBoss = pPage; + pBody = pPage->FindBodyCont(); + } + // We have the previous page, we might need to find the last column of it + if( pBody ) + { + if ( pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + { + pNxtBoss = static_cast<SwFootnoteBossFrame*>(pBody->GetLastLower()); + } + } + } + SwFootnoteContFrame *pCont = pNxtBoss->FindFootnoteCont(); + if ( pCont ) + { + pRet = pCont; + break; + } + if ( pStop == pNxtBoss ) + { + // Reached the column/page of the reference. + // Try to add a container and paste our content. + if ( eMakeFootnote == MAKEPAGE_FTN && pNxtBoss->GetMaxFootnoteHeight() ) + pRet = pNxtBoss->MakeFootnoteCont(); + break; + } + } while( !pRet ); + } + if ( pRet ) + { + const SwFootnoteBossFrame* pNewBoss = pRet->FindFootnoteBossFrame(); + bool bJump = false; + if( pOldBoss->IsColumnFrame() && pOldBoss->GetPrev() ) // a previous column exists + bJump = pOldBoss->GetPrev() != static_cast<SwFrame const *>(pNewBoss); // did we chose it? + else if( pNewBoss->IsColumnFrame() && pNewBoss->GetNext() ) + bJump = true; // there is another column after the boss (not the old boss) + else + { + // Will be reached only if old and new boss are both either pages or the last (new) + // or first (old) column of a page. In this case, check if pages were skipped. + const sal_uInt16 nDiff = pOldPage->GetPhyPageNum() - pRet->FindPageFrame()->GetPhyPageNum(); + if ( nDiff > 2 || + (nDiff > 1 && !static_cast<SwPageFrame*>(pOldPage->GetPrev())->IsEmptyPage()) ) + bJump = true; + } + if( bJump ) + SwFlowFrame::SetMoveBwdJump( true ); + } + return pRet; +} + +bool SwFrame::IsFootnoteAllowed() const +{ + if ( !IsInDocBody() ) + return false; + + if ( IsInTab() ) + { + // no footnotes in repeated headlines + const SwTabFrame *pTab = const_cast<SwFrame*>(this)->ImplFindTabFrame(); + assert(pTab); + if ( pTab->IsFollow() ) + return !pTab->IsInHeadline( *this ); + } + return true; +} + +void SwRootFrame::UpdateFootnoteNums() +{ + // page numbering only if set at the document + if ( GetFormat()->GetDoc()->GetFootnoteInfo().m_eNum == FTNNUM_PAGE ) + { + SwPageFrame *pPage = static_cast<SwPageFrame*>(Lower()); + while ( pPage && !pPage->IsFootnotePage() ) + { + pPage->UpdateFootnoteNum(); + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + } +} + +/// remove all footnotes (not the references) and all footnote pages +void sw_RemoveFootnotes( SwFootnoteBossFrame* pBoss, bool bPageOnly, bool bEndNotes ) +{ + do + { + SwFootnoteContFrame *pCont = pBoss->FindFootnoteCont(); + if ( pCont ) + { + SwFootnoteFrame *pFootnote = static_cast<SwFootnoteFrame*>(pCont->Lower()); + assert(pFootnote); + if ( bPageOnly ) + while ( pFootnote->GetMaster() ) + pFootnote = pFootnote->GetMaster(); + do + { + SwFootnoteFrame *pNxt = static_cast<SwFootnoteFrame*>(pFootnote->GetNext()); + if ( !pFootnote->GetAttr()->GetFootnote().IsEndNote() || + bEndNotes ) + { + pFootnote->GetRef()->Prepare( PrepareHint::FootnoteInvalidation, static_cast<void*>(pFootnote->GetAttr()) ); + if ( bPageOnly && !pNxt ) + pNxt = pFootnote->GetFollow(); + pFootnote->Cut(); + SwFrame::DestroyFrame(pFootnote); + } + pFootnote = pNxt; + + } while ( pFootnote ); + } + if( !pBoss->IsInSct() ) + { + // A sectionframe with the Footnote/EndnAtEnd-flags may contain + // foot/endnotes. If the last lower frame of the bodyframe is + // a multicolumned sectionframe, it may contain footnotes, too. + SwLayoutFrame* pBody = pBoss->FindBodyCont(); + if( pBody && pBody->Lower() ) + { + SwFrame* pLow = pBody->Lower(); + while (pLow) + { + if( pLow->IsSctFrame() && ( !pLow->GetNext() || + static_cast<SwSectionFrame*>(pLow)->IsAnyNoteAtEnd() ) && + static_cast<SwSectionFrame*>(pLow)->Lower() && + static_cast<SwSectionFrame*>(pLow)->Lower()->IsColumnFrame() ) + sw_RemoveFootnotes( static_cast<SwColumnFrame*>(static_cast<SwSectionFrame*>(pLow)->Lower()), + bPageOnly, bEndNotes ); + pLow = pLow->GetNext(); + } + } + } + // is there another column? + pBoss = pBoss->IsColumnFrame() ? static_cast<SwColumnFrame*>(pBoss->GetNext()) : nullptr; + } while( pBoss ); +} + +void SwRootFrame::RemoveFootnotes( SwPageFrame *pPage, bool bPageOnly, bool bEndNotes ) +{ + if ( !pPage ) + pPage = static_cast<SwPageFrame*>(Lower()); + + do + { // On columned pages we have to clean up in all columns + SwFootnoteBossFrame* pBoss; + SwLayoutFrame* pBody = pPage->FindBodyCont(); + if( pBody && pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + pBoss = static_cast<SwFootnoteBossFrame*>(pBody->Lower()); // the first column + else + pBoss = pPage; // no columns + sw_RemoveFootnotes( pBoss, bPageOnly, bEndNotes ); + if ( !bPageOnly ) + { + if ( pPage->IsFootnotePage() && + (!pPage->IsEndNotePage() || bEndNotes) ) + { + SwFrame *pDel = pPage; + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + pDel->Cut(); + SwFrame::DestroyFrame(pDel); + } + else + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + else + break; + + } while ( pPage ); +} + +/// Change the page template of the footnote pages +void SwRootFrame::CheckFootnotePageDescs( bool bEndNote ) +{ + SwPageFrame *pPage = static_cast<SwPageFrame*>(Lower()); + while ( pPage && !pPage->IsFootnotePage() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + while ( pPage && pPage->IsEndNotePage() != bEndNote ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + + if ( pPage ) + SwFrame::CheckPageDescs( pPage, false ); +} + +/** Insert a footnote container + * + * A footnote container is always placed directly behind the body text. + * + * The frame format (FrameFormat) is always the default frame format. + * + * @return footnote container frame + */ +SwFootnoteContFrame *SwFootnoteBossFrame::MakeFootnoteCont() +{ + SAL_WARN_IF(FindFootnoteCont(), "sw.core", "footnote container exists already"); + + SwFootnoteContFrame *pNew = new SwFootnoteContFrame( GetFormat()->GetDoc()->GetDfltFrameFormat(), this ); + SwLayoutFrame *pLay = FindBodyCont(); + pNew->Paste( this, pLay->GetNext() ); + return pNew; +} + +SwFootnoteContFrame *SwFootnoteBossFrame::FindFootnoteCont() +{ + SwFrame *pFrame = Lower(); + while( pFrame && !pFrame->IsFootnoteContFrame() ) + pFrame = pFrame->GetNext(); + +#if OSL_DEBUG_LEVEL > 0 + if ( pFrame ) + { + SwFrame *pFootnote = pFrame->GetLower(); + assert(pFootnote); + while ( pFootnote ) + { + assert(pFootnote->IsFootnoteFrame() && "Neighbor of footnote must be a footnote"); + pFootnote = pFootnote->GetNext(); + } + } +#endif + + return static_cast<SwFootnoteContFrame*>(pFrame); +} + +/// Search the next available footnote container. +SwFootnoteContFrame *SwFootnoteBossFrame::FindNearestFootnoteCont( bool bDontLeave ) +{ + SwFootnoteContFrame *pCont = nullptr; + if ( !GetFormat()->GetDoc()->GetFootnoteIdxs().empty() ) + { + pCont = FindFootnoteCont(); + if ( !pCont ) + { + SwPageFrame *pPage = FindPageFrame(); + SwFootnoteBossFrame* pBoss = this; + bool bEndNote = pPage->IsEndNotePage(); + do + { + bool bChgPage = lcl_NextFootnoteBoss( pBoss, pPage, bDontLeave ); + // Found another boss? When changing pages, also the endnote flag must match. + if( pBoss && ( !bChgPage || pPage->IsEndNotePage() == bEndNote ) ) + pCont = pBoss->FindFootnoteCont(); + } while ( !pCont && pPage ); + } + } + return pCont; +} + +SwFootnoteFrame *SwFootnoteBossFrame::FindFirstFootnote() +{ + // search for the nearest footnote container + SwFootnoteContFrame *pCont = FindNearestFootnoteCont(); + if ( !pCont ) + return nullptr; + + // Starting from the first footnote, search the first + // footnote that is referenced by the current column/page + + SwFootnoteFrame *pRet = static_cast<SwFootnoteFrame*>(pCont->Lower()); + const sal_uInt16 nRefNum = FindPageFrame()->GetPhyPageNum(); + const sal_uInt16 nRefCol = lcl_ColumnNum( this ); + sal_uInt16 nPgNum, nColNum; // page number, column number + SwFootnoteBossFrame* pBoss; + SwPageFrame* pPage; + if( pRet ) + { + pBoss = pRet->GetRef()->FindFootnoteBossFrame(); + OSL_ENSURE( pBoss, "FindFirstFootnote: No boss found" ); + if( !pBoss ) + return nullptr; // ?There must be a bug, but no GPF + pPage = pBoss->FindPageFrame(); + nPgNum = pPage->GetPhyPageNum(); + if ( nPgNum == nRefNum ) + { + nColNum = lcl_ColumnNum( pBoss ); + if( nColNum == nRefCol ) + return pRet; // found + else if( nColNum > nRefCol ) + return nullptr; // at least one column too far + } + else if ( nPgNum > nRefNum ) + return nullptr; // at least one column too far + } + else + return nullptr; + // Done if Ref is on a subsequent page or on the same page in a subsequent column + + do + { + while ( pRet->GetFollow() ) + pRet = pRet->GetFollow(); + + SwFootnoteFrame *pNxt = static_cast<SwFootnoteFrame*>(pRet->GetNext()); + if ( !pNxt ) + { + pBoss = pRet->FindFootnoteBossFrame(); + pPage = pBoss->FindPageFrame(); + lcl_NextFootnoteBoss( pBoss, pPage, false ); // next FootnoteBoss + pCont = pBoss ? pBoss->FindNearestFootnoteCont() : nullptr; + if ( pCont ) + pNxt = static_cast<SwFootnoteFrame*>(pCont->Lower()); + } + if ( pNxt ) + { + pRet = pNxt; + pBoss = pRet->GetRef()->FindFootnoteBossFrame(); + pPage = pBoss->FindPageFrame(); + nPgNum = pPage->GetPhyPageNum(); + if ( nPgNum == nRefNum ) + { + nColNum = lcl_ColumnNum( pBoss ); + if( nColNum == nRefCol ) + break; // found + else if( nColNum > nRefCol ) + pRet = nullptr; // at least one column too far + } + else if ( nPgNum > nRefNum ) + pRet = nullptr; // at least a page too far + } + else + pRet = nullptr; // there is none + } while( pRet ); + return pRet; +} + +/// Get the first footnote of a given content +const SwFootnoteFrame *SwFootnoteBossFrame::FindFirstFootnote( SwContentFrame const *pCnt ) const +{ + const SwFootnoteFrame *pRet = const_cast<SwFootnoteBossFrame*>(this)->FindFirstFootnote(); + if ( pRet ) + { + const sal_uInt16 nColNum = lcl_ColumnNum( this ); + const sal_uInt16 nPageNum = GetPhyPageNum(); + while ( pRet && (pRet->GetRef() != pCnt) ) + { + while ( pRet->GetFollow() ) + pRet = pRet->GetFollow(); + + if ( pRet->GetNext() ) + pRet = static_cast<const SwFootnoteFrame*>(pRet->GetNext()); + else + { SwFootnoteBossFrame *pBoss = const_cast<SwFootnoteBossFrame*>(pRet->FindFootnoteBossFrame()); + SwPageFrame *pPage = pBoss->FindPageFrame(); + lcl_NextFootnoteBoss( pBoss, pPage, false ); // next FootnoteBoss + SwFootnoteContFrame *pCont = pBoss ? pBoss->FindNearestFootnoteCont() : nullptr; + pRet = pCont ? static_cast<SwFootnoteFrame*>(pCont->Lower()) : nullptr; + } + if ( pRet ) + { + const SwFootnoteBossFrame* pBoss = pRet->GetRef()->FindFootnoteBossFrame(); + if( pBoss->GetPhyPageNum() != nPageNum || + nColNum != lcl_ColumnNum( pBoss ) ) + pRet = nullptr; + } + } + } + return pRet; +} + +void SwFootnoteBossFrame::ResetFootnote( const SwFootnoteFrame *pCheck ) +{ + // Destroy the incarnations of footnotes to an attribute, if they don't + // belong to pAssumed + OSL_ENSURE( !pCheck->GetMaster(), "given master is not a Master." ); + + SwNodeIndex aIdx( *pCheck->GetAttr()->GetStartNode(), 1 ); + SwContentNode *pNd = aIdx.GetNode().GetContentNode(); + if ( !pNd ) + pNd = pCheck->GetFormat()->GetDoc()-> + GetNodes().GoNextSection( &aIdx, true, false ); + SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*pNd); + SwFrame* pFrame = aIter.First(); + while( pFrame ) + { + if( pFrame->getRootFrame() == pCheck->getRootFrame() ) + { + SwFrame *pTmp = pFrame->GetUpper(); + while ( pTmp && !pTmp->IsFootnoteFrame() ) + pTmp = pTmp->GetUpper(); + + SwFootnoteFrame *pFootnote = static_cast<SwFootnoteFrame*>(pTmp); + while ( pFootnote && pFootnote->GetMaster() ) + pFootnote = pFootnote->GetMaster(); + if ( pFootnote != pCheck ) + { + while (pFootnote && !pFootnote->IsDeleteForbidden()) + { + SwFootnoteFrame *pNxt = pFootnote->GetFollow(); + pFootnote->Cut(); + SwFrame::DestroyFrame(pFootnote); + pFootnote = pNxt; + } + } + } + + pFrame = aIter.Next(); + } +} + +void SwFootnoteBossFrame::InsertFootnote( SwFootnoteFrame* pNew ) +{ + // Place the footnote in front of the footnote whose attribute + // is in front of the new one (get position via the Doc). + // If there is no footnote in this footnote-boss yet, create a new container. + // If there is a container but no footnote for this footnote-boss yet, place + // the footnote behind the last footnote of the closest previous column/page. + + ResetFootnote( pNew ); + SwFootnoteFrame *pSibling = FindFirstFootnote(); + bool bDontLeave = false; + + // Ok, a sibling has been found, but is the sibling in an acceptable + // environment? + if( IsInSct() ) + { + SwSectionFrame* pMySect = ImplFindSctFrame(); + bool bEndnt = pNew->GetAttr()->GetFootnote().IsEndNote(); + if( bEndnt ) + { + const SwSectionFormat* pEndFormat = pMySect->GetEndSectFormat(); + bDontLeave = nullptr != pEndFormat; + if( pSibling ) + { + if( pEndFormat ) + { + if( !pSibling->IsInSct() || + !pSibling->ImplFindSctFrame()->IsDescendantFrom( pEndFormat ) ) + pSibling = nullptr; + } + else if( pSibling->IsInSct() ) + pSibling = nullptr; + } + } + else + { + bDontLeave = pMySect->IsFootnoteAtEnd(); + if( pSibling ) + { + if( pMySect->IsFootnoteAtEnd() ) + { + if( !pSibling->IsInSct() || + !pMySect->IsAnFollow( pSibling->ImplFindSctFrame() ) ) + pSibling = nullptr; + } + else if( pSibling->IsInSct() ) + pSibling = nullptr; + } + } + } + + if( pSibling && pSibling->FindPageFrame()->IsEndNotePage() != + FindPageFrame()->IsEndNotePage() ) + pSibling = nullptr; + + // use the Doc to find out the position + SwDoc *pDoc = GetFormat()->GetDoc(); + const sal_uLong nStPos = ::lcl_FindFootnotePos( pDoc, pNew->GetAttr() ); + + sal_uLong nCmpPos = 0; + sal_uLong nLastPos = 0; + SwFootnoteContFrame *pParent = nullptr; + if( pSibling ) + { + nCmpPos = ::lcl_FindFootnotePos( pDoc, pSibling->GetAttr() ); + if( nCmpPos > nStPos ) + pSibling = nullptr; + } + + if ( !pSibling ) + { pParent = FindFootnoteCont(); + if ( !pParent ) + { + // There is no footnote container yet. Before creating one, keep in mind that + // there might exist another following footnote that must be placed before the + // new inserted one e.g. because it was divided over multiple pages etc. + pParent = FindNearestFootnoteCont( bDontLeave ); + if ( pParent ) + { + SwFootnoteFrame *pFootnote = static_cast<SwFootnoteFrame*>(pParent->Lower()); + if ( pFootnote ) + { + + nCmpPos = ::lcl_FindFootnotePos( pDoc, pFootnote->GetAttr() ); + if ( nCmpPos > nStPos ) + pParent = nullptr; + } + else + pParent = nullptr; + } + } + if ( !pParent ) + // here, we are sure that we can create a footnote container + pParent = MakeFootnoteCont(); + else + { + // Based on the first footnote below the Parent, search for the first footnote whose + // index is after the index of the newly inserted, to place the new one correctly + pSibling = static_cast<SwFootnoteFrame*>(pParent->Lower()); + if ( !pSibling ) + { + OSL_ENSURE( false, "Could not find space for footnote."); + return; + } + nCmpPos = ::lcl_FindFootnotePos( pDoc, pSibling->GetAttr() ); + + SwFootnoteBossFrame *pNxtB; // remember the last one to not + SwFootnoteFrame *pLastSib = nullptr; // go too far. + + while ( pSibling && nCmpPos <= nStPos ) + { + pLastSib = pSibling; // potential candidate + nLastPos = nCmpPos; + + while ( pSibling->GetFollow() ) + pSibling = pSibling->GetFollow(); + + if ( pSibling->GetNext() ) + { + pSibling = static_cast<SwFootnoteFrame*>(pSibling->GetNext()); + OSL_ENSURE( !pSibling->GetMaster() || ( ENDNOTE > nStPos && + pSibling->GetAttr()->GetFootnote().IsEndNote() ), + "InsertFootnote: Master expected I" ); + } + else + { + pNxtB = pSibling->FindFootnoteBossFrame(); + SwPageFrame *pSibPage = pNxtB->FindPageFrame(); + bool bEndNote = pSibPage->IsEndNotePage(); + bool bChgPage = lcl_NextFootnoteBoss( pNxtB, pSibPage, bDontLeave ); + // When changing pages, also the endnote flag must match. + SwFootnoteContFrame *pCont = pNxtB && ( !bChgPage || + pSibPage->IsEndNotePage() == bEndNote ) + ? pNxtB->FindNearestFootnoteCont( bDontLeave ) : nullptr; + if( pCont ) + pSibling = static_cast<SwFootnoteFrame*>(pCont->Lower()); + else // no further FootnoteContainer, insert after pSibling + break; + } + if ( pSibling ) + { + nCmpPos = ::lcl_FindFootnotePos( pDoc, pSibling->GetAttr() ); + OSL_ENSURE( nCmpPos > nLastPos, "InsertFootnote: Order of FootnoteFrame's buggy" ); + } + } + // pLastSib is the last footnote before the new one and + // pSibling is empty or the first one after the new one + if ( pSibling && pLastSib && (pSibling != pLastSib) ) + { + // too far? + if ( nCmpPos > nStPos ) + pSibling = pLastSib; + } + else if ( !pSibling ) + { + // Last chance: Take the last footnote of the parent. + // Special case that happens e.g. when moving paragraphs with multiple footnotes. + // To keep the order, use the parent of the last inspected footnote. + pSibling = pLastSib; + while( pSibling->GetFollow() ) + pSibling = pSibling->GetFollow(); + OSL_ENSURE( !pSibling->GetNext(), "InsertFootnote: Who's that guy?" ); + } + } + } + else + { + // First footnote of the column/page found. Now search from there for the first one on the + // same column/page whose index is after the given one. The last one found is the predecessor. + SwFootnoteBossFrame* pBoss = pNew->GetRef()->FindFootnoteBossFrame( + !pNew->GetAttr()->GetFootnote().IsEndNote() ); + sal_uInt16 nRefNum = pBoss->GetPhyPageNum(); // page number of the new footnote + sal_uInt16 nRefCol = lcl_ColumnNum( pBoss ); // column number of the new footnote + bool bEnd = false; + SwFootnoteFrame *pLastSib = nullptr; + while ( pSibling && !bEnd && (nCmpPos <= nStPos) ) + { + pLastSib = pSibling; + nLastPos = nCmpPos; + + while ( pSibling->GetFollow() ) + pSibling = pSibling->GetFollow(); + + SwFootnoteFrame *pFoll = static_cast<SwFootnoteFrame*>(pSibling->GetNext()); + if ( pFoll ) + { + pBoss = pSibling->GetRef()->FindFootnoteBossFrame( !pSibling-> + GetAttr()->GetFootnote().IsEndNote() ); + sal_uInt16 nTmpRef; + if( nStPos >= ENDNOTE || + (nTmpRef = pBoss->GetPhyPageNum()) < nRefNum || + ( nTmpRef == nRefNum && lcl_ColumnNum( pBoss ) <= nRefCol )) + pSibling = pFoll; + else + bEnd = true; + } + else + { + SwFootnoteBossFrame* pNxtB = pSibling->FindFootnoteBossFrame(); + SwPageFrame *pSibPage = pNxtB->FindPageFrame(); + bool bEndNote = pSibPage->IsEndNotePage(); + bool bChgPage = lcl_NextFootnoteBoss( pNxtB, pSibPage, bDontLeave ); + // When changing pages, also the endnote flag must match. + SwFootnoteContFrame *pCont = pNxtB && ( !bChgPage || + pSibPage->IsEndNotePage() == bEndNote ) + ? pNxtB->FindNearestFootnoteCont( bDontLeave ) : nullptr; + if ( pCont ) + pSibling = static_cast<SwFootnoteFrame*>(pCont->Lower()); + else + bEnd = true; + } + if ( !bEnd && pSibling ) + nCmpPos = ::lcl_FindFootnotePos( pDoc, pSibling->GetAttr() ); + if (pSibling && (pSibling != pLastSib)) + { + // too far? + if ( (nLastPos < nCmpPos) && (nCmpPos > nStPos) ) + { + pSibling = pLastSib; + bEnd = true; + } + } + } + } + if ( pSibling ) + { + nCmpPos = ::lcl_FindFootnotePos( pDoc, pSibling->GetAttr() ); + if ( nCmpPos < nStPos ) + { + while ( pSibling->GetFollow() ) + pSibling = pSibling->GetFollow(); + pParent = static_cast<SwFootnoteContFrame*>(pSibling->GetUpper()); + pSibling = static_cast<SwFootnoteFrame*>(pSibling->GetNext()); + } + else + { + if( pSibling->GetMaster() ) + { + if( ENDNOTE > nCmpPos || nStPos >= ENDNOTE ) + { + OSL_FAIL( "InsertFootnote: Master expected II" ); + do + pSibling = pSibling->GetMaster(); + while ( pSibling->GetMaster() ); + } + } + pParent = static_cast<SwFootnoteContFrame*>(pSibling->GetUpper()); + } + } + OSL_ENSURE( pParent, "paste in space?" ); + pNew->Paste( pParent, pSibling ); +} + +static SwPageFrame* lcl_GetApproximateFootnotePage(const bool bEnd, const SwPageFrame* pPage, + const SwDoc *pDoc, const SwTextFootnote *pAttr) +{ + // We can at least search the approximately correct page + // to ensure that we will finish in finite time even if + // hundreds of footnotes exist. + const SwPageFrame *pNxt = static_cast<const SwPageFrame*>(pPage->GetNext()); + const sal_uLong nStPos = ::lcl_FindFootnotePos(pDoc, pAttr); + while (pNxt && (bEnd ? pNxt->IsEndNotePage() : pNxt->IsFootnotePage() && !pNxt->IsEndNotePage())) + { + const SwFootnoteContFrame *pCont = pNxt->FindFootnoteCont(); + if (pCont && pCont->Lower()) + { + OSL_ENSURE( pCont->Lower()->IsFootnoteFrame(), "no footnote in the container" ); + if (nStPos > ::lcl_FindFootnotePos(pDoc, + static_cast<const SwFootnoteFrame*>(pCont->Lower())->GetAttr())) + { + pPage = pNxt; + pNxt = static_cast<const SwPageFrame*>(pPage->GetNext()); + continue; + } + } + break; + } + return const_cast<SwPageFrame*>(pPage); +} + +void SwFootnoteBossFrame::AppendFootnote( SwContentFrame *pRef, SwTextFootnote *pAttr ) +{ + // If the footnote already exists, do nothing. + if ( FindFootnote( pRef, pAttr ) ) + return; + + // If footnotes are inserted at the end of the document, + // we only need to search from the relevant page on. + // If there is none yet, we need to create one. + // If it is an Endnote, we need to search for or create an + // Endnote page. + SwDoc *pDoc = GetFormat()->GetDoc(); + SwFootnoteBossFrame *pBoss = this; + SwPageFrame *pPage = FindPageFrame(); + SwPageFrame *pMyPage = pPage; + bool bChgPage = false; + const bool bEnd = pAttr->GetFootnote().IsEndNote(); + if (bEnd) + { + const IDocumentSettingAccess& rSettings = *pAttr->GetTextNode().getIDocumentSettingAccess(); + if( GetUpper()->IsSctFrame() && + static_cast<SwSectionFrame*>(GetUpper())->IsEndnAtEnd() ) + { + // Endnotes at the end of the section. + SwFrame* pLast = + static_cast<SwSectionFrame*>(GetUpper())->FindLastContent( SwFindMode::EndNote ); + if( pLast ) + { + pBoss = pLast->FindFootnoteBossFrame(); + pPage = pBoss->FindPageFrame(); + } + } + else if (rSettings.get(DocumentSettingId::CONTINUOUS_ENDNOTES)) + { + // Endnotes at the end of the document. + pBoss = getRootFrame()->GetLastPage(); + pPage = pBoss->FindPageFrame(); + } + else + { + // Endnotes on a separate page. + while ( pPage->GetNext() && !pPage->IsEndNotePage() ) + { + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + bChgPage = true; + } + if ( !pPage->IsEndNotePage() ) + { + SwPageDesc *pDesc = pDoc->GetEndNoteInfo().GetPageDesc( *pDoc ); + pPage = ::InsertNewPage( *pDesc, pPage->GetUpper(), + !pPage->OnRightPage(), false, false, true, nullptr ); + pPage->SetEndNotePage( true ); + bChgPage = true; + } + else + pPage = lcl_GetApproximateFootnotePage(true, pPage, pDoc, pAttr); + } + } + else if( FTNPOS_CHAPTER == pDoc->GetFootnoteInfo().m_ePos && ( !GetUpper()-> + IsSctFrame() || !static_cast<SwSectionFrame*>(GetUpper())->IsFootnoteAtEnd() ) ) + { + while ( pPage->GetNext() && !pPage->IsFootnotePage() && + !static_cast<SwPageFrame*>(pPage->GetNext())->IsEndNotePage() ) + { + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + bChgPage = true; + } + + if ( !pPage->IsFootnotePage() ) + { + SwPageDesc *pDesc = pDoc->GetFootnoteInfo().GetPageDesc( *pDoc ); + pPage = ::InsertNewPage( *pDesc, pPage->GetUpper(), + !pPage->OnRightPage(), false, false, true, pPage->GetNext() ); + bChgPage = true; + } + else + pPage = lcl_GetApproximateFootnotePage(false, pPage, pDoc, pAttr); + } + + // For now, create a footnote and the corresponding content frames + if ( !pAttr->GetStartNode() ) + { + OSL_ENSURE( false, "no footnote content." ); + return; + } + + // If there is already a footnote content on the column/page, + // another one cannot be created in a column area. + if( pBoss->IsInSct() && pBoss->IsColumnFrame() && !pPage->IsFootnotePage() ) + { + SwSectionFrame* pSct = pBoss->FindSctFrame(); + if( bEnd ? !pSct->IsEndnAtEnd() : !pSct->IsFootnoteAtEnd() ) + { + SwFootnoteContFrame* pFootnoteCont = pSct->FindFootnoteBossFrame(!bEnd)->FindFootnoteCont(); + if( pFootnoteCont ) + { + SwFootnoteFrame* pTmp = static_cast<SwFootnoteFrame*>(pFootnoteCont->Lower()); + if( bEnd ) + while( pTmp && !pTmp->GetAttr()->GetFootnote().IsEndNote() ) + pTmp = static_cast<SwFootnoteFrame*>(pTmp->GetNext()); + if( pTmp && *pTmp < pAttr ) + return; + } + } + } + + SwFootnoteFrame *pNew = new SwFootnoteFrame( pDoc->GetDfltFrameFormat(), this, pRef, pAttr ); + { + SwNodeIndex aIdx( *pAttr->GetStartNode(), 1 ); + ::InsertCnt_( pNew, pDoc, aIdx.GetIndex() ); + } + // If the page was changed or newly created, + // we need to place ourselves in the first column + if( bChgPage ) + { + SwLayoutFrame* pBody = pPage->FindBodyCont(); + OSL_ENSURE( pBody, "AppendFootnote: NoPageBody?" ); + if( pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + pBoss = static_cast<SwFootnoteBossFrame*>(pBody->Lower()); + else + pBoss = pPage; // page if no columns exist + } + pBoss->InsertFootnote( pNew ); + if ( pNew->GetUpper() ) // inserted or not? + { + ::RegistFlys( pNew->FindPageFrame(), pNew ); + SwSectionFrame* pSect = FindSctFrame(); + // The content of a FootnoteContainer in a (column) section only need to be calculated + // if the section stretches already to the bottom edge of the Upper. + if( pSect && !pSect->IsJoinLocked() && ( bEnd ? !pSect->IsEndnAtEnd() : + !pSect->IsFootnoteAtEnd() ) && pSect->Growable() ) + pSect->InvalidateSize(); + else + { + // #i49383# - disable unlock of position of + // lower objects during format of footnote content. + const bool bOldFootnoteFrameLocked( pNew->IsColLocked() ); + pNew->ColLock(); + pNew->KeepLockPosOfLowerObjs(); + // #i57914# - adjust fix #i49383# + SwContentFrame *pCnt = pNew->ContainsContent(); + while ( pCnt && pCnt->FindFootnoteFrame()->GetAttr() == pAttr ) + { + pCnt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i49383# - format anchored objects + if ( pCnt->IsTextFrame() && pCnt->isFrameAreaDefinitionValid() ) + { + if ( !SwObjectFormatter::FormatObjsAtFrame( *pCnt, + *(pCnt->FindPageFrame()) ) ) + { + // restart format with first content + pCnt = pNew->ContainsContent(); + continue; + } + } + pCnt = pCnt->FindNextCnt(); + } + // #i49383# + if ( !bOldFootnoteFrameLocked ) + { + pNew->ColUnlock(); + } + // #i57914# - adjust fix #i49383# + // enable lock of lower object position before format of footnote frame. + pNew->UnlockPosOfLowerObjs(); + pNew->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i57914# - adjust fix #i49383# + if ( !bOldFootnoteFrameLocked && !pNew->GetLower() && + !pNew->IsColLocked() && !pNew->IsBackMoveLocked() && + !pNew->IsDeleteForbidden() ) + { + pNew->Cut(); + SwFrame::DestroyFrame(pNew); + } + } + pMyPage->UpdateFootnoteNum(); + } + else + SwFrame::DestroyFrame(pNew); +} + +SwFootnoteFrame *SwFootnoteBossFrame::FindFootnote( const SwContentFrame *pRef, const SwTextFootnote *pAttr ) +{ + // the easiest and savest way goes via the attribute + OSL_ENSURE( pAttr->GetStartNode(), "FootnoteAtr without StartNode." ); + SwNodeIndex aIdx( *pAttr->GetStartNode(), 1 ); + SwContentNode *pNd = aIdx.GetNode().GetContentNode(); + if ( !pNd ) + pNd = pRef->GetAttrSet()->GetDoc()-> + GetNodes().GoNextSection( &aIdx, true, false ); + if ( !pNd ) + return nullptr; + SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*pNd); + SwFrame* pFrame = aIter.First(); + if( pFrame ) + do + { + pFrame = pFrame->GetUpper(); + // #i28500#, #i27243# Due to the endnode collector, there are + // SwFootnoteFrames, which are not in the layout. Therefore the + // bInfFootnote flags are not set correctly, and a cell of FindFootnoteFrame + // would return 0. Therefore we better call ImplFindFootnoteFrame(). + SwFootnoteFrame *pFootnote = pFrame->ImplFindFootnoteFrame(); + if ( pFootnote && pFootnote->GetRef() == pRef ) + { + // The following condition becomes true, if the whole + // footnotecontent is a section. While no frames exist, + // the HiddenFlag of the section is set, this causes + // the GoNextSection-function leaves the footnote. + if( pFootnote->GetAttr() != pAttr ) + return nullptr; + while ( pFootnote && pFootnote->GetMaster() ) + pFootnote = pFootnote->GetMaster(); + return pFootnote; + } + + } while ( nullptr != (pFrame = aIter.Next()) ); + + return nullptr; +} + +bool SwFootnoteBossFrame::RemoveFootnote( + const SwContentFrame *const pRef, const SwTextFootnote *const pAttr, + bool bPrep ) +{ + bool ret(false); + SwFootnoteFrame *pFootnote = FindFootnote( pRef, pAttr ); + if( pFootnote ) + { + ret = true; + do + { + SwFootnoteFrame *pFoll = pFootnote->GetFollow(); + pFootnote->Cut(); + SwFrame::DestroyFrame(pFootnote); + pFootnote = pFoll; + } while ( pFootnote ); + if( bPrep && pRef->IsFollow() ) + { + OSL_ENSURE( pRef->IsTextFrame(), "NoTextFrame has Footnote?" ); + SwTextFrame* pMaster = pRef->FindMaster(); + if( !pMaster->IsLocked() ) + pMaster->Prepare( PrepareHint::FootnoteInvalidationGone ); + } + } + FindPageFrame()->UpdateFootnoteNum(); + return ret; +} + +void SwFootnoteBossFrame::ChangeFootnoteRef( const SwContentFrame *pOld, const SwTextFootnote *pAttr, + SwContentFrame *pNew ) +{ + SwFootnoteFrame *pFootnote = FindFootnote( pOld, pAttr ); + while ( pFootnote ) + { + pFootnote->SetRef( pNew ); + pFootnote = pFootnote->GetFollow(); + } +} + +/// OD 03.04.2003 #108446# - add parameter <_bCollectOnlyPreviousFootnotes> in +/// order to control, if only footnotes, which are positioned before the +/// footnote boss frame <this> have to be collected. +void SwFootnoteBossFrame::CollectFootnotes( const SwContentFrame* _pRef, + SwFootnoteBossFrame* _pOld, + SwFootnoteFrames& _rFootnoteArr, + const bool _bCollectOnlyPreviousFootnotes ) +{ + SwFootnoteFrame *pFootnote = _pOld->FindFirstFootnote(); + while( !pFootnote ) + { + if( _pOld->IsColumnFrame() ) + { + // visit columns + while ( !pFootnote && _pOld->GetPrev() ) + { + // Still no problem if no footnote was found yet. The loop is needed to pick up + // following rows in tables. In all other cases it might correct bad contexts. + _pOld = static_cast<SwFootnoteBossFrame*>(_pOld->GetPrev()); + pFootnote = _pOld->FindFirstFootnote(); + } + } + if( !pFootnote ) + { + // previous page + SwPageFrame* pPg; + for ( SwFrame* pTmp = _pOld; + nullptr != ( pPg = static_cast<SwPageFrame*>(pTmp->FindPageFrame()->GetPrev())) + && pPg->IsEmptyPage() ; + ) + { + pTmp = pPg; + } + if( !pPg ) + return; + + SwLayoutFrame* pBody = pPg->FindBodyCont(); + if( pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + { + // multiple columns on one page => search last column + _pOld = static_cast<SwFootnoteBossFrame*>(pBody->GetLastLower()); + } + else + _pOld = pPg; // single column page + pFootnote = _pOld->FindFirstFootnote(); + } + } + + CollectFootnotes_(_pRef, pFootnote, _rFootnoteArr, _bCollectOnlyPreviousFootnotes ? this : nullptr); +} + +static void FootnoteInArr( SwFootnoteFrames& rFootnoteArr, SwFootnoteFrame* pFootnote ) +{ + if ( rFootnoteArr.end() == std::find( rFootnoteArr.begin(), rFootnoteArr.end(), pFootnote ) ) + rFootnoteArr.push_back( pFootnote ); +} + +void SwFootnoteBossFrame::CollectFootnotes_( const SwContentFrame* _pRef, + SwFootnoteFrame* _pFootnote, + SwFootnoteFrames& _rFootnoteArr, + const SwFootnoteBossFrame* _pRefFootnoteBossFrame) +{ + // Collect all footnotes referenced by pRef (attribute by attribute), combine them + // (the content might be divided over multiple pages) and cut them. + + // For robustness, we do not log the corresponding footnotes here. If a footnote + // is touched twice, there might be a crash. This allows this function here to + // also handle corrupt layouts in some degrees (without loops or even crashes). + SwFootnoteFrames aNotFootnoteArr; + + // here we have a footnote placed in front of the first one of the reference + OSL_ENSURE( !_pFootnote->GetMaster() || _pFootnote->GetRef() != _pRef, "move FollowFootnote?" ); + while ( _pFootnote->GetMaster() ) + _pFootnote = _pFootnote->GetMaster(); + + bool bFound = false; + + do + { + // Search for the next footnote in this column/page so that + // we do not start from zero again after cutting one footnote. + SwFootnoteFrame *pNxtFootnote = _pFootnote; + while ( pNxtFootnote->GetFollow() ) + pNxtFootnote = pNxtFootnote->GetFollow(); + pNxtFootnote = static_cast<SwFootnoteFrame*>(pNxtFootnote->GetNext()); + + if ( !pNxtFootnote ) + { + SwFootnoteBossFrame* pBoss = _pFootnote->FindFootnoteBossFrame(); + SwPageFrame* pPage = pBoss->FindPageFrame(); + do + { + lcl_NextFootnoteBoss( pBoss, pPage, false ); + if( pBoss ) + { + SwLayoutFrame* pCont = pBoss->FindFootnoteCont(); + if( pCont ) + { + pNxtFootnote = static_cast<SwFootnoteFrame*>(pCont->Lower()); + if( pNxtFootnote ) + { + while( pNxtFootnote->GetMaster() ) + pNxtFootnote = pNxtFootnote->GetMaster(); + if( pNxtFootnote == _pFootnote ) + pNxtFootnote = nullptr; + } + } + } + } while( !pNxtFootnote && pBoss ); + } + else if( !pNxtFootnote->GetAttr()->GetFootnote().IsEndNote() ) + { + OSL_ENSURE( !pNxtFootnote->GetMaster(), "_CollectFootnote: Master expected" ); + while ( pNxtFootnote->GetMaster() ) + pNxtFootnote = pNxtFootnote->GetMaster(); + } + if ( pNxtFootnote == _pFootnote ) + { + OSL_FAIL( "_CollectFootnote: Vicious circle" ); + pNxtFootnote = nullptr; + } + + // OD 03.04.2003 #108446# - determine, if found footnote has to be collected. + bool bCollectFoundFootnote = false; + // Ignore endnotes which are on a separate endnote page. + bool bEndNote = _pFootnote->GetAttr()->GetFootnote().IsEndNote(); + const IDocumentSettingAccess& rSettings + = _pFootnote->GetAttrSet()->GetDoc()->getIDocumentSettingAccess(); + bool bContinuousEndnotes = rSettings.get(DocumentSettingId::CONTINUOUS_ENDNOTES); + if (_pFootnote->GetRef() == _pRef && (!bEndNote || bContinuousEndnotes)) + { + if (_pRefFootnoteBossFrame) + { + SwFootnoteBossFrame* pBossOfFoundFootnote = _pFootnote->FindFootnoteBossFrame( true ); + OSL_ENSURE( pBossOfFoundFootnote, + "<SwFootnoteBossFrame::CollectFootnotes_(..)> - footnote boss frame of found footnote frame missing.\nWrong layout!" ); + if ( !pBossOfFoundFootnote || // don't crash, if no footnote boss is found. + pBossOfFoundFootnote->IsBefore( _pRefFootnoteBossFrame ) + ) + { + bCollectFoundFootnote = true; + } + } + else + { + bCollectFoundFootnote = true; + } + } + + if ( bCollectFoundFootnote ) + { + OSL_ENSURE( !_pFootnote->GetMaster(), "move FollowFootnote?" ); + SwFootnoteFrame *pNxt = _pFootnote->GetFollow(); + while ( pNxt ) + { + SwFrame *pCnt = pNxt->ContainsAny(); + if ( pCnt ) + { + // destroy the follow on the way as it is empty + do + { SwFrame *pNxtCnt = pCnt->GetNext(); + pCnt->Cut(); + pCnt->Paste( _pFootnote ); + pCnt = pNxtCnt; + } while ( pCnt ); + } + else + { + OSL_ENSURE( !pNxt, "footnote without content?" ); + pNxt->Cut(); + SwFrame::DestroyFrame(pNxt); + } + pNxt = _pFootnote->GetFollow(); + } + _pFootnote->Cut(); + FootnoteInArr( _rFootnoteArr, _pFootnote ); + bFound = true; + } + else + { + FootnoteInArr( aNotFootnoteArr, _pFootnote ); + if( bFound ) + break; + } + if ( pNxtFootnote && + _rFootnoteArr.end() == std::find( _rFootnoteArr.begin(), _rFootnoteArr.end(), pNxtFootnote ) && + aNotFootnoteArr.end() == std::find( aNotFootnoteArr.begin(), aNotFootnoteArr.end(), pNxtFootnote ) ) + _pFootnote = pNxtFootnote; + else + break; + } + while ( _pFootnote ); +} + +void SwFootnoteBossFrame::MoveFootnotes_( SwFootnoteFrames &rFootnoteArr, bool bCalc ) +{ + // All footnotes referenced by pRef need to be moved + // to a new position (based on the new column/page) + const sal_uInt16 nMyNum = FindPageFrame()->GetPhyPageNum(); + const sal_uInt16 nMyCol = lcl_ColumnNum( this ); + SwRectFnSet aRectFnSet(this); + + // #i21478# - keep last inserted footnote in order to + // format the content of the following one. + SwFootnoteFrame* pLastInsertedFootnote = nullptr; + for (SwFootnoteFrame* pFootnote : rFootnoteArr) + { + SwFootnoteBossFrame* pRefBoss(pFootnote->GetRef()->FindFootnoteBossFrame( + !pFootnote->GetAttr()->GetFootnote().IsEndNote())); + if( pRefBoss != this ) + { + const sal_uInt16 nRefNum = pRefBoss->FindPageFrame()->GetPhyPageNum(); + const sal_uInt16 nRefCol = lcl_ColumnNum( this ); + if( nRefNum < nMyNum || ( nRefNum == nMyNum && nRefCol <= nMyCol ) ) + pRefBoss = this; + } + pRefBoss->InsertFootnote( pFootnote ); + + if ( pFootnote->GetUpper() ) // robust, e.g. with duplicates + { + // First condense the content so that footnote frames that do not fit on the page + // do not do too much harm (Loop 66312). So, the footnote content first grows as + // soon as the content gets formatted and it is sure that it fits on the page. + SwFrame *pCnt = pFootnote->ContainsAny(); + while( pCnt ) + { + if( pCnt->IsLayoutFrame() ) + { + SwFrame* pTmp = static_cast<SwLayoutFrame*>(pCnt)->ContainsAny(); + while( pTmp && static_cast<SwLayoutFrame*>(pCnt)->IsAnLower( pTmp ) ) + { + pTmp->Prepare( PrepareHint::FootnoteMove ); + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pTmp); + aRectFnSet.SetHeight(aFrm, 0); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pTmp); + aRectFnSet.SetHeight(aPrt, 0); + + pTmp = pTmp->FindNext(); + } + } + else + { + pCnt->Prepare( PrepareHint::FootnoteMove ); + } + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pCnt); + aRectFnSet.SetHeight(aFrm, 0); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pCnt); + aRectFnSet.SetHeight(aPrt, 0); + + pCnt = pCnt->GetNext(); + } + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFootnote); + aRectFnSet.SetHeight(aFrm, 0); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFootnote); + aRectFnSet.SetHeight(aPrt, 0); + } + + pFootnote->Calc(getRootFrame()->GetCurrShell()->GetOut()); + pFootnote->GetUpper()->Calc(getRootFrame()->GetCurrShell()->GetOut()); + + if( bCalc ) + { + SwTextFootnote *pAttr = pFootnote->GetAttr(); + pCnt = pFootnote->ContainsAny(); + bool bUnlock = !pFootnote->IsBackMoveLocked(); + pFootnote->LockBackMove(); + + // #i49383# - disable unlock of position of + // lower objects during format of footnote content. + pFootnote->KeepLockPosOfLowerObjs(); + // #i57914# - adjust fix #i49383# + + while ( pCnt && pCnt->FindFootnoteFrame()->GetAttr() == pAttr ) + { + pCnt->InvalidatePos_(); + pCnt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i49383# - format anchored objects + if ( pCnt->IsTextFrame() && pCnt->isFrameAreaDefinitionValid() ) + { + if ( !SwObjectFormatter::FormatObjsAtFrame( *pCnt, + *(pCnt->FindPageFrame()) ) ) + { + // restart format with first content + pCnt = pFootnote->ContainsAny(); + continue; + } + } + if( pCnt->IsSctFrame() ) + { + // If the area is not empty, iterate also over the content + SwFrame* pTmp = static_cast<SwSectionFrame*>(pCnt)->ContainsAny(); + if( pTmp ) + pCnt = pTmp; + else + pCnt = pCnt->FindNext(); + } + else + pCnt = pCnt->FindNext(); + } + if( bUnlock ) + { + pFootnote->UnlockBackMove(); + if( !pFootnote->ContainsAny() && !pFootnote->IsColLocked() ) + { + pFootnote->Cut(); + SwFrame::DestroyFrame(pFootnote); + // #i21478# + pFootnote = nullptr; + } + } + // #i49383# + if ( pFootnote ) + { + // #i57914# - adjust fix #i49383# + // enable lock of lower object position before format of footnote frame. + pFootnote->UnlockPosOfLowerObjs(); + pFootnote->Calc(getRootFrame()->GetCurrShell()->GetOut()); + } + } + } + else + { + OSL_ENSURE( !pFootnote->GetMaster() && !pFootnote->GetFollow(), + "DelFootnote and Master/Follow?" ); + SwFrame::DestroyFrame(pFootnote); + // #i21478# + pFootnote = nullptr; + } + + // #i21478# + if ( pFootnote ) + { + pLastInsertedFootnote = pFootnote; + } + } + + // #i21478# - format content of footnote following + // the new inserted ones. + if ( !(bCalc && pLastInsertedFootnote) ) + return; + + if ( !pLastInsertedFootnote->GetNext() ) + return; + + SwFootnoteFrame* pNextFootnote = static_cast<SwFootnoteFrame*>(pLastInsertedFootnote->GetNext()); + SwTextFootnote* pAttr = pNextFootnote->GetAttr(); + SwFrame* pCnt = pNextFootnote->ContainsAny(); + + bool bUnlock = !pNextFootnote->IsBackMoveLocked(); + pNextFootnote->LockBackMove(); + // #i49383# - disable unlock of position of + // lower objects during format of footnote content. + pNextFootnote->KeepLockPosOfLowerObjs(); + // #i57914# - adjust fix #i49383# + + while ( pCnt && pCnt->FindFootnoteFrame()->GetAttr() == pAttr ) + { + pCnt->InvalidatePos_(); + pCnt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i49383# - format anchored objects + if ( pCnt->IsTextFrame() && pCnt->isFrameAreaDefinitionValid() ) + { + if ( !SwObjectFormatter::FormatObjsAtFrame( *pCnt, + *(pCnt->FindPageFrame()) ) ) + { + // restart format with first content + pCnt = pNextFootnote->ContainsAny(); + continue; + } + } + if( pCnt->IsSctFrame() ) + { + // If the area is not empty, iterate also over the content + SwFrame* pTmp = static_cast<SwSectionFrame*>(pCnt)->ContainsAny(); + if( pTmp ) + pCnt = pTmp; + else + pCnt = pCnt->FindNext(); + } + else + pCnt = pCnt->FindNext(); + } + if( bUnlock ) + { + pNextFootnote->UnlockBackMove(); + } + // #i49383# + // #i57914# - adjust fix #i49383# + // enable lock of lower object position before format of footnote frame. + pNextFootnote->UnlockPosOfLowerObjs(); + pNextFootnote->Calc(getRootFrame()->GetCurrShell()->GetOut()); +} + +void SwFootnoteBossFrame::MoveFootnotes( const SwContentFrame *pSrc, SwContentFrame *pDest, + SwTextFootnote const *pAttr ) +{ + if( ( GetFormat()->GetDoc()->GetFootnoteInfo().m_ePos == FTNPOS_CHAPTER && + (!GetUpper()->IsSctFrame() || !static_cast<SwSectionFrame*>(GetUpper())->IsFootnoteAtEnd())) + || pAttr->GetFootnote().IsEndNote() ) + return; + + OSL_ENSURE( this == pSrc->FindFootnoteBossFrame( true ), + "SwPageFrame::MoveFootnotes: source frame isn't on that FootnoteBoss" ); + + SwFootnoteFrame *pFootnote = FindFirstFootnote(); + if( !pFootnote ) + return; + + ChangeFootnoteRef( pSrc, pAttr, pDest ); + SwFootnoteBossFrame *pDestBoss = pDest->FindFootnoteBossFrame( true ); + OSL_ENSURE( pDestBoss, "+SwPageFrame::MoveFootnotes: no destination boss" ); + if( !pDestBoss ) // robust + return; + + SwFootnoteFrames aFootnoteArr; + SwFootnoteBossFrame::CollectFootnotes_(pDest, pFootnote, aFootnoteArr, nullptr); + if ( aFootnoteArr.empty() ) + return; + + pDestBoss->MoveFootnotes_( aFootnoteArr, true ); + SwPageFrame* pSrcPage = FindPageFrame(); + SwPageFrame* pDestPage = pDestBoss->FindPageFrame(); + // update FootnoteNum only at page change + if( pSrcPage != pDestPage ) + { + if( pSrcPage->GetPhyPageNum() > pDestPage->GetPhyPageNum() ) + pSrcPage->UpdateFootnoteNum(); + pDestPage->UpdateFootnoteNum(); + } +} + +void SwFootnoteBossFrame::RearrangeFootnotes( const SwTwips nDeadLine, const bool bLock, + const SwTextFootnote *pAttr ) +{ + // Format all footnotes of a column/page so that they might change the column/page. + + SwSaveFootnoteHeight aSave( this, nDeadLine ); + SwFootnoteFrame *pFootnote = FindFirstFootnote(); + if( pFootnote && pFootnote->GetPrev() && bLock ) + { + SwFootnoteFrame* pFirst = static_cast<SwFootnoteFrame*>(pFootnote->GetUpper()->Lower()); + SwFrame* pContent = pFirst->ContainsAny(); + if( pContent ) + { + bool bUnlock = !pFirst->IsBackMoveLocked(); + pFirst->LockBackMove(); + pFirst->Calc(getRootFrame()->GetCurrShell()->GetOut()); + pContent->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i49383# - format anchored objects + if ( pContent->IsTextFrame() && pContent->isFrameAreaDefinitionValid() ) + { + SwObjectFormatter::FormatObjsAtFrame( *pContent, + *(pContent->FindPageFrame()) ); + } + if( bUnlock ) + pFirst->UnlockBackMove(); + } + pFootnote = FindFirstFootnote(); + } + SwDoc *pDoc = GetFormat()->GetDoc(); + const sal_uLong nFootnotePos = pAttr ? ::lcl_FindFootnotePos( pDoc, pAttr ) : 0; + SwFrame *pCnt = pFootnote ? pFootnote->ContainsAny() : nullptr; + if ( !pCnt ) + return; + + bool bMore = true; + bool bStart = pAttr == nullptr; // If no attribute is given, process all + // #i49383# - disable unlock of position of + // lower objects during format of footnote and footnote content. + SwFootnoteFrame* pLastFootnoteFrame( nullptr ); + // footnote frame needs to be locked, if <bLock> isn't set. + bool bUnlockLastFootnoteFrame( false ); + do + { + if( !bStart ) + bStart = ::lcl_FindFootnotePos( pDoc, pCnt->FindFootnoteFrame()->GetAttr() ) + == nFootnotePos; + if( bStart ) + { + pCnt->InvalidatePos_(); + pCnt->InvalidateSize_(); + pCnt->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + SwFootnoteFrame* pFootnoteFrame = pCnt->FindFootnoteFrame(); + // #i49383# + if ( pFootnoteFrame != pLastFootnoteFrame ) + { + if ( pLastFootnoteFrame ) + { + if ( !bLock && bUnlockLastFootnoteFrame ) + { + pLastFootnoteFrame->ColUnlock(); + } + // #i57914# - adjust fix #i49383# + // enable lock of lower object position before format of footnote frame. + pLastFootnoteFrame->UnlockPosOfLowerObjs(); + pLastFootnoteFrame->Calc(getRootFrame()->GetCurrShell()->GetOut()); + if ( !bLock && bUnlockLastFootnoteFrame && + !pLastFootnoteFrame->GetLower() && + !pLastFootnoteFrame->IsColLocked() && + !pLastFootnoteFrame->IsBackMoveLocked() && + !pLastFootnoteFrame->IsDeleteForbidden() ) + { + pLastFootnoteFrame->Cut(); + SwFrame::DestroyFrame(pLastFootnoteFrame); + pLastFootnoteFrame = nullptr; + } + } + if ( !bLock ) + { + bUnlockLastFootnoteFrame = !pFootnoteFrame->IsColLocked(); + pFootnoteFrame->ColLock(); + } + pFootnoteFrame->KeepLockPosOfLowerObjs(); + pLastFootnoteFrame = pFootnoteFrame; + } + // OD 30.10.2002 #97265# - invalidate position of footnote + // frame, if it's below its footnote container, in order to + // assure its correct position, probably calculating its previous + // footnote frames. + { + SwRectFnSet aRectFnSet(this); + SwFrame* pFootnoteContFrame = pFootnoteFrame->GetUpper(); + if ( aRectFnSet.TopDist(pFootnoteFrame->getFrameArea(), aRectFnSet.GetPrtBottom(*pFootnoteContFrame)) > 0 ) + { + pFootnoteFrame->InvalidatePos_(); + } + } + if ( bLock ) + { + bool bUnlock = !pFootnoteFrame->IsBackMoveLocked(); + pFootnoteFrame->LockBackMove(); + pFootnoteFrame->Calc(getRootFrame()->GetCurrShell()->GetOut()); + pCnt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i49383# - format anchored objects + if ( pCnt->IsTextFrame() && pCnt->isFrameAreaDefinitionValid() ) + { + SwFrameDeleteGuard aDeleteGuard(pFootnote); + if ( !SwObjectFormatter::FormatObjsAtFrame( *pCnt, + *(pCnt->FindPageFrame()) ) ) + { + // restart format with first content + pCnt = pFootnote ? pFootnote->ContainsAny() : nullptr; + if (!pCnt) + bMore = false; + continue; + } + } + if( bUnlock ) + { + pFootnoteFrame->UnlockBackMove(); + if( !pFootnoteFrame->Lower() && + !pFootnoteFrame->IsColLocked() ) + { + // #i49383# + OSL_ENSURE( pLastFootnoteFrame == pFootnoteFrame, + "<SwFootnoteBossFrame::RearrangeFootnotes(..)> - <pLastFootnoteFrame> != <pFootnoteFrame>" ); + pLastFootnoteFrame = nullptr; + pFootnoteFrame->Cut(); + SwFrame::DestroyFrame(pFootnoteFrame); + if (pFootnote == pFootnoteFrame) + pFootnote = nullptr; + } + } + } + else + { + pFootnoteFrame->Calc(getRootFrame()->GetCurrShell()->GetOut()); + pCnt->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i49383# - format anchored objects + if ( pCnt->IsTextFrame() && pCnt->isFrameAreaDefinitionValid() ) + { + if ( !SwObjectFormatter::FormatObjsAtFrame( *pCnt, + *(pCnt->FindPageFrame()) ) ) + { + // restart format with first content + pCnt = pFootnote->ContainsAny(); + continue; + } + } + } + } + SwSectionFrame *pDel = nullptr; + if( pCnt->IsSctFrame() ) + { + SwFrame* pTmp = static_cast<SwSectionFrame*>(pCnt)->ContainsAny(); + if( pTmp ) + { + pCnt = pTmp; + continue; + } + pDel = static_cast<SwSectionFrame*>(pCnt); + } + if ( pCnt->GetNext() ) + pCnt = pCnt->GetNext(); + else + { + pCnt = pCnt->FindNext(); + if ( pCnt ) + { + SwFootnoteFrame* pFootnoteFrame = pCnt->FindFootnoteFrame(); + if( pFootnoteFrame->GetRef()->FindFootnoteBossFrame( + pFootnoteFrame->GetAttr()->GetFootnote().IsEndNote() ) != this ) + bMore = false; + } + else + bMore = false; + } + if( pDel ) + { + bool bUnlockLastFootnoteFrameGuard = pLastFootnoteFrame && !pLastFootnoteFrame->IsColLocked(); + if (bUnlockLastFootnoteFrameGuard) + pLastFootnoteFrame->ColLock(); + pDel->Cut(); + if (bUnlockLastFootnoteFrameGuard) + pLastFootnoteFrame->ColUnlock(); + SwFrame::DestroyFrame(pDel); + } + if ( bMore ) + { + // Go not further than to the provided footnote (if given) + if ( pAttr && + (::lcl_FindFootnotePos( pDoc, + pCnt->FindFootnoteFrame()->GetAttr()) > nFootnotePos ) ) + bMore = false; + } + } while ( bMore ); + // #i49383# + if ( !pLastFootnoteFrame ) + return; + + if ( !bLock && bUnlockLastFootnoteFrame ) + { + pLastFootnoteFrame->ColUnlock(); + } + // #i57914# - adjust fix #i49383# + // enable lock of lower object position before format of footnote frame. + pLastFootnoteFrame->UnlockPosOfLowerObjs(); + pLastFootnoteFrame->Calc(getRootFrame()->GetCurrShell()->GetOut()); + if ( !bLock && bUnlockLastFootnoteFrame && + !pLastFootnoteFrame->GetLower() && + !pLastFootnoteFrame->IsColLocked() && + !pLastFootnoteFrame->IsBackMoveLocked() && + !pLastFootnoteFrame->IsDeleteForbidden() ) + { + pLastFootnoteFrame->Cut(); + SwFrame::DestroyFrame(pLastFootnoteFrame); + } +} + +void SwPageFrame::UpdateFootnoteNum() +{ + // page numbering only if set at the document + if ( GetFormat()->GetDoc()->GetFootnoteInfo().m_eNum != FTNNUM_PAGE ) + return; + + SwLayoutFrame* pBody = FindBodyCont(); + if( !pBody || !pBody->Lower() ) + return; + + SwContentFrame* pContent = pBody->ContainsContent(); + sal_uInt16 nNum = 0; + + while( pContent && pContent->FindPageFrame() == this ) + { + if( static_cast<SwTextFrame*>(pContent)->HasFootnote() ) + { + SwFootnoteBossFrame* pBoss = pContent->FindFootnoteBossFrame( true ); + if( pBoss->GetUpper()->IsSctFrame() && + static_cast<SwSectionFrame*>(pBoss->GetUpper())->IsOwnFootnoteNum() ) + pContent = static_cast<SwSectionFrame*>(pBoss->GetUpper())->FindLastContent(); + else + { + SwFootnoteFrame* pFootnote = const_cast<SwFootnoteFrame*>(pBoss->FindFirstFootnote( pContent )); + while( pFootnote ) + { + SwTextFootnote* pTextFootnote = pFootnote->GetAttr(); + if( !pTextFootnote->GetFootnote().IsEndNote() && + pTextFootnote->GetFootnote().GetNumStr().isEmpty() && + !pFootnote->GetMaster()) + { + // sw_redlinehide: the layout can only keep one number + // up to date; depending on its setting, this is either + // the non-hidden or the hidden number; the other + // number will simply be preserved as-is (so in case + // there are 2 layouts, maybe both can be updated...) + ++nNum; + sal_uInt16 const nOldNum(pTextFootnote->GetFootnote().GetNumber()); + sal_uInt16 const nOldNumRLHidden(pTextFootnote->GetFootnote().GetNumberRLHidden()); + if (getRootFrame()->IsHideRedlines()) + { + if (nNum != nOldNumRLHidden) + { + pTextFootnote->SetNumber(nOldNum, nNum, OUString()); + } + } + else + { + if (nNum != nOldNum) + { + pTextFootnote->SetNumber(nNum, nOldNumRLHidden, OUString()); + } + } + } + if ( pFootnote->GetNext() ) + pFootnote = static_cast<SwFootnoteFrame*>(pFootnote->GetNext()); + else + { + SwFootnoteBossFrame* pTmpBoss = pFootnote->FindFootnoteBossFrame( true ); + if( pTmpBoss ) + { + SwPageFrame* pPage = pTmpBoss->FindPageFrame(); + pFootnote = nullptr; + lcl_NextFootnoteBoss( pTmpBoss, pPage, false ); + SwFootnoteContFrame *pCont = pTmpBoss ? pTmpBoss->FindNearestFootnoteCont() : nullptr; + if ( pCont ) + pFootnote = static_cast<SwFootnoteFrame*>(pCont->Lower()); + } + } + if( pFootnote && pFootnote->GetRef() != pContent ) + pFootnote = nullptr; + } + } + } + pContent = pContent->FindNextCnt(); + } +} + +void SwFootnoteBossFrame::SetFootnoteDeadLine( const SwTwips nDeadLine ) +{ + SwFrame *pBody = FindBodyCont(); + pBody->Calc(getRootFrame()->GetCurrShell()->GetOut()); + + SwFrame *pCont = FindFootnoteCont(); + const SwTwips nMax = m_nMaxFootnoteHeight;// current should exceed MaxHeight + SwRectFnSet aRectFnSet(this); + if ( pCont ) + { + pCont->Calc(getRootFrame()->GetCurrShell()->GetOut()); + m_nMaxFootnoteHeight = -aRectFnSet.BottomDist( pCont->getFrameArea(), nDeadLine ); + } + else + m_nMaxFootnoteHeight = -aRectFnSet.BottomDist( pBody->getFrameArea(), nDeadLine ); + + const SwViewShell *pSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + m_nMaxFootnoteHeight += pBody->Grow( LONG_MAX, true ); + if ( IsInSct() ) + m_nMaxFootnoteHeight += FindSctFrame()->Grow( LONG_MAX, true ); + + if ( m_nMaxFootnoteHeight < 0 ) + m_nMaxFootnoteHeight = 0; + if ( nMax != LONG_MAX && m_nMaxFootnoteHeight > nMax ) + m_nMaxFootnoteHeight = nMax; +} + +SwTwips SwFootnoteBossFrame::GetVarSpace() const +{ + // To not fall below 20% of the page height + // (in contrast to MSOffice where footnotes can fill a whole column/page) + + const SwPageFrame* pPg = FindPageFrame(); + OSL_ENSURE( pPg || IsInSct(), "Footnote lost page" ); + + const SwFrame *pBody = FindBodyCont(); + SwTwips nRet; + if( pBody ) + { + SwRectFnSet aRectFnSet(this); + if( IsInSct() ) + { + nRet = 0; + SwTwips nTmp = aRectFnSet.YDiff( aRectFnSet.GetPrtTop(*pBody), + aRectFnSet.GetTop(getFrameArea()) ); + const SwSectionFrame* pSect = FindSctFrame(); + // Endnotes in a ftncontainer causes a deadline: + // the bottom of the last contentfrm + if( pSect->IsEndnAtEnd() ) // endnotes allowed? + { + OSL_ENSURE( !Lower() || !Lower()->GetNext() || Lower()->GetNext()-> + IsFootnoteContFrame(), "FootnoteContainer expected" ); + const SwFootnoteContFrame* pCont = Lower() ? + static_cast<const SwFootnoteContFrame*>(Lower()->GetNext()) : nullptr; + if( pCont ) + { + const SwFootnoteFrame* pFootnote = static_cast<const SwFootnoteFrame*>(pCont->Lower()); + while( pFootnote) + { + if( pFootnote->GetAttr()->GetFootnote().IsEndNote() ) + { // endnote found + const SwFrame* pFrame = static_cast<const SwLayoutFrame*>(Lower())->Lower(); + if( pFrame ) + { + while( pFrame->GetNext() ) + pFrame = pFrame->GetNext(); // last cntntfrm + nTmp += aRectFnSet.YDiff( + aRectFnSet.GetTop(getFrameArea()), + aRectFnSet.GetBottom(pFrame->getFrameArea()) ); + } + break; + } + pFootnote = static_cast<const SwFootnoteFrame*>(pFootnote->GetNext()); + } + } + } + if( nTmp < nRet ) + nRet = nTmp; + } + else + nRet = - aRectFnSet.GetHeight(pPg->getFramePrintArea())/5; + nRet += aRectFnSet.GetHeight(pBody->getFrameArea()); + if( nRet < 0 ) + nRet = 0; + } + else + nRet = 0; + if ( IsPageFrame() ) + { + const SwViewShell *pSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + nRet += BROWSE_HEIGHT - getFrameArea().Height(); + } + return nRet; +} + +/** Obtain if pFrame's size adjustment should be processed + * + * For a page frame of columns directly below the page AdjustNeighbourhood() needs + * to be called, or Grow()/ Shrink() for frame columns respectively. + * + * A column section is special, since if there is a footnote container in a column + * and those footnotes are not collected, it is handled like a page frame. + * + * @see AdjustNeighbourhood() + * @see Grow() + * @see Shrink() + */ +SwNeighbourAdjust SwFootnoteBossFrame::NeighbourhoodAdjustment_() const +{ + SwNeighbourAdjust nRet = SwNeighbourAdjust::OnlyAdjust; + if( GetUpper() && !GetUpper()->IsPageBodyFrame() ) + { + // column sections need grow/shrink + if( GetUpper()->IsFlyFrame() ) + nRet = SwNeighbourAdjust::GrowShrink; + else + { + OSL_ENSURE( GetUpper()->IsSctFrame(), "NeighbourhoodAdjustment: Unexpected Upper" ); + if( !GetNext() && !GetPrev() ) + nRet = SwNeighbourAdjust::GrowAdjust; // section with a single column (FootnoteAtEnd) + else + { + const SwFrame* pTmp = Lower(); + OSL_ENSURE( pTmp, "NeighbourhoodAdjustment: Missing Lower()" ); + if( !pTmp->GetNext() ) + nRet = SwNeighbourAdjust::GrowShrink; + else if( !GetUpper()->IsColLocked() ) + nRet = SwNeighbourAdjust::AdjustGrow; + OSL_ENSURE( !pTmp->GetNext() || pTmp->GetNext()->IsFootnoteContFrame(), + "NeighbourhoodAdjustment: Who's that guy?" ); + } + } + } + return nRet; +} + +void SwPageFrame::SetColMaxFootnoteHeight() +{ + SwLayoutFrame *pBody = FindBodyCont(); + if( pBody && pBody->Lower() && pBody->Lower()->IsColumnFrame() ) + { + SwColumnFrame* pCol = static_cast<SwColumnFrame*>(pBody->Lower()); + do + { + pCol->SetMaxFootnoteHeight( GetMaxFootnoteHeight() ); + pCol = static_cast<SwColumnFrame*>(pCol->GetNext()); + } while ( pCol ); + } +} + +bool SwLayoutFrame::MoveLowerFootnotes( SwContentFrame *pStart, SwFootnoteBossFrame *pOldBoss, + SwFootnoteBossFrame *pNewBoss, const bool bFootnoteNums ) +{ + SwDoc *pDoc = GetFormat()->GetDoc(); + if ( pDoc->GetFootnoteIdxs().empty() ) + return false; + if( pDoc->GetFootnoteInfo().m_ePos == FTNPOS_CHAPTER && + ( !IsInSct() || !FindSctFrame()->IsFootnoteAtEnd() ) ) + return true; + + if ( !pNewBoss ) + pNewBoss = FindFootnoteBossFrame( true ); + if ( pNewBoss == pOldBoss ) + return false; + + bool bMoved = false; + if( !pStart ) + pStart = ContainsContent(); + + SwFootnoteFrames aFootnoteArr; + + while ( IsAnLower( pStart ) ) + { + if ( static_cast<SwTextFrame*>(pStart)->HasFootnote() ) + { + // OD 03.04.2003 #108446# - To avoid unnecessary moves of footnotes + // use new parameter <_bCollectOnlyPreviousFootnote> (4th parameter of + // method <SwFootnoteBossFrame::CollectFootnote(..)>) to control, that only + // footnotes have to be collected, that are positioned before the + // new dedicated footnote boss frame. + pNewBoss->CollectFootnotes( pStart, pOldBoss, aFootnoteArr, true ); + } + pStart = pStart->GetNextContentFrame(); + } + + OSL_ENSURE( pOldBoss->IsInSct() == pNewBoss->IsInSct(), + "MoveLowerFootnotes: Section confusion" ); + std::unique_ptr<SwFootnoteFrames> pFootnoteArr; + SwLayoutFrame* pNewChief = nullptr; + SwLayoutFrame* pOldChief = nullptr; + + bool bFoundCandidate = false; + if (pStart && pOldBoss->IsInSct()) + { + pOldChief = pOldBoss->FindSctFrame(); + pNewChief = pNewBoss->FindSctFrame(); + bFoundCandidate = pOldChief != pNewChief; + } + + if (bFoundCandidate) + { + pFootnoteArr.reset(new SwFootnoteFrames); + pOldChief = pOldBoss->FindFootnoteBossFrame( true ); + pNewChief = pNewBoss->FindFootnoteBossFrame( true ); + while( pOldChief->IsAnLower( pStart ) ) + { + if ( static_cast<SwTextFrame*>(pStart)->HasFootnote() ) + static_cast<SwFootnoteBossFrame*>(pNewChief)->CollectFootnotes( pStart, + pOldBoss, *pFootnoteArr ); + pStart = pStart->GetNextContentFrame(); + } + if( pFootnoteArr->empty() ) + { + pFootnoteArr.reset(); + } + } + else + pFootnoteArr = nullptr; + + if ( !aFootnoteArr.empty() || pFootnoteArr ) + { + if( !aFootnoteArr.empty() ) + pNewBoss->MoveFootnotes_( aFootnoteArr, true ); + if( pFootnoteArr ) + { + assert(pNewChief); + static_cast<SwFootnoteBossFrame*>(pNewChief)->MoveFootnotes_( *pFootnoteArr, true ); + pFootnoteArr.reset(); + } + bMoved = true; + + // update FootnoteNum only at page change + if ( bFootnoteNums ) + { + SwPageFrame* pOldPage = pOldBoss->FindPageFrame(); + SwPageFrame* pNewPage =pNewBoss->FindPageFrame(); + if( pOldPage != pNewPage ) + { + pOldPage->UpdateFootnoteNum(); + pNewPage->UpdateFootnoteNum(); + } + } + } + return bMoved; +} + +/// Return value guarantees that a new page was not created. See SwFlowFrame::MoveFwd. +bool SwContentFrame::MoveFootnoteCntFwd( bool bMakePage, SwFootnoteBossFrame *pOldBoss ) +{ + OSL_ENSURE( IsInFootnote(), "no footnote." ); + SwLayoutFrame *pFootnote = FindFootnoteFrame(); + + // The first paragraph in the first footnote in the first column in the + // sectionfrm at the top of the page has not to move forward, if the + // columnbody is empty. + if( pOldBoss->IsInSct() && !pOldBoss->GetIndPrev() && !GetIndPrev() && + !pFootnote->GetPrev() ) + { + SwLayoutFrame* pBody = pOldBoss->FindBodyCont(); + if( !pBody || !pBody->Lower() ) + return true; + } + + //fix(9538): if the footnote has neighbors behind itself, remove them temporarily + SwLayoutFrame *pNxt = static_cast<SwLayoutFrame*>(pFootnote->GetNext()); + SwLayoutFrame *pLst = nullptr; + while ( pNxt ) + { + while ( pNxt->GetNext() ) + pNxt = static_cast<SwLayoutFrame*>(pNxt->GetNext()); + if ( pNxt == pLst ) + pNxt = nullptr; + else + { pLst = pNxt; + SwContentFrame *pCnt = pNxt->ContainsContent(); + if( pCnt ) + pCnt->MoveFootnoteCntFwd( true, pOldBoss ); + pNxt = static_cast<SwLayoutFrame*>(pFootnote->GetNext()); + } + } + + bool bSamePage = true; + SwLayoutFrame *pNewUpper = + GetLeaf( bMakePage ? MAKEPAGE_INSERT : MAKEPAGE_NONE, true ); + + if ( pNewUpper ) + { + SwFootnoteBossFrame * const pNewBoss = pNewUpper->FindFootnoteBossFrame(); + // Are we changing the column/page? + bool bSameBoss = pNewBoss == pOldBoss; + if ( !bSameBoss ) + { + bSamePage = pOldBoss->FindPageFrame() == pNewBoss->FindPageFrame(); // page change? + pNewUpper->Calc(getRootFrame()->GetCurrShell()->GetOut()); + } + + // The layout leaf of the footnote is either a footnote container or a footnote. + // If it is a footnote and it has the same footnote reference like the old Upper, + // then move the content inside of it. + // If it is a container or the reference differs, create a new footnote and add + // it into the container. + // Create also a SectionFrame if currently in an area inside a footnote. + SwFootnoteFrame* pTmpFootnote = pNewUpper->IsFootnoteFrame() ? static_cast<SwFootnoteFrame*>(pNewUpper) : nullptr; + if (!pTmpFootnote) + { + assert(pNewUpper->IsFootnoteContFrame() && "New Upper not a FootnoteCont"); + SwFootnoteContFrame *pCont = static_cast<SwFootnoteContFrame*>(pNewUpper); + pTmpFootnote = SwFootnoteContFrame::AppendChained(this, true); + SwFrame* pNx = pCont->Lower(); + if( pNx && pTmpFootnote->GetAttr()->GetFootnote().IsEndNote() ) + while(pNx && !static_cast<SwFootnoteFrame*>(pNx)->GetAttr()->GetFootnote().IsEndNote()) + pNx = pNx->GetNext(); + pTmpFootnote->Paste( pCont, pNx ); + pTmpFootnote->Calc(getRootFrame()->GetCurrShell()->GetOut()); + } + OSL_ENSURE( pTmpFootnote->GetAttr() == FindFootnoteFrame()->GetAttr(), "Wrong Footnote!" ); + // areas inside of footnotes get a special treatment + SwLayoutFrame *pNewUp = pTmpFootnote; + if( IsInSct() ) + { + SwSectionFrame* pSect = FindSctFrame(); + // area inside of a footnote (or only footnote in an area)? + if( pSect->IsInFootnote() ) + { + if( pTmpFootnote->Lower() && pTmpFootnote->Lower()->IsSctFrame() && + pSect->GetFollow() == static_cast<SwSectionFrame*>(pTmpFootnote->Lower()) ) + pNewUp = static_cast<SwSectionFrame*>(pTmpFootnote->Lower()); + else + { + pNewUp = new SwSectionFrame( *pSect, false ); + pNewUp->InsertBefore( pTmpFootnote, pTmpFootnote->Lower() ); + static_cast<SwSectionFrame*>(pNewUp)->Init(); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pNewUp); + aFrm.Pos() = pTmpFootnote->getFrameArea().Pos(); + aFrm.Pos().AdjustY(1 ); // for notifications + } + + // If the section frame has a successor then the latter needs + // to be moved behind the new Follow of the section frame. + SwFrame* pTmp = pSect->GetNext(); + if( pTmp ) + { + SwFlowFrame* pTmpNxt; + if( pTmp->IsContentFrame() ) + pTmpNxt = static_cast<SwContentFrame*>(pTmp); + else if( pTmp->IsSctFrame() ) + pTmpNxt = static_cast<SwSectionFrame*>(pTmp); + else + { + OSL_ENSURE( pTmp->IsTabFrame(), "GetNextSctLeaf: Wrong Type" ); + pTmpNxt = static_cast<SwTabFrame*>(pTmp); + } + // we will dereference pNewUp in the following MoveSubTree call + // so it certainly should not be deleted before that + SwFrameDeleteGuard aDeleteGuard(pNewUp); + pTmpNxt->MoveSubTree( pTmpFootnote, pNewUp->GetNext() ); + } + } + } + } + + MoveSubTree( pNewUp, pNewUp->Lower() ); + + if( !bSameBoss ) + Prepare( PrepareHint::BossChanged ); + } + return bSamePage; +} + +SwSaveFootnoteHeight::SwSaveFootnoteHeight( SwFootnoteBossFrame *pBs, const SwTwips nDeadLine ) : + aGuard(pBs), + pBoss( pBs ), + nOldHeight( pBs->GetMaxFootnoteHeight() ) +{ + pBoss->SetFootnoteDeadLine( nDeadLine ); + nNewHeight = pBoss->GetMaxFootnoteHeight(); +} + +SwSaveFootnoteHeight::~SwSaveFootnoteHeight() +{ + // If somebody tweaked the deadline meanwhile, we let it happen + if ( nNewHeight == pBoss->GetMaxFootnoteHeight() ) + pBoss->m_nMaxFootnoteHeight = nOldHeight; +} + +#ifdef DBG_UTIL +//JP 15.10.2001: in a non pro version test if the attribute has the same +// meaning which his reference is + +// Normally, the pRef member and the GetRefFromAttr() result has to be +// identically. Sometimes footnote will be moved from a master to its follow, +// but the GetRef() is called first, so we have to ignore a master/follow +// mismatch. + +const SwContentFrame* SwFootnoteFrame::GetRef() const +{ + const SwContentFrame* pRefAttr = GetRefFromAttr(); + // check consistency: access to deleted frame? + assert(mpReference == pRefAttr || mpReference->IsAnFollow(pRefAttr) + || pRefAttr->IsAnFollow(mpReference)); + (void) pRefAttr; + return mpReference; +} + +SwContentFrame* SwFootnoteFrame::GetRef() +{ + const SwContentFrame* pRefAttr = GetRefFromAttr(); + // check consistency: access to deleted frame? + assert(mpReference == pRefAttr || mpReference->IsAnFollow(pRefAttr) + || pRefAttr->IsAnFollow(mpReference)); + (void) pRefAttr; + return mpReference; +} +#endif + +const SwContentFrame* SwFootnoteFrame::GetRefFromAttr() const +{ + SwFootnoteFrame* pThis = const_cast<SwFootnoteFrame*>(this); + return pThis->GetRefFromAttr(); +} + +SwContentFrame* SwFootnoteFrame::GetRefFromAttr() +{ + assert(mpAttribute && "invalid Attribute"); + SwTextNode& rTNd = const_cast<SwTextNode&>(mpAttribute->GetTextNode()); + SwPosition aPos( rTNd, SwIndex( &rTNd, mpAttribute->GetStart() )); + SwContentFrame* pCFrame = rTNd.getLayoutFrame(getRootFrame(), &aPos); + return pCFrame; +} + +/** search for last content in the current footnote frame + + OD 2005-12-02 #i27138# +*/ +SwContentFrame* SwFootnoteFrame::FindLastContent() +{ + SwContentFrame* pLastContentFrame( nullptr ); + + // find last lower, which is a content frame or contains content. + // hidden text frames, empty sections and empty tables have to be skipped. + SwFrame* pLastLowerOfFootnote( GetLower() ); + SwFrame* pTmpLastLower( pLastLowerOfFootnote ); + while ( pTmpLastLower && pTmpLastLower->GetNext() ) + { + pTmpLastLower = pTmpLastLower->GetNext(); + if ( ( pTmpLastLower->IsTextFrame() && + !static_cast<SwTextFrame*>(pTmpLastLower)->IsHiddenNow() ) || + ( pTmpLastLower->IsSctFrame() && + static_cast<SwSectionFrame*>(pTmpLastLower)->GetSection() && + static_cast<SwSectionFrame*>(pTmpLastLower)->ContainsContent() ) || + ( pTmpLastLower->IsTabFrame() && + static_cast<SwTabFrame*>(pTmpLastLower)->ContainsContent() ) ) + { + pLastLowerOfFootnote = pTmpLastLower; + } + } + + // determine last content frame depending on type of found last lower. + if ( pLastLowerOfFootnote && pLastLowerOfFootnote->IsTabFrame() ) + { + pLastContentFrame = static_cast<SwTabFrame*>(pLastLowerOfFootnote)->FindLastContent(); + } + else if ( pLastLowerOfFootnote && pLastLowerOfFootnote->IsSctFrame() ) + { + pLastContentFrame = static_cast<SwSectionFrame*>(pLastLowerOfFootnote)->FindLastContent(); + } + else + { + pLastContentFrame = dynamic_cast<SwContentFrame*>(pLastLowerOfFootnote); + } + + return pLastContentFrame; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/hffrm.cxx b/sw/source/core/layout/hffrm.cxx new file mode 100644 index 000000000..a8e9bfa5a --- /dev/null +++ b/sw/source/core/layout/hffrm.cxx @@ -0,0 +1,768 @@ +/* -*- 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 <pagefrm.hxx> +#include <fmtcntnt.hxx> +#include <fmthdft.hxx> +#include <fmtfsize.hxx> +#include <viewopt.hxx> +#include <hffrm.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> +#include <sectfrm.hxx> +#include <flyfrm.hxx> +#include <frmtool.hxx> +#include <hfspacingitem.hxx> +#include <sortedobjs.hxx> +#include <objectformatter.hxx> +#include <ndindex.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> + +static SwTwips lcl_GetFrameMinHeight(const SwLayoutFrame & rFrame) +{ + const SwFormatFrameSize &rSz = rFrame.GetFormat()->GetFrameSize(); + SwTwips nMinHeight; + + switch (rSz.GetHeightSizeType()) + { + case SwFrameSize::Minimum: + nMinHeight = rSz.GetHeight(); + + break; + + default: + nMinHeight = 0; + } + + return nMinHeight; +} + +static SwTwips lcl_CalcContentHeight(SwLayoutFrame & frm) +{ + SwTwips nRemaining = 0; + SwFrame* pFrame = frm.Lower(); + + while ( pFrame ) + { + SwTwips nTmp; + + nTmp = pFrame->getFrameArea().Height(); + nRemaining += nTmp; + if( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsUndersized() ) + { + nTmp = static_cast<SwTextFrame*>(pFrame)->GetParHeight() + - pFrame->getFramePrintArea().Height(); + // This TextFrame would like to be a bit bigger + nRemaining += nTmp; + } + else if( pFrame->IsSctFrame() && static_cast<SwSectionFrame*>(pFrame)->IsUndersized() ) + { + nTmp = static_cast<SwSectionFrame*>(pFrame)->Undersize(); + nRemaining += nTmp; + } + pFrame = pFrame->GetNext(); + } + + return nRemaining; +} + +static void lcl_LayoutFrameEnsureMinHeight(SwLayoutFrame & rFrame) +{ + SwTwips nMinHeight = lcl_GetFrameMinHeight(rFrame); + + if (rFrame.getFrameArea().Height() < nMinHeight) + { + rFrame.Grow(nMinHeight - rFrame.getFrameArea().Height()); + } +} + +SwHeadFootFrame::SwHeadFootFrame( SwFrameFormat * pFormat, SwFrame* pSib, SwFrameType nTypeIn) + : SwLayoutFrame( pFormat, pSib ) +{ + mnFrameType = nTypeIn; + SetDerivedVert( false ); + + const SwFormatContent &rCnt = pFormat->GetContent(); + + OSL_ENSURE( rCnt.GetContentIdx(), "No content for Header." ); + + // Have the objects created right now for header and footer + bool bOld = bObjsDirect; + bObjsDirect = true; + SwNodeOffset nIndex = rCnt.GetContentIdx()->GetIndex(); + ::InsertCnt_( this, pFormat->GetDoc(), ++nIndex ); + bObjsDirect = bOld; +} + +void SwHeadFootFrame::FormatPrt(SwTwips & nUL, const SwBorderAttrs * pAttrs) +{ + if (GetEatSpacing()) + { + /* The minimal height of the print area is the minimal height of the + frame without the height needed for borders and shadow. */ + SwTwips nMinHeight = lcl_GetFrameMinHeight(*this); + + nMinHeight -= pAttrs->CalcTop(); + nMinHeight -= pAttrs->CalcBottom(); + + /* If the minimal height of the print area is negative, try to + compensate by overlapping */ + SwTwips nOverlap = 0; + if (nMinHeight < 0) + { + nOverlap = -nMinHeight; + nMinHeight = 0; + } + + /* Calculate desired height of content. The minimal height has to be + adhered. */ + SwTwips nHeight; + + if ( ! HasFixSize() ) + nHeight = lcl_CalcContentHeight(*this); + else + nHeight = nMinHeight; + + if (nHeight < nMinHeight) + nHeight = nMinHeight; + + /* calculate initial spacing/line space */ + SwTwips nSpace, nLine; + + if (IsHeaderFrame()) + { + nSpace = pAttrs->CalcBottom(); + nLine = pAttrs->CalcBottomLine(); + } + else + { + nSpace = pAttrs->CalcTop(); + nLine = pAttrs->CalcTopLine(); + } + + /* calculate overlap and correct spacing */ + nOverlap += nHeight - nMinHeight; + if (nOverlap < nSpace - nLine) + nSpace -= nOverlap; + else + nSpace = nLine; + + /* calculate real vertical space between frame and print area */ + if (IsHeaderFrame()) + nUL = pAttrs->CalcTop() + nSpace; + else + nUL = pAttrs->CalcBottom() + nSpace; + + /* set print area */ + SwTwips nLR = pAttrs->CalcLeft( this ) + pAttrs->CalcRight( this ); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + + aPrt.Left(pAttrs->CalcLeft(this)); + + if (IsHeaderFrame()) + { + aPrt.Top(pAttrs->CalcTop()); + } + else + { + aPrt.Top(nSpace); + } + + aPrt.Width(getFrameArea().Width() - nLR); + + SwTwips nNewHeight; + + if (nUL < getFrameArea().Height()) + { + nNewHeight = getFrameArea().Height() - nUL; + } + else + { + nNewHeight = 0; + } + + aPrt.Height(nNewHeight); + } + else + { + // Set position + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Left( pAttrs->CalcLeft( this ) ); + aPrt.Top ( pAttrs->CalcTop() ); + + // Set sizes - the sizes are given by the surrounding Frame, just + // subtract the borders. + SwTwips nLR = pAttrs->CalcLeft( this ) + pAttrs->CalcRight( this ); + aPrt.Width ( getFrameArea().Width() - nLR ); + aPrt.Height( getFrameArea().Height()- nUL ); + } + + setFramePrintAreaValid(true); +} + +void SwHeadFootFrame::FormatSize(SwTwips nUL, const SwBorderAttrs * pAttrs) +{ + if ( !HasFixSize() ) + { + if( !IsColLocked() ) + { + setFramePrintAreaValid(true); + setFrameAreaSizeValid(true); + + const SwTwips nBorder = nUL; + SwTwips nMinHeight = lcl_GetFrameMinHeight(*this); + nMinHeight -= pAttrs->CalcTop(); + nMinHeight -= pAttrs->CalcBottom(); + + if (nMinHeight < 0) + nMinHeight = 0; + + ColLock(); + + SwTwips nMaxHeight = LONG_MAX; + SwTwips nRemaining, nOldHeight; + // #i64301# + // use the position of the footer printing area to control invalidation + // of the first footer content. + Point aOldFooterPrtPos; + + do + { + nOldHeight = getFramePrintArea().Height(); + SwFrame* pFrame = Lower(); + // #i64301# + if ( pFrame && + aOldFooterPrtPos != ( getFrameArea().Pos() + getFramePrintArea().Pos() ) ) + { + pFrame->InvalidatePos_(); + aOldFooterPrtPos = getFrameArea().Pos() + getFramePrintArea().Pos(); + } + int nLoopControl = 0; + while( pFrame ) + { + pFrame->Calc(getRootFrame()->GetCurrShell()->GetOut()); + // #i43771# - format also object anchored + // at the frame + // #i46941# - frame has to be valid. + // Note: frame could be invalid after calling its format, + // if it's locked + OSL_ENSURE( StackHack::IsLocked() || !pFrame->IsTextFrame() || + pFrame->isFrameAreaDefinitionValid() || + static_cast<SwTextFrame*>(pFrame)->IsJoinLocked(), + "<SwHeadFootFrame::FormatSize(..)> - text frame invalid and not locked." ); + + if ( pFrame->IsTextFrame() && pFrame->isFrameAreaDefinitionValid() ) + { + if ( !SwObjectFormatter::FormatObjsAtFrame( *pFrame, + *(pFrame->FindPageFrame()) ) ) + { + if (nLoopControl++ < 20) + { + // restart format with first content + pFrame = Lower(); + continue; + } + else + SAL_WARN("sw", "SwHeadFootFrame::FormatSize: loop detection triggered"); + } + } + pFrame = pFrame->GetNext(); + } + nRemaining = 0; + pFrame = Lower(); + + while ( pFrame ) + { + nRemaining += pFrame->getFrameArea().Height(); + + if( pFrame->IsTextFrame() && + static_cast<SwTextFrame*>(pFrame)->IsUndersized() ) + // This TextFrame would like to be a bit bigger + nRemaining += static_cast<SwTextFrame*>(pFrame)->GetParHeight() + - pFrame->getFramePrintArea().Height(); + else if( pFrame->IsSctFrame() && + static_cast<SwSectionFrame*>(pFrame)->IsUndersized() ) + nRemaining += static_cast<SwSectionFrame*>(pFrame)->Undersize(); + pFrame = pFrame->GetNext(); + } + if ( nRemaining < nMinHeight ) + nRemaining = nMinHeight; + + SwTwips nDiff = nRemaining - nOldHeight; + + if( !nDiff ) + break; + if( nDiff < 0 ) + { + nMaxHeight = nOldHeight; + + if( nRemaining <= nMinHeight ) + nRemaining = ( nMaxHeight + nMinHeight + 1 ) / 2; + } + else + { + if (nOldHeight > nMinHeight) + nMinHeight = nOldHeight; + + if( nRemaining >= nMaxHeight ) + nRemaining = ( nMaxHeight + nMinHeight + 1 ) / 2; + } + + nDiff = nRemaining - nOldHeight; + + if ( nDiff ) + { + ColUnlock(); + if ( nDiff > 0 ) + { + if ( Grow( nDiff ) ) + { + pFrame = Lower(); + + while ( pFrame ) + { + if( pFrame->IsTextFrame()) + { + SwTextFrame * pTmpFrame = static_cast<SwTextFrame*>(pFrame); + if (pTmpFrame->IsUndersized() ) + { + pTmpFrame->InvalidateSize(); + pTmpFrame->Prepare(PrepareHint::AdjustSizeWithoutFormatting); + } + } + /* #i3568# Undersized sections need to be + invalidated too. */ + else if (pFrame->IsSctFrame()) + { + SwSectionFrame * pTmpFrame = + static_cast<SwSectionFrame*>(pFrame); + if (pTmpFrame->IsUndersized() ) + { + pTmpFrame->InvalidateSize(); + pTmpFrame->Prepare(PrepareHint::AdjustSizeWithoutFormatting); + } + } + pFrame = pFrame->GetNext(); + } + } + } + else + Shrink( -nDiff ); + // Quickly update the position + + MakePos(); + ColLock(); + } + else + break; + // Don't overwrite the lower edge of the upper + if ( GetUpper() && getFrameArea().Height() ) + { + const SwTwips nDeadLine = GetUpper()->getFrameArea().Top() + GetUpper()->getFramePrintArea().Bottom(); + const SwTwips nBot = getFrameArea().Bottom(); + + if ( nBot > nDeadLine ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Bottom( nDeadLine ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( getFrameArea().Height() - nBorder ); + } + } + + setFramePrintAreaValid(true); + setFrameAreaSizeValid(true); + } while( nRemaining<=nMaxHeight && nOldHeight!=getFramePrintArea().Height() ); + ColUnlock(); + } + + setFramePrintAreaValid(true); + setFrameAreaSizeValid(true); + } + else //if (GetType() & FRM_HEADFOOT) + { + do + { + if ( getFrameArea().Height() != pAttrs->GetSize().Height() ) + { + ChgSize( Size( getFrameArea().Width(), pAttrs->GetSize().Height())); + } + + setFrameAreaSizeValid(true); + MakePos(); + } while ( !isFrameAreaSizeValid() ); + } +} + +void SwHeadFootFrame::Format(vcl::RenderContext* pRenderContext, const SwBorderAttrs * pAttrs) +{ + OSL_ENSURE( pAttrs, "SwFooterFrame::Format, pAttrs is 0." ); + + if ( isFramePrintAreaValid() && isFrameAreaSizeValid() ) + return; + + if ( ! GetEatSpacing() && IsHeaderFrame()) + { + SwLayoutFrame::Format(pRenderContext, pAttrs); + } + else + { + lcl_LayoutFrameEnsureMinHeight(*this); + + SwTwips nUL = pAttrs->CalcTop() + pAttrs->CalcBottom(); + + if ( !isFramePrintAreaValid() ) + FormatPrt(nUL, pAttrs); + + if ( !isFrameAreaSizeValid() ) + FormatSize(nUL, pAttrs); + } +} + +SwTwips SwHeadFootFrame::GrowFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + SwTwips nResult; + + if ( IsColLocked() ) + { + nResult = 0; + } + else if (!GetEatSpacing()) + { + nResult = SwLayoutFrame::GrowFrame(nDist, bTst, bInfo); + } + else + { + nResult = 0; + + std::optional<SwBorderAttrAccess> oAccess(std::in_place, SwFrame::GetCache(), this); + OSL_ENSURE(oAccess, "no border attributes"); + + SwBorderAttrs * pAttrs = oAccess->Get(); + + /* First assume the whole amount to grow can be provided by eating + spacing. */ + SwTwips nEat = nDist; + SwTwips nMaxEat; + + /* calculate maximum eatable spacing */ + if (IsHeaderFrame()) + nMaxEat = getFrameArea().Height() - getFramePrintArea().Top() - getFramePrintArea().Height() - pAttrs->CalcBottomLine(); + else + nMaxEat = getFramePrintArea().Top() - pAttrs->CalcTopLine(); + + if (nMaxEat < 0) + nMaxEat = 0; + + /* If the frame is too small, eat less spacing thus letting the frame + grow more. */ + SwTwips nMinHeight = lcl_GetFrameMinHeight(*this); + SwTwips nFrameTooSmall = nMinHeight - getFrameArea().Height(); + + if (nFrameTooSmall > 0) + nEat -= nFrameTooSmall; + + /* No negative eating, not eating more than allowed. */ + if (nEat < 0) + nEat = 0; + else if (nEat > nMaxEat) + nEat = nMaxEat; + + // Notify fly frame, if header frame + // grows. Consider, that 'normal' grow of layout frame already notifies + // the fly frames. + bool bNotifyFlys = false; + if (nEat > 0) + { + if ( ! bTst) + { + if (! IsHeaderFrame()) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Top(aPrt.Top() - nEat); + aPrt.Height(aPrt.Height() - nEat); + } + + InvalidateAll(); + } + + nResult += nEat; + // trigger fly frame notify. + if ( IsHeaderFrame() ) + { + bNotifyFlys = true; + } + } + + if (nDist - nEat > 0) + { + const SwTwips nFrameGrow = + SwLayoutFrame::GrowFrame( nDist - nEat, bTst, bInfo ); + + nResult += nFrameGrow; + if ( nFrameGrow > 0 ) + { + bNotifyFlys = false; + } + } + + // notify fly frames, if necessary and triggered. + if ( ( nResult > 0 ) && bNotifyFlys ) + { + NotifyLowerObjs(); + } + } + + if ( nResult && !bTst ) + SetCompletePaint(); + + return nResult; +} + +SwTwips SwHeadFootFrame::ShrinkFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + SwTwips nResult; + + if ( IsColLocked() ) + { + nResult = 0; + } + else if (! GetEatSpacing()) + { + nResult = SwLayoutFrame::ShrinkFrame(nDist, bTst, bInfo); + } + else + { + nResult = 0; + + SwTwips nMinHeight = lcl_GetFrameMinHeight(*this); + SwTwips nOldHeight = getFrameArea().Height(); + SwTwips nRest = 0; // Amount to shrink by spitting out spacing + + if ( nOldHeight >= nMinHeight ) + { + /* If the frame's height is bigger than its minimum height, shrink + the frame towards its minimum height. If this is not sufficient + to provide the shrinking requested provide the rest by spitting + out spacing. */ + + SwTwips nBiggerThanMin = nOldHeight - nMinHeight; + + if (nBiggerThanMin < nDist) + { + nRest = nDist - nBiggerThanMin; + } + /* info: declaration of nRest -> else nRest = 0 */ + } + else + /* The frame cannot shrink. Provide shrinking by spitting out + spacing. */ + nRest = nDist; + + // Notify fly frame, if header/footer frame shrinks. + // Consider, that 'normal' shrink of layout frame already notifies the fly frames. + bool bNotifyFlys = false; + if (nRest > 0) + { + std::optional<SwBorderAttrAccess> oAccess(std::in_place, SwFrame::GetCache(), this); + OSL_ENSURE(oAccess, "no border attributes"); + + SwBorderAttrs * pAttrs = oAccess->Get(); + + /* minimal height of print area */ + SwTwips nMinPrtHeight = nMinHeight + - pAttrs->CalcTop() + - pAttrs->CalcBottom(); + + if (nMinPrtHeight < 0) + nMinPrtHeight = 0; + + /* assume all shrinking can be provided */ + SwTwips nShrink = nRest; + + /* calculate maximum shrinking */ + SwTwips nMaxShrink = getFramePrintArea().Height() - nMinPrtHeight; + + /* shrink no more than maximum shrinking */ + if (nShrink > nMaxShrink) + { + //nRest -= nShrink - nMaxShrink; + nShrink = nMaxShrink; + } + + if (!bTst) + { + if (! IsHeaderFrame() ) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Top(aPrt.Top() + nShrink); + aPrt.Height(aPrt.Height() - nShrink); + } + + InvalidateAll(); + } + nResult += nShrink; + // Trigger fly frame notify. + if ( IsHeaderFrame() ) + { + bNotifyFlys = true; + } + } + + /* The shrinking not providable by spitting out spacing has to be done + by the frame. */ + if (nDist - nRest > 0) + { + SwTwips nShrinkAmount = SwLayoutFrame::ShrinkFrame( nDist - nRest, bTst, bInfo ); + nResult += nShrinkAmount; + if ( nShrinkAmount > 0 ) + { + bNotifyFlys = false; + } + } + + // Notify fly frames, if necessary. + if ( ( nResult > 0 ) && bNotifyFlys ) + { + NotifyLowerObjs(); + } + } + + return nResult; +} + +bool SwHeadFootFrame::GetEatSpacing() const +{ + const SwFrameFormat * pFormat = GetFormat(); + OSL_ENSURE(pFormat, "SwHeadFootFrame: no format?"); + + return pFormat->GetHeaderAndFooterEatSpacing().GetValue(); +} + +static void DelFlys( const SwLayoutFrame& rFrame, SwPageFrame &rPage) +{ + size_t i = 0; + while ( rPage.GetSortedObjs() && + rPage.GetSortedObjs()->size() && + i < rPage.GetSortedObjs()->size() ) + { + SwAnchoredObject* pObj = (*rPage.GetSortedObjs())[i]; + if (SwFlyFrame* pFlyFrame = pObj->DynCastFlyFrame()) + { + if (rFrame.IsAnLower(pFlyFrame)) + { + SwFrame::DestroyFrame(pFlyFrame); + // Do not increment index, in this case + continue; + } + } + ++i; + } +} + +/// Creates or removes headers +void SwPageFrame::PrepareHeader() +{ + SwLayoutFrame *pLay = static_cast<SwLayoutFrame*>(Lower()); + if ( !pLay ) + return; + + const SwFormatHeader &rH = static_cast<SwFrameFormat*>(GetDep())->GetHeader(); + + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bOn = !(pSh && (pSh->GetViewOptions()->getBrowseMode() || + pSh->GetViewOptions()->IsWhitespaceHidden())); + + if ( bOn && rH.IsActive() ) + { //Implant header, but remove first, if already present + OSL_ENSURE( rH.GetHeaderFormat(), "FrameFormat for Header not found." ); + + if ( pLay->GetFormat() == rH.GetHeaderFormat() ) + return; // Header is already the correct one. + + if ( pLay->IsHeaderFrame() ) + { SwLayoutFrame *pDel = pLay; + pLay = static_cast<SwLayoutFrame*>(pLay->GetNext()); + ::DelFlys(*pDel, *this); + pDel->Cut(); + SwFrame::DestroyFrame(pDel); + } + OSL_ENSURE( pLay, "Where to with the Header?" ); + SwHeaderFrame *pH = new SwHeaderFrame( const_cast<SwFrameFormat*>(rH.GetHeaderFormat()), this ); + pH->Paste( this, pLay ); + if ( GetUpper() ) + ::RegistFlys( this, pH ); + } + else if (pLay->IsHeaderFrame()) + { // Remove header if present. + ::DelFlys(*pLay, *this); + pLay->Cut(); + SwFrame::DestroyFrame(pLay); + } +} + +/// Creates or removes footer +void SwPageFrame::PrepareFooter() +{ + SwLayoutFrame *pLay = static_cast<SwLayoutFrame*>(Lower()); + if ( !pLay ) + return; + + const SwFormatFooter &rF = static_cast<SwFrameFormat*>(GetDep())->GetFooter(); + while ( pLay->GetNext() ) + pLay = static_cast<SwLayoutFrame*>(pLay->GetNext()); + + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bOn = !(pSh && (pSh->GetViewOptions()->getBrowseMode() || + pSh->GetViewOptions()->IsWhitespaceHidden())); + + if ( bOn && rF.IsActive() ) + { //Implant footer, but remove first, if already present + OSL_ENSURE( rF.GetFooterFormat(), "FrameFormat for Footer not found." ); + + if ( pLay->GetFormat() == rF.GetFooterFormat() ) + return; // Footer is already the correct one. + + if ( pLay->IsFooterFrame() ) + { + ::DelFlys(*pLay, *this); + pLay->Cut(); + SwFrame::DestroyFrame(pLay); + } + SwFooterFrame *pF = new SwFooterFrame( const_cast<SwFrameFormat*>(rF.GetFooterFormat()), this ); + pF->Paste( this ); + if ( GetUpper() ) + ::RegistFlys( this, pF ); + } + else if ( pLay->IsFooterFrame() ) + { + // Remove footer if already present + ::DelFlys(*pLay, *this); + SwViewShell *pShell; + if ( pLay->GetPrev() && nullptr != (pShell = getRootFrame()->GetCurrShell()) && + pShell->VisArea().HasArea() ) + pShell->InvalidateWindows( pShell->VisArea() ); + pLay->Cut(); + SwFrame::DestroyFrame(pLay); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/layact.cxx b/sw/source/core/layout/layact.cxx new file mode 100644 index 000000000..6cdfcfce0 --- /dev/null +++ b/sw/source/core/layout/layact.cxx @@ -0,0 +1,2419 @@ +/* -*- 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 <config_feature_desktop.h> +#include <config_wasm_strip.h> + +#include <ctime> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <viewimp.hxx> +#include <crsrsh.hxx> +#include <dflyobj.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <viewopt.hxx> +#include <dbg_lay.hxx> +#include <layouter.hxx> +#include <docstat.hxx> +#include <swevent.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentStatistics.hxx> +#include <IDocumentLayoutAccess.hxx> + +#include <sfx2/event.hxx> + +#include <ftnidx.hxx> +#include <vcl/svapp.hxx> +#include <editeng/opaqitem.hxx> +#include <SwSmartTagMgr.hxx> +#include <sal/log.hxx> + +#include <layact.hxx> +#include <swwait.hxx> +#include <fmtsrnd.hxx> +#include <docsh.hxx> + +#include <anchoreddrawobject.hxx> +#include <ndtxt.hxx> +#include <tabfrm.hxx> +#include <ftnfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <flyfrms.hxx> +#include <mdiexp.hxx> +#include <sectfrm.hxx> +#include <acmplwrd.hxx> +#include <deletelistener.hxx> +#include <sortedobjs.hxx> +#include <objectformatter.hxx> +#include <fntcache.hxx> +#include <fmtanchr.hxx> +#include <comphelper/scopeguard.hxx> +#include <vector> +#include <tools/diagnose_ex.h> + +void SwLayAction::CheckWaitCursor() +{ + if (IsReschedule()) + { + ::RescheduleProgress(m_pImp->GetShell()->GetDoc()->GetDocShell()); + } + if ( !m_pWait && IsWaitAllowed() && IsPaint() && + ((std::clock() - m_nStartTicks) * 1000 / CLOCKS_PER_SEC >= CLOCKS_PER_SEC/2) ) + { + m_pWait.reset( new SwWait( *m_pRoot->GetFormat()->GetDoc()->GetDocShell(), true ) ); + } +} + +// Time over already? +inline void SwLayAction::CheckIdleEnd() +{ + if (!IsInterrupt()) + m_bInterrupt = bool(GetInputType()) && Application::AnyInput(GetInputType()); +} + +void SwLayAction::SetStatBar( bool bNew ) +{ + if ( bNew ) + { + m_nEndPage = m_pRoot->GetPageNum(); + m_nEndPage += m_nEndPage * 10 / 100; + } + else + m_nEndPage = USHRT_MAX; +} + +bool SwLayAction::PaintWithoutFlys( const SwRect &rRect, const SwContentFrame *pCnt, + const SwPageFrame *pPage ) +{ + SwRegionRects aTmp( rRect ); + const SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + const SwFlyFrame *pSelfFly = pCnt->FindFlyFrame(); + + for ( size_t i = 0; i < rObjs.size() && !aTmp.empty(); ++i ) + { + SwVirtFlyDrawObj *pVirtFly = dynamic_cast<SwVirtFlyDrawObj*>(rObjs[i]->DrawObj()); + if ( !pVirtFly ) + continue; + + // do not consider invisible objects + const IDocumentDrawModelAccess& rIDDMA = pPage->GetFormat()->getIDocumentDrawModelAccess(); + if ( !rIDDMA.IsVisibleLayerId( pVirtFly->GetLayer() ) ) + { + continue; + } + + SwFlyFrame *pFly = pVirtFly->GetFlyFrame(); + + if ( pFly == pSelfFly || !rRect.Overlaps( pFly->getFrameArea() ) ) + continue; + + if ( pSelfFly && pSelfFly->IsLowerOf( pFly ) ) + continue; + + if ( pFly->GetVirtDrawObj()->GetLayer() == rIDDMA.GetHellId() ) + continue; + + if ( pSelfFly ) + { + const SdrObject *pTmp = pSelfFly->GetVirtDrawObj(); + if ( pVirtFly->GetLayer() == pTmp->GetLayer() ) + { + if ( pVirtFly->GetOrdNumDirect() < pTmp->GetOrdNumDirect() ) + // Only look at things above us, if inside the same layer + continue; + } + else + { + const bool bLowerOfSelf = pFly->IsLowerOf( pSelfFly ); + if ( !bLowerOfSelf && !pFly->GetFormat()->GetOpaque().GetValue() ) + // Things from other layers are only interesting to us if + // they're not transparent or lie inwards + continue; + } + } + + // Fly frame without a lower have to be subtracted from paint region. + // For checking, if fly frame contains transparent graphic or + // has surrounded contour, assure that fly frame has a lower + if ( pFly->Lower() && + pFly->Lower()->IsNoTextFrame() && + ( static_cast<SwNoTextFrame*>(pFly->Lower())->IsTransparent() || + pFly->GetFormat()->GetSurround().IsContour() ) + ) + { + continue; + } + + // vcl::Region of a fly frame with transparent background or a transparent + // shadow have not to be subtracted from paint region + if ( pFly->IsBackgroundTransparent() ) + { + continue; + } + + aTmp -= pFly->getFrameArea(); + } + + bool bRetPaint = false; + for ( const auto& rRegionRect : aTmp ) + bRetPaint |= m_pImp->GetShell()->AddPaintRect( rRegionRect ); + return bRetPaint; +} + +inline bool SwLayAction::PaintContent_( const SwContentFrame *pContent, + const SwPageFrame *pPage, + const SwRect &rRect ) +{ + if ( rRect.HasArea() ) + { + if ( pPage->GetSortedObjs() ) + return PaintWithoutFlys( rRect, pContent, pPage ); + else + return m_pImp->GetShell()->AddPaintRect( rRect ); + } + return false; +} + +/** + * Depending of the type, the Content is output according to its changes, or the area + * to be outputted is registered with the region, respectively. + */ +void SwLayAction::PaintContent( const SwContentFrame *pCnt, + const SwPageFrame *pPage, + const SwRect &rOldRect, + tools::Long nOldBottom ) +{ + SwRectFnSet aRectFnSet(pCnt); + + if ( pCnt->IsCompletePaint() || !pCnt->IsTextFrame() ) + { + SwRect aPaint( pCnt->GetPaintArea() ); + if ( !PaintContent_( pCnt, pPage, aPaint ) ) + pCnt->ResetCompletePaint(); + } + else + { + // paint the area between printing bottom and frame bottom and + // the area left and right beside the frame, if its height changed. + tools::Long nOldHeight = aRectFnSet.GetHeight(rOldRect); + tools::Long nNewHeight = aRectFnSet.GetHeight(pCnt->getFrameArea()); + const bool bHeightDiff = nOldHeight != nNewHeight; + if( bHeightDiff ) + { + // consider whole potential paint area. + SwRect aDrawRect( pCnt->GetPaintArea() ); + if( nOldHeight > nNewHeight ) + nOldBottom = aRectFnSet.GetPrtBottom(*pCnt); + aRectFnSet.SetTop( aDrawRect, nOldBottom ); + PaintContent_( pCnt, pPage, aDrawRect ); + } + // paint content area + SwRect aPaintRect = static_cast<SwTextFrame*>(const_cast<SwContentFrame*>(pCnt))->GetPaintSwRect(); + PaintContent_( pCnt, pPage, aPaintRect ); + } + + if ( !pCnt->IsRetouche() || pCnt->GetNext() ) + return; + + const SwFrame *pTmp = pCnt; + if( pCnt->IsInSct() ) + { + const SwSectionFrame* pSct = pCnt->FindSctFrame(); + if( pSct->IsRetouche() && !pSct->GetNext() ) + pTmp = pSct; + } + SwRect aRect( pTmp->GetUpper()->GetPaintArea() ); + aRectFnSet.SetTop( aRect, aRectFnSet.GetPrtBottom(*pTmp) ); + if ( !PaintContent_( pCnt, pPage, aRect ) ) + pCnt->ResetRetouche(); +} + +SwLayAction::SwLayAction( SwRootFrame *pRt, SwViewShellImp *pI ) : + m_pRoot( pRt ), + m_pImp( pI ), + m_pOptTab( nullptr ), + m_nPreInvaPage( USHRT_MAX ), + m_nStartTicks( std::clock() ), + m_nInputType( VclInputFlags::NONE ), + m_nEndPage( USHRT_MAX ), + m_nCheckPageNum( USHRT_MAX ) +{ + m_bPaintExtraData = ::IsExtraData( m_pImp->GetShell()->GetDoc() ); + m_bPaint = m_bComplete = m_bWaitAllowed = m_bCheckPages = true; + m_bInterrupt = m_bAgain = m_bNextCycle = m_bCalcLayout = m_bIdle = m_bReschedule = + m_bUpdateExpFields = m_bBrowseActionStop = m_bActionInProgress = false; + // init new flag <mbFormatContentOnInterrupt>. + mbFormatContentOnInterrupt = false; + + assert(!m_pImp->m_pLayAction); // there can be only one SwLayAction + m_pImp->m_pLayAction = this; // register there +} + +SwLayAction::~SwLayAction() +{ + OSL_ENSURE( !m_pWait, "Wait object not destroyed" ); + m_pImp->m_pLayAction = nullptr; // unregister +} + +void SwLayAction::Reset() +{ + SetAgain(false); + m_pOptTab = nullptr; + m_nStartTicks = std::clock(); + m_nInputType = VclInputFlags::NONE; + m_nEndPage = m_nPreInvaPage = m_nCheckPageNum = USHRT_MAX; + m_bPaint = m_bComplete = m_bWaitAllowed = m_bCheckPages = true; + m_bInterrupt = m_bNextCycle = m_bCalcLayout = m_bIdle = m_bReschedule = + m_bUpdateExpFields = m_bBrowseActionStop = false; +} + +bool SwLayAction::RemoveEmptyBrowserPages() +{ + // switching from the normal to the browser mode, empty pages may be + // retained for an annoyingly long time, so delete them here + bool bRet = false; + const SwViewShell *pSh = m_pRoot->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + { + SwPageFrame *pPage = static_cast<SwPageFrame*>(m_pRoot->Lower()); + do + { + if ( (pPage->GetSortedObjs() && pPage->GetSortedObjs()->size()) || + pPage->ContainsContent() || + pPage->FindFootnoteCont() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + else + { + bRet = true; + SwPageFrame *pDel = pPage; + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + pDel->Cut(); + SwFrame::DestroyFrame(pDel); + } + } while ( pPage ); + } + return bRet; +} + +void SwLayAction::SetAgain(bool bAgain) +{ + if (bAgain == m_bAgain) + return; + + m_bAgain = bAgain; + + assert(m_aFrameStack.size() == m_aFrameDeleteGuards.size()); + size_t nCount = m_aFrameStack.size(); + if (m_bAgain) + { + // LayAction::FormatLayout is now flagged to exit early and will avoid + // dereferencing any SwFrames in the stack of FormatLayouts so allow + // their deletion + for (size_t i = 0; i < nCount; ++i) + m_aFrameDeleteGuards[i].reset(); + } + else + { + // LayAction::FormatLayout is now continue normally and will + // dereference the top SwFrame in the stack of m_aFrameStack as each + // FormatLevel returns so disallow their deletion + for (size_t i = 0; i < nCount; ++i) + m_aFrameDeleteGuards[i] = std::make_unique<SwFrameDeleteGuard>(m_aFrameStack[i]); + } +} + +void SwLayAction::PushFormatLayout(SwFrame* pLow) +{ + /* Workaround crash seen in crashtesting with fdo53985-1.docx + + Lock pLow against getting deleted when it will be dereferenced + after FormatLayout + + If SetAgain is called to make SwLayAction exit early to avoid that + dereference, then it clears these guards + */ + m_aFrameStack.push_back(pLow); + m_aFrameDeleteGuards.push_back(std::make_unique<SwFrameDeleteGuard>(pLow)); +} + +void SwLayAction::PopFormatLayout() +{ + m_aFrameDeleteGuards.pop_back(); + m_aFrameStack.pop_back(); +} + +void SwLayAction::Action(OutputDevice* pRenderContext) +{ + m_bActionInProgress = true; + + //TurboMode? Hands-off during idle-format + if ( IsPaint() && !IsIdle() && TurboAction() ) + { + m_pWait.reset(); + m_pRoot->ResetTurboFlag(); + m_bActionInProgress = false; + m_pRoot->DeleteEmptySct(); + return; + } + else if ( m_pRoot->GetTurbo() ) + { + m_pRoot->DisallowTurbo(); + const SwFrame *pFrame = m_pRoot->GetTurbo(); + m_pRoot->ResetTurbo(); + pFrame->InvalidatePage(); + } + m_pRoot->DisallowTurbo(); + + if ( IsCalcLayout() ) + SetCheckPages( false ); + + InternalAction(pRenderContext); + if (RemoveEmptyBrowserPages()) + SetAgain(true); + while ( IsAgain() ) + { + SetAgain(false); + m_bNextCycle = false; + InternalAction(pRenderContext); + if (RemoveEmptyBrowserPages()) + SetAgain(true); + } + m_pRoot->DeleteEmptySct(); + + m_pWait.reset(); + + //Turbo-Action permitted again for all cases. + m_pRoot->ResetTurboFlag(); + m_pRoot->ResetTurbo(); + + SetCheckPages( true ); + + m_bActionInProgress = false; +} + +SwPageFrame* SwLayAction::CheckFirstVisPage( SwPageFrame *pPage ) +{ + SwContentFrame *pCnt = pPage->FindFirstBodyContent(); + SwContentFrame *pChk = pCnt; + bool bPageChgd = false; + while ( pCnt && pCnt->IsFollow() ) + pCnt = pCnt->FindMaster(); + if ( pCnt && pChk != pCnt ) + { bPageChgd = true; + pPage = pCnt->FindPageFrame(); + } + + if ( !pPage->GetFormat()->GetDoc()->GetFootnoteIdxs().empty() ) + { + SwFootnoteContFrame *pCont = pPage->FindFootnoteCont(); + if ( pCont ) + { + pCnt = pCont->ContainsContent(); + pChk = pCnt; + while ( pCnt && pCnt->IsFollow() ) + pCnt = static_cast<SwContentFrame*>(pCnt->FindPrev()); + if ( pCnt && pCnt != pChk ) + { + if ( bPageChgd ) + { + // Use the 'topmost' page + SwPageFrame *pTmp = pCnt->FindPageFrame(); + if ( pPage->GetPhyPageNum() > pTmp->GetPhyPageNum() ) + pPage = pTmp; + } + else + pPage = pCnt->FindPageFrame(); + } + } + } + return pPage; +} + +// unlock position on start and end of page +// layout process. +static void unlockPositionOfObjects( SwPageFrame *pPageFrame ) +{ + assert( pPageFrame ); + + SwSortedObjs* pObjs = pPageFrame->GetSortedObjs(); + if ( pObjs ) + { + for (SwAnchoredObject* pObj : *pObjs) + { + pObj->UnlockPosition(); + } + } +} + +void SwLayAction::InternalAction(OutputDevice* pRenderContext) +{ + OSL_ENSURE( m_pRoot->Lower()->IsPageFrame(), ":-( No page below the root."); + + m_pRoot->Calc(pRenderContext); + + // Figure out the first invalid page or the first one to be formatted, + // respectively. A complete-action means the first invalid page. + // However, the first page to be formatted might be the one having the + // number 1. If we're doing a fake formatting, the number of the first + // page is the number of the first visible page. + SwPageFrame *pPage = IsComplete() ? static_cast<SwPageFrame*>(m_pRoot->Lower()) : + m_pImp->GetFirstVisPage(pRenderContext); + if ( !pPage ) + pPage = static_cast<SwPageFrame*>(m_pRoot->Lower()); + + // If there's a first-flow-Content in the first visible page that's also a Follow, + // we switch the page back to the original master of that Content. + if ( !IsComplete() ) + pPage = CheckFirstVisPage( pPage ); + sal_uInt16 nFirstPageNum = pPage->GetPhyPageNum(); + + while ( pPage && !pPage->IsInvalid() && !pPage->IsInvalidFly() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + + IDocumentLayoutAccess& rLayoutAccess = m_pRoot->GetFormat()->getIDocumentLayoutAccess(); + const bool bNoLoop = pPage && SwLayouter::StartLoopControl(m_pRoot->GetFormat()->GetDoc(), pPage); + sal_uInt16 nPercentPageNum = 0; + + auto lcl_isLayoutLooping = [&]() + { + const bool bAgain = this->IsAgain(); + if (bAgain && bNoLoop) + rLayoutAccess.GetLayouter()->EndLoopControl(); + return bAgain; + }; + + while ( (pPage && !IsInterrupt()) || m_nCheckPageNum != USHRT_MAX ) + { + // note: this is the only place that consumes and resets m_nCheckPageNum + if ((IsInterrupt() || !pPage) && m_nCheckPageNum != USHRT_MAX) + { + if (!pPage || m_nCheckPageNum < pPage->GetPhyPageNum()) + { + SwPageFrame *pPg = static_cast<SwPageFrame*>(m_pRoot->Lower()); + while (pPg && pPg->GetPhyPageNum() < m_nCheckPageNum) + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + if (pPg) + pPage = pPg; + if (!pPage) + break; + } + + SwPageFrame *pTmp = pPage->GetPrev() ? + static_cast<SwPageFrame*>(pPage->GetPrev()) : pPage; + SetCheckPages( true ); + SwFrame::CheckPageDescs( pPage, true, &pTmp ); + SetCheckPages( false ); + m_nCheckPageNum = USHRT_MAX; + pPage = pTmp; + continue; + } + + if ( m_nEndPage != USHRT_MAX && pPage->GetPhyPageNum() > nPercentPageNum ) + { + nPercentPageNum = pPage->GetPhyPageNum(); + ::SetProgressState( nPercentPageNum, m_pImp->GetShell()->GetDoc()->GetDocShell()); + } + m_pOptTab = nullptr; + + // No Shortcut for Idle or CalcLayout + const bool bTakeShortcut = !IsIdle() && !IsComplete() && IsShortCut(pPage); + + m_pRoot->DeleteEmptySct(); + if (lcl_isLayoutLooping()) return; + + if (!bTakeShortcut) + { + while ( !IsInterrupt() && !IsNextCycle() && + ((pPage->GetSortedObjs() && pPage->IsInvalidFly()) || pPage->IsInvalid()) ) + { + unlockPositionOfObjects( pPage ); + + SwObjectFormatter::FormatObjsAtFrame( *pPage, *pPage, this ); + if ( !pPage->GetSortedObjs() ) + { + // If there are no (more) Flys, the flags are superfluous. + pPage->ValidateFlyLayout(); + pPage->ValidateFlyContent(); + } + // change condition + while ( !IsInterrupt() && !IsNextCycle() && + ( pPage->IsInvalid() || + (pPage->GetSortedObjs() && pPage->IsInvalidFly()) ) ) + { + PROTOCOL( pPage, PROT::FileInit, DbgAction::NONE, nullptr) + if (lcl_isLayoutLooping()) return; + + // new loop control + int nLoopControlRuns_1 = 0; + const int nLoopControlMax = 20; + + while ( !IsNextCycle() && pPage->IsInvalidLayout() ) + { + pPage->ValidateLayout(); + + if ( ++nLoopControlRuns_1 > nLoopControlMax ) + { + OSL_FAIL( "LoopControl_1 in SwLayAction::InternalAction" ); + break; + } + + FormatLayout( pRenderContext, pPage ); + if (lcl_isLayoutLooping()) return; + } + // change condition + if ( !IsNextCycle() && + ( pPage->IsInvalidContent() || + (pPage->GetSortedObjs() && pPage->IsInvalidFly()) ) ) + { + pPage->ValidateFlyInCnt(); + pPage->ValidateContent(); + pPage->ValidateFlyLayout(); + pPage->ValidateFlyContent(); + if ( !FormatContent( pPage ) ) + { + if (lcl_isLayoutLooping()) return; + pPage->InvalidateContent(); + pPage->InvalidateFlyInCnt(); + pPage->InvalidateFlyLayout(); + pPage->InvalidateFlyContent(); + if ( IsBrowseActionStop() ) + m_bInterrupt = true; + } + } + if( bNoLoop ) + rLayoutAccess.GetLayouter()->LoopControl( pPage ); + } + + unlockPositionOfObjects( pPage ); + } + + // A previous page may be invalid again. + if (lcl_isLayoutLooping()) return; + if ( !pPage->GetSortedObjs() ) + { + // If there are no (more) Flys, the flags are superfluous. + pPage->ValidateFlyLayout(); + pPage->ValidateFlyContent(); + } + if ( !IsInterrupt() ) + { + SetNextCycle( false ); + + if ( m_nPreInvaPage != USHRT_MAX ) + { + if( !IsComplete() && m_nPreInvaPage + 2 < nFirstPageNum ) + { + m_pImp->SetFirstVisPageInvalid(); + SwPageFrame *pTmpPage = m_pImp->GetFirstVisPage(pRenderContext); + nFirstPageNum = pTmpPage->GetPhyPageNum(); + if( m_nPreInvaPage < nFirstPageNum ) + { + m_nPreInvaPage = nFirstPageNum; + pPage = pTmpPage; + } + } + while ( pPage->GetPrev() && pPage->GetPhyPageNum() > m_nPreInvaPage ) + pPage = static_cast<SwPageFrame*>(pPage->GetPrev()); + m_nPreInvaPage = USHRT_MAX; + } + + while ( pPage->GetPrev() && + ( static_cast<SwPageFrame*>(pPage->GetPrev())->IsInvalid() || + ( static_cast<SwPageFrame*>(pPage->GetPrev())->GetSortedObjs() && + static_cast<SwPageFrame*>(pPage->GetPrev())->IsInvalidFly())) && + (static_cast<SwPageFrame*>(pPage->GetPrev())->GetPhyPageNum() >= + nFirstPageNum) ) + { + pPage = static_cast<SwPageFrame*>(pPage->GetPrev()); + } + + // Continue to the next invalid page + while ( pPage && !pPage->IsInvalid() && + (!pPage->GetSortedObjs() || !pPage->IsInvalidFly()) ) + { + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + if( bNoLoop ) + rLayoutAccess.GetLayouter()->LoopControl( pPage ); + } + CheckIdleEnd(); + } + + if ((bTakeShortcut || !pPage) && !IsInterrupt() && + (m_pRoot->IsSuperfluous() || m_pRoot->IsAssertFlyPages()) ) + { + // tdf#139426 allow suppression of AssertFlyPages + if ( m_pRoot->IsAssertFlyPages() && !m_pRoot->IsTableUpdateInProgress()) + { + m_pRoot->AssertFlyPages(); + } + if ( m_pRoot->IsSuperfluous() ) + { + bool bOld = IsAgain(); + m_pRoot->RemoveSuperfluous(); + SetAgain(bOld); + } + if (lcl_isLayoutLooping()) return; + pPage = static_cast<SwPageFrame*>(m_pRoot->Lower()); + while ( pPage && !pPage->IsInvalid() && !pPage->IsInvalidFly() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + while ( pPage && pPage->GetNext() && + pPage->GetPhyPageNum() < nFirstPageNum ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + else if (bTakeShortcut) + break; + } + if ( IsInterrupt() && pPage ) + { + // If we have input, we don't want to format content anymore, but + // we still should clean the layout. + // Otherwise, the following situation might arise: + // The user enters some text at the end of the paragraph of the last + // page, causing the paragraph to create a Follow for the next page. + // Meanwhile the user continues typing, so we have input while + // still formatting. + // The paragraph on the new page has already been partially formatted, + // and the new page has been fully formatted and is set to CompletePaint, + // but hasn't added itself to the area to be output. Then we paint, + // the CompletePaint of the page is reset because the new paragraph + // already added itself, but the borders of the page haven't been painted + // yet. + // Oh well, with the inevitable following LayAction, the page doesn't + // register itself, because it's (LayoutFrame) flags have been reset + // already - the border of the page will never be painted. + SwPageFrame *pPg = pPage; + if (lcl_isLayoutLooping()) return; + const SwRect &rVis = m_pImp->GetShell()->VisArea(); + + while( pPg && pPg->getFrameArea().Bottom() < rVis.Top() ) + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + if( pPg != pPage ) + pPg = pPg ? static_cast<SwPageFrame*>(pPg->GetPrev()) : pPage; + + // set flag for interrupt content formatting + mbFormatContentOnInterrupt = true; + tools::Long nBottom = rVis.Bottom(); + // #i42586# - format current page, if idle action is active + // This is an optimization for the case that the interrupt is created by + // the move of a form control object, which is represented by a window. + while ( pPg && ( pPg->getFrameArea().Top() < nBottom || + ( IsIdle() && pPg == pPage ) ) ) + { + unlockPositionOfObjects( pPg ); + + if (lcl_isLayoutLooping()) return; + + // new loop control + int nLoopControlRuns_2 = 0; + const int nLoopControlMax = 20; + + // special case: interrupt content formatting + // conditions are incorrect and are too strict. + // adjust interrupt formatting to normal page formatting - see above. + while ( ( mbFormatContentOnInterrupt && + ( pPg->IsInvalid() || + ( pPg->GetSortedObjs() && pPg->IsInvalidFly() ) ) ) || + ( !mbFormatContentOnInterrupt && pPg->IsInvalidLayout() ) ) + { + if (lcl_isLayoutLooping()) return; + // format also at-page anchored objects + SwObjectFormatter::FormatObjsAtFrame( *pPg, *pPg, this ); + if ( !pPg->GetSortedObjs() ) + { + pPg->ValidateFlyLayout(); + pPg->ValidateFlyContent(); + } + + // new loop control + int nLoopControlRuns_3 = 0; + + while ( pPg->IsInvalidLayout() ) + { + pPg->ValidateLayout(); + + if ( ++nLoopControlRuns_3 > nLoopControlMax ) + { + OSL_FAIL( "LoopControl_3 in Interrupt formatting in SwLayAction::InternalAction" ); + break; + } + + FormatLayout( pRenderContext, pPg ); + if (lcl_isLayoutLooping()) return; + } + + if ( mbFormatContentOnInterrupt && + ( pPg->IsInvalidContent() || + ( pPg->GetSortedObjs() && pPg->IsInvalidFly() ) ) ) + { + pPg->ValidateFlyInCnt(); + pPg->ValidateContent(); + pPg->ValidateFlyLayout(); + pPg->ValidateFlyContent(); + + if ( ++nLoopControlRuns_2 > nLoopControlMax ) + { + OSL_FAIL( "LoopControl_2 in Interrupt formatting in SwLayAction::InternalAction" ); + break; + } + + if ( !FormatContent( pPg ) ) + { + if (lcl_isLayoutLooping()) return; + pPg->InvalidateContent(); + pPg->InvalidateFlyInCnt(); + pPg->InvalidateFlyLayout(); + pPg->InvalidateFlyContent(); + } + // we are satisfied if the content is formatted once complete. + else + { + break; + } + } + } + + unlockPositionOfObjects( pPg ); + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + } + // reset flag for special interrupt content formatting. + mbFormatContentOnInterrupt = false; + } + m_pOptTab = nullptr; + if( bNoLoop ) + rLayoutAccess.GetLayouter()->EndLoopControl(); +} + +bool SwLayAction::TurboAction_( const SwContentFrame *pCnt ) +{ + + const SwPageFrame *pPage = nullptr; + if ( !pCnt->isFrameAreaDefinitionValid() || pCnt->IsCompletePaint() || pCnt->IsRetouche() ) + { + const SwRect aOldRect( pCnt->UnionFrame( true ) ); + const tools::Long nOldBottom = pCnt->getFrameArea().Top() + pCnt->getFramePrintArea().Bottom(); + pCnt->Calc(m_pImp->GetShell()->GetOut()); + if ( pCnt->getFrameArea().Bottom() < aOldRect.Bottom() ) + pCnt->SetRetouche(); + + pPage = pCnt->FindPageFrame(); + PaintContent( pCnt, pPage, aOldRect, nOldBottom ); + + if ( !pCnt->GetValidLineNumFlag() && pCnt->IsTextFrame() ) + { + const sal_uLong nAllLines = static_cast<const SwTextFrame*>(pCnt)->GetAllLines(); + const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pCnt))->RecalcAllLines(); + if ( nAllLines != static_cast<const SwTextFrame*>(pCnt)->GetAllLines() ) + { + if ( IsPaintExtraData() ) + m_pImp->GetShell()->AddPaintRect( pCnt->getFrameArea() ); + // This is to calculate the remaining LineNums on the page, + // and we don't stop processing here. To perform this inside RecalcAllLines + // would be expensive, because we would have to notify the page even + // in unnecessary cases (normal actions). + const SwContentFrame *pNxt = pCnt->GetNextContentFrame(); + while ( pNxt && + (pNxt->IsInTab() || pNxt->IsInDocBody() != pCnt->IsInDocBody()) ) + pNxt = pNxt->GetNextContentFrame(); + if ( pNxt ) + pNxt->InvalidatePage(); + } + return false; + } + + if ( pPage->IsInvalidLayout() || (pPage->GetSortedObjs() && pPage->IsInvalidFly()) ) + return false; + } + if ( !pPage ) + pPage = pCnt->FindPageFrame(); + + // format floating screen objects at content frame. + if ( pCnt->IsTextFrame() && + !SwObjectFormatter::FormatObjsAtFrame( *const_cast<SwContentFrame*>(pCnt), + *pPage, this ) ) + { + return false; + } + + if ( pPage->IsInvalidContent() ) + return false; + return true; +} + +bool SwLayAction::TurboAction() +{ + bool bRet = true; + + if ( m_pRoot->GetTurbo() ) + { + if ( !TurboAction_( m_pRoot->GetTurbo() ) ) + { + CheckIdleEnd(); + bRet = false; + } + m_pRoot->ResetTurbo(); + } + else + bRet = false; + return bRet; +} + +static bool lcl_IsInvaLay( const SwFrame *pFrame, tools::Long nBottom ) +{ + return !pFrame->isFrameAreaDefinitionValid() || + (pFrame->IsCompletePaint() && ( pFrame->getFrameArea().Top() < nBottom ) ); +} + +static const SwFrame *lcl_FindFirstInvaLay( const SwFrame *pFrame, tools::Long nBottom ) +{ + OSL_ENSURE( pFrame->IsLayoutFrame(), "FindFirstInvaLay, no LayFrame" ); + + if (lcl_IsInvaLay(pFrame, nBottom)) + return pFrame; + pFrame = static_cast<const SwLayoutFrame*>(pFrame)->Lower(); + while ( pFrame ) + { + if ( pFrame->IsLayoutFrame() ) + { + if (lcl_IsInvaLay(pFrame, nBottom)) + return pFrame; + const SwFrame *pTmp = lcl_FindFirstInvaLay( pFrame, nBottom ); + if ( nullptr != pTmp ) + return pTmp; + } + pFrame = pFrame->GetNext(); + } + return nullptr; +} + +static const SwFrame *lcl_FindFirstInvaContent( const SwLayoutFrame *pLay, tools::Long nBottom, + const SwContentFrame *pFirst ) +{ + const SwContentFrame *pCnt = pFirst ? pFirst->GetNextContentFrame() : + pLay->ContainsContent(); + while ( pCnt ) + { + if ( !pCnt->isFrameAreaDefinitionValid() || pCnt->IsCompletePaint() ) + { + if ( pCnt->getFrameArea().Top() <= nBottom ) + return pCnt; + } + + if ( pCnt->GetDrawObjs() ) + { + const SwSortedObjs &rObjs = *pCnt->GetDrawObjs(); + for (SwAnchoredObject* pObj : rObjs) + { + if ( auto pFly = pObj->DynCastFlyFrame() ) + { + if ( pFly->IsFlyInContentFrame() ) + { + if ( static_cast<const SwFlyInContentFrame*>(pFly)->IsInvalid() || + pFly->IsCompletePaint() ) + { + if ( pFly->getFrameArea().Top() <= nBottom ) + return pFly; + } + const SwFrame *pFrame = lcl_FindFirstInvaContent( pFly, nBottom, nullptr ); + if ( pFrame && pFrame->getFrameArea().Bottom() <= nBottom ) + return pFrame; + } + } + } + } + if ( pCnt->getFrameArea().Top() > nBottom && !pCnt->IsInTab() ) + return nullptr; + pCnt = pCnt->GetNextContentFrame(); + if ( !pLay->IsAnLower( pCnt ) ) + break; + } + return nullptr; +} + +// consider drawing objects +static const SwAnchoredObject* lcl_FindFirstInvaObj( const SwPageFrame* _pPage, + tools::Long _nBottom ) +{ + OSL_ENSURE( _pPage->GetSortedObjs(), "FindFirstInvaObj, no Objs" ); + + for (SwAnchoredObject* pObj : *_pPage->GetSortedObjs()) + { + if ( auto pFly = pObj->DynCastFlyFrame() ) + { + if ( pFly->getFrameArea().Top() <= _nBottom ) + { + if ( pFly->IsInvalid() || pFly->IsCompletePaint() ) + return pFly; + + const SwFrame* pTmp; + if ( nullptr != (pTmp = lcl_FindFirstInvaContent( pFly, _nBottom, nullptr )) && + pTmp->getFrameArea().Top() <= _nBottom ) + return pFly; + } + } + else if ( auto pDrawObject = dynamic_cast< const SwAnchoredDrawObject *>( pObj ) ) + { + if ( !pDrawObject->IsValidPos() ) + { + return pObj; + } + } + } + return nullptr; +} + +/* Returns True if the page lies directly below or right of the visible area. + * + * It's possible for things to change in such a way that the processing + * (of the caller!) has to continue with the predecessor of the passed page. + * The parameter might therefore get modified! + * For BrowseMode, you may even activate the ShortCut if the invalid content + * of the page lies below the visible area. + */ +bool SwLayAction::IsShortCut( SwPageFrame *&prPage ) +{ + vcl::RenderContext* pRenderContext = m_pImp->GetShell()->GetOut(); + bool bRet = false; + const SwViewShell *pSh = m_pRoot->GetCurrShell(); + const bool bBrowse = pSh && pSh->GetViewOptions()->getBrowseMode(); + + // If the page is not valid, we quickly format it, otherwise + // there's gonna be no end of trouble + if ( !prPage->isFrameAreaDefinitionValid() ) + { + if ( bBrowse ) + { + // format complete page + // Thus, loop on all lowers of the page <prPage>, instead of only + // format its first lower. + // NOTE: In online layout (bBrowse == true) a page can contain + // a header frame and/or a footer frame beside the body frame. + prPage->Calc(pRenderContext); + SwFrame* pPageLowerFrame = prPage->Lower(); + while ( pPageLowerFrame ) + { + pPageLowerFrame->Calc(pRenderContext); + pPageLowerFrame = pPageLowerFrame->GetNext(); + } + } + else + FormatLayout( pSh ? pSh->GetOut() : nullptr, prPage ); + if ( IsAgain() ) + return false; + } + + const SwRect &rVis = m_pImp->GetShell()->VisArea(); + if ( (prPage->getFrameArea().Top() >= rVis.Bottom()) || + (prPage->getFrameArea().Left()>= rVis.Right()) ) + { + bRet = true; + + // This is going to be a bit nasty: The first ContentFrame of this + // page in the Body text needs formatting; if it changes the page during + // that process, I need to start over a page further back, because we + // have been processing a PageBreak. + // Even more uncomfortable: The next ContentFrame must be formatted, + // because it's possible for empty pages to exist temporarily (for example + // a paragraph across multiple pages gets deleted or reduced in size). + + // This is irrelevant for the browser, if the last Cnt above it + // isn't visible anymore. + + const SwPageFrame *p2ndPage = prPage; + const SwContentFrame *pContent; + const SwLayoutFrame* pBody = p2ndPage->FindBodyCont(); + if( p2ndPage->IsFootnotePage() && pBody ) + pBody = static_cast<const SwLayoutFrame*>(pBody->GetNext()); + pContent = pBody ? pBody->ContainsContent() : nullptr; + while ( p2ndPage && !pContent ) + { + p2ndPage = static_cast<const SwPageFrame*>(p2ndPage->GetNext()); + if( p2ndPage ) + { + pBody = p2ndPage->FindBodyCont(); + if( p2ndPage->IsFootnotePage() && pBody ) + pBody = static_cast<const SwLayoutFrame*>(pBody->GetNext()); + pContent = pBody ? pBody->ContainsContent() : nullptr; + } + } + if ( pContent ) + { + bool bTstCnt = true; + if ( bBrowse ) + { + // Is the Cnt before already invisible? + const SwFrame *pLst = pContent; + if ( pLst->IsInTab() ) + pLst = pContent->FindTabFrame(); + if ( pLst->IsInSct() ) + pLst = pContent->FindSctFrame(); + pLst = pLst->FindPrev(); + if ( pLst && + (pLst->getFrameArea().Top() >= rVis.Bottom() || + pLst->getFrameArea().Left()>= rVis.Right()) ) + { + bTstCnt = false; + } + } + + if ( bTstCnt ) + { + // check after each frame calculation, + // if the content frame has changed the page. If yes, no other + // frame calculation is performed + bool bPageChg = false; + + if ( pContent->IsInSct() ) + { + const SwSectionFrame *pSct = const_cast<SwFrame*>(static_cast<SwFrame const *>(pContent))->ImplFindSctFrame(); + if ( !pSct->isFrameAreaDefinitionValid() ) + { + pSct->Calc(pRenderContext); + pSct->SetCompletePaint(); + if ( IsAgain() ) + return false; + + bPageChg = pContent->FindPageFrame() != p2ndPage && + prPage->GetPrev(); + } + } + + if ( !bPageChg && !pContent->isFrameAreaDefinitionValid() ) + { + pContent->Calc(pRenderContext); + pContent->SetCompletePaint(); + if ( IsAgain() ) + return false; + + bPageChg = pContent->FindPageFrame() != p2ndPage && + prPage->GetPrev(); + } + + if ( !bPageChg && pContent->IsInTab() ) + { + const SwTabFrame *pTab = const_cast<SwFrame*>(static_cast<SwFrame const *>(pContent))->ImplFindTabFrame(); + if ( !pTab->isFrameAreaDefinitionValid() ) + { + pTab->Calc(pRenderContext); + pTab->SetCompletePaint(); + if ( IsAgain() ) + return false; + + bPageChg = pContent->FindPageFrame() != p2ndPage && + prPage->GetPrev(); + } + } + + if ( !bPageChg && pContent->IsInSct() ) + { + const SwSectionFrame *pSct = const_cast<SwFrame*>(static_cast<SwFrame const *>(pContent))->ImplFindSctFrame(); + if ( !pSct->isFrameAreaDefinitionValid() ) + { + pSct->Calc(pRenderContext); + pSct->SetCompletePaint(); + if ( IsAgain() ) + return false; + + bPageChg = pContent->FindPageFrame() != p2ndPage && + prPage->GetPrev(); + } + } + + if ( bPageChg ) + { + bRet = false; + const SwPageFrame* pTmp = pContent->FindPageFrame(); + if ( pTmp->GetPhyPageNum() < prPage->GetPhyPageNum() && + pTmp->IsInvalid() ) + { + prPage = const_cast<SwPageFrame*>(pTmp); + } + else + { + prPage = static_cast<SwPageFrame*>(prPage->GetPrev()); + } + } + // no shortcut, if at previous page + // an anchored object is registered, whose anchor is <pContent>. + else if ( prPage->GetPrev() ) + { + SwSortedObjs* pObjs = + static_cast<SwPageFrame*>(prPage->GetPrev())->GetSortedObjs(); + if ( pObjs ) + { + for (SwAnchoredObject* pObj : *pObjs) + { + if ( pObj->GetAnchorFrameContainingAnchPos() == pContent ) + { + bRet = false; + break; + } + } + } + } + } + } + } + + if ( !bRet && bBrowse ) + { + const tools::Long nBottom = rVis.Bottom(); + const SwAnchoredObject* pObj( nullptr ); + if ( prPage->GetSortedObjs() && + (prPage->IsInvalidFlyLayout() || prPage->IsInvalidFlyContent()) && + nullptr != (pObj = lcl_FindFirstInvaObj( prPage, nBottom )) && + pObj->GetObjRect().Top() <= nBottom ) + { + return false; + } + const SwFrame* pFrame( nullptr ); + if ( prPage->IsInvalidLayout() && + nullptr != (pFrame = lcl_FindFirstInvaLay( prPage, nBottom )) && + pFrame->getFrameArea().Top() <= nBottom ) + { + return false; + } + if ( (prPage->IsInvalidContent() || prPage->IsInvalidFlyInCnt()) && + nullptr != (pFrame = lcl_FindFirstInvaContent( prPage, nBottom, nullptr )) && + pFrame->getFrameArea().Top() <= nBottom ) + { + return false; + } + bRet = true; + } + return bRet; +} + +// introduce support for vertical layout +bool SwLayAction::FormatLayout( OutputDevice *pRenderContext, SwLayoutFrame *pLay, bool bAddRect ) +{ + OSL_ENSURE( !IsAgain(), "Attention to the invalid page." ); + if ( IsAgain() ) + return false; + + bool bChanged = false; + bool bAlreadyPainted = false; + // remember frame at complete paint + SwRect aFrameAtCompletePaint; + + if ( !pLay->isFrameAreaDefinitionValid() || pLay->IsCompletePaint() ) + { + if ( pLay->GetPrev() && !pLay->GetPrev()->isFrameAreaDefinitionValid() ) + pLay->GetPrev()->SetCompletePaint(); + + SwRect aOldFrame( pLay->getFrameArea() ); + SwRect aOldRect( aOldFrame ); + if( pLay->IsPageFrame() ) + { + aOldRect = static_cast<SwPageFrame*>(pLay)->GetBoundRect(pRenderContext); + } + + { + SwFrameDeleteGuard aDeleteGuard(pLay); + pLay->Calc(pRenderContext); + } + + if ( aOldFrame != pLay->getFrameArea() ) + bChanged = true; + + bool bNoPaint = false; + if ( pLay->IsPageBodyFrame() && + pLay->getFrameArea().Pos() == aOldRect.Pos() && + pLay->Lower() ) + { + const SwViewShell *pSh = pLay->getRootFrame()->GetCurrShell(); + // Limitations because of headers / footers + if( pSh && pSh->GetViewOptions()->getBrowseMode() && + !( pLay->IsCompletePaint() && pLay->FindPageFrame()->FindFootnoteCont() ) ) + bNoPaint = true; + } + + if ( !bNoPaint && IsPaint() && bAddRect && (pLay->IsCompletePaint() || bChanged) ) + { + SwRect aPaint( pLay->getFrameArea() ); + // consider border and shadow for + // page frames -> enlarge paint rectangle correspondingly. + if ( pLay->IsPageFrame() ) + { + SwPageFrame* pPageFrame = static_cast<SwPageFrame*>(pLay); + aPaint = pPageFrame->GetBoundRect(pRenderContext); + } + + bool bPageInBrowseMode = pLay->IsPageFrame(); + if( bPageInBrowseMode ) + { + const SwViewShell *pSh = pLay->getRootFrame()->GetCurrShell(); + if( !pSh || !pSh->GetViewOptions()->getBrowseMode() ) + bPageInBrowseMode = false; + } + if( bPageInBrowseMode ) + { + // NOTE: no vertical layout in online layout + // Is the change even visible? + if ( pLay->IsCompletePaint() ) + { + m_pImp->GetShell()->AddPaintRect( aPaint ); + bAddRect = false; + } + else + { + SwRegionRects aRegion( aOldRect ); + aRegion -= aPaint; + for ( size_t i = 0; i < aRegion.size(); ++i ) + m_pImp->GetShell()->AddPaintRect( aRegion[i] ); + aRegion.ChangeOrigin( aPaint ); + aRegion.clear(); + aRegion.push_back( aPaint ); + aRegion -= aOldRect; + for ( size_t i = 0; i < aRegion.size(); ++i ) + m_pImp->GetShell()->AddPaintRect( aRegion[i] ); + } + } + else + { + m_pImp->GetShell()->AddPaintRect( aPaint ); + bAlreadyPainted = true; + // remember frame at complete paint + aFrameAtCompletePaint = pLay->getFrameArea(); + } + + // provide paint of spacing + // between pages (not only for in online mode). + if ( pLay->IsPageFrame() ) + { + const SwViewShell *pSh = pLay->getRootFrame()->GetCurrShell(); + const SwTwips nHalfDocBorder = pSh ? pSh->GetViewOptions()->GetGapBetweenPages() + : SwViewOption::defGapBetweenPages; + const bool bLeftToRightViewLayout = m_pRoot->IsLeftToRightViewLayout(); + const bool bPrev = bLeftToRightViewLayout ? pLay->GetPrev() : pLay->GetNext(); + const bool bNext = bLeftToRightViewLayout ? pLay->GetNext() : pLay->GetPrev(); + SwPageFrame* pPageFrame = static_cast<SwPageFrame*>(pLay); + SwRect aPageRect( pLay->getFrameArea() ); + + if(pSh) + { + SwPageFrame::GetBorderAndShadowBoundRect(aPageRect, pSh, + pRenderContext, + aPageRect, pPageFrame->IsLeftShadowNeeded(), pPageFrame->IsRightShadowNeeded(), + pPageFrame->SidebarPosition() == sw::sidebarwindows::SidebarPosition::RIGHT); + } + + if ( bPrev ) + { + // top + SwRect aSpaceToPrevPage( aPageRect ); + aSpaceToPrevPage.Top( aSpaceToPrevPage.Top() - nHalfDocBorder ); + aSpaceToPrevPage.Bottom( pLay->getFrameArea().Top() ); + if(!aSpaceToPrevPage.IsEmpty()) + m_pImp->GetShell()->AddPaintRect( aSpaceToPrevPage ); + + // left + aSpaceToPrevPage = aPageRect; + aSpaceToPrevPage.Left( aSpaceToPrevPage.Left() - nHalfDocBorder ); + aSpaceToPrevPage.Right( pLay->getFrameArea().Left() ); + if(!aSpaceToPrevPage.IsEmpty()) + m_pImp->GetShell()->AddPaintRect( aSpaceToPrevPage ); + } + if ( bNext ) + { + // bottom + SwRect aSpaceToNextPage( aPageRect ); + aSpaceToNextPage.Bottom( aSpaceToNextPage.Bottom() + nHalfDocBorder ); + aSpaceToNextPage.Top( pLay->getFrameArea().Bottom() ); + if(!aSpaceToNextPage.IsEmpty()) + m_pImp->GetShell()->AddPaintRect( aSpaceToNextPage ); + + // right + aSpaceToNextPage = aPageRect; + aSpaceToNextPage.Right( aSpaceToNextPage.Right() + nHalfDocBorder ); + aSpaceToNextPage.Left( pLay->getFrameArea().Right() ); + if(!aSpaceToNextPage.IsEmpty()) + m_pImp->GetShell()->AddPaintRect( aSpaceToNextPage ); + } + } + } + pLay->ResetCompletePaint(); + } + + if ( IsPaint() && bAddRect && + !pLay->GetNext() && pLay->IsRetoucheFrame() && pLay->IsRetouche() ) + { + // vertical layout support + SwRectFnSet aRectFnSet(pLay); + SwRect aRect( pLay->GetUpper()->GetPaintArea() ); + aRectFnSet.SetTop( aRect, aRectFnSet.GetPrtBottom(*pLay) ); + if ( !m_pImp->GetShell()->AddPaintRect( aRect ) ) + pLay->ResetRetouche(); + } + + if( bAlreadyPainted ) + bAddRect = false; + + CheckWaitCursor(); + + if ( IsAgain() ) + return false; + + // Now, deal with the lowers that are LayoutFrames + + if ( pLay->IsFootnoteFrame() ) // no LayFrames as Lower + return bChanged; + + SwFrame *pLow = pLay->Lower(); + bool bTabChanged = false; + while ( pLow && pLow->GetUpper() == pLay ) + { + SwFrame* pNext = nullptr; + if ( pLow->IsLayoutFrame() ) + { + if ( pLow->IsTabFrame() ) + { + // Remember what was the next of the lower. Formatting may move it to the previous + // page, in which case it looses its next. + pNext = pLow->GetNext(); + + if (pNext && pNext->IsTabFrame()) + { + auto pTab = static_cast<SwTabFrame*>(pNext); + if (pTab->IsFollow()) + { + // The next frame is a follow of the previous frame, SwTabFrame::Join() will + // delete this one as part of formatting, so forget about it. + pNext = nullptr; + } + } + + bTabChanged |= FormatLayoutTab( static_cast<SwTabFrame*>(pLow), bAddRect ); + } + // Skip the ones already registered for deletion + else if( !pLow->IsSctFrame() || static_cast<SwSectionFrame*>(pLow)->GetSection() ) + { + PushFormatLayout(pLow); + bChanged |= FormatLayout( pRenderContext, static_cast<SwLayoutFrame*>(pLow), bAddRect ); + PopFormatLayout(); + } + } + else if ( m_pImp->GetShell()->IsPaintLocked() ) + // Shortcut to minimize the cycles. With Lock, the + // paint is coming either way (primarily for browse) + pLow->OptCalc(); + + if ( IsAgain() ) + return false; + if (!pNext) + { + pNext = pLow->GetNext(); + } + pLow = pNext; + } + // add complete frame area as paint area, if frame + // area has been already added and after formatting its lowers the frame area + // is enlarged. + SwRect aBoundRect(pLay->IsPageFrame() ? static_cast<SwPageFrame*>(pLay)->GetBoundRect(pRenderContext) : pLay->getFrameArea() ); + + if ( bAlreadyPainted && + ( aBoundRect.Width() > aFrameAtCompletePaint.Width() || + aBoundRect.Height() > aFrameAtCompletePaint.Height() ) + ) + { + m_pImp->GetShell()->AddPaintRect( aBoundRect ); + } + return bChanged || bTabChanged; +} + +void SwLayAction::FormatLayoutFly( SwFlyFrame* pFly ) +{ + vcl::RenderContext* pRenderContext = m_pImp->GetShell()->GetOut(); + OSL_ENSURE( !IsAgain(), "Attention to the invalid page." ); + if ( IsAgain() ) + return; + + bool bChanged = false; + bool bAddRect = true; + + if ( !pFly->isFrameAreaDefinitionValid() || pFly->IsCompletePaint() || pFly->IsInvalid() ) + { + // The Frame has changed, now it's getting formatted. + const SwRect aOldRect( pFly->getFrameArea() ); + pFly->Calc(pRenderContext); + bChanged = aOldRect != pFly->getFrameArea(); + + if ( IsPaint() && (pFly->IsCompletePaint() || bChanged) && + pFly->getFrameArea().Top() > 0 && pFly->getFrameArea().Left() > 0 ) + m_pImp->GetShell()->AddPaintRect( pFly->getFrameArea() ); + + if ( bChanged ) + pFly->Invalidate(); + else + pFly->Validate(); + + bAddRect = false; + pFly->ResetCompletePaint(); + } + + if ( IsAgain() ) + return; + + // Now, deal with the lowers that are LayoutFrames + SwFrame *pLow = pFly->Lower(); + while ( pLow ) + { + if ( pLow->IsLayoutFrame() ) + { + if ( pLow->IsTabFrame() ) + FormatLayoutTab( static_cast<SwTabFrame*>(pLow), bAddRect ); + else + FormatLayout( m_pImp->GetShell()->GetOut(), static_cast<SwLayoutFrame*>(pLow), bAddRect ); + } + pLow = pLow->GetNext(); + } +} + +// Implement vertical layout support +bool SwLayAction::FormatLayoutTab( SwTabFrame *pTab, bool bAddRect ) +{ + OSL_ENSURE( !IsAgain(), "8-) Attention to the invalid page." ); + if ( IsAgain() || !pTab->Lower() ) + return false; + + vcl::RenderContext* pRenderContext = m_pImp->GetShell()->GetOut(); + IDocumentTimerAccess& rTimerAccess = m_pRoot->GetFormat()->getIDocumentTimerAccess(); + rTimerAccess.BlockIdling(); + + bool bChanged = false; + bool bPainted = false; + + const SwPageFrame *pOldPage = pTab->FindPageFrame(); + + // vertical layout support + SwRectFnSet aRectFnSet(pTab); + + if ( !pTab->isFrameAreaDefinitionValid() || pTab->IsCompletePaint() || pTab->IsComplete() ) + { + if ( pTab->GetPrev() && !pTab->GetPrev()->isFrameAreaDefinitionValid() ) + { + pTab->GetPrev()->SetCompletePaint(); + } + + const SwRect aOldRect( pTab->getFrameArea() ); + pTab->SetLowersFormatted( false ); + pTab->Calc(pRenderContext); + if ( aOldRect != pTab->getFrameArea() ) + { + bChanged = true; + } + const SwRect aPaintFrame = pTab->GetPaintArea(); + + if ( IsPaint() && bAddRect ) + { + // add condition <pTab->getFrameArea().HasArea()> + if ( !pTab->IsCompletePaint() && + pTab->IsComplete() && + ( pTab->getFrameArea().SSize() != pTab->getFramePrintArea().SSize() || + // vertical layout support + aRectFnSet.GetLeftMargin(*pTab) ) && + pTab->getFrameArea().HasArea() + ) + { + // re-implement calculation of margin rectangles. + SwRect aMarginRect; + + SwTwips nLeftMargin = aRectFnSet.GetLeftMargin(*pTab); + if ( nLeftMargin > 0) + { + aMarginRect = pTab->getFrameArea(); + aRectFnSet.SetWidth( aMarginRect, nLeftMargin ); + m_pImp->GetShell()->AddPaintRect( aMarginRect ); + } + + if ( aRectFnSet.GetRightMargin(*pTab) > 0) + { + aMarginRect = pTab->getFrameArea(); + aRectFnSet.SetLeft( aMarginRect, aRectFnSet.GetPrtRight(*pTab) ); + m_pImp->GetShell()->AddPaintRect( aMarginRect ); + } + + SwTwips nTopMargin = aRectFnSet.GetTopMargin(*pTab); + if ( nTopMargin > 0) + { + aMarginRect = pTab->getFrameArea(); + aRectFnSet.SetHeight( aMarginRect, nTopMargin ); + m_pImp->GetShell()->AddPaintRect( aMarginRect ); + } + + if ( aRectFnSet.GetBottomMargin(*pTab) > 0) + { + aMarginRect = pTab->getFrameArea(); + aRectFnSet.SetTop( aMarginRect, aRectFnSet.GetPrtBottom(*pTab) ); + m_pImp->GetShell()->AddPaintRect( aMarginRect ); + } + } + else if ( pTab->IsCompletePaint() ) + { + m_pImp->GetShell()->AddPaintRect( aPaintFrame ); + bAddRect = false; + bPainted = true; + } + + if ( pTab->IsRetouche() && !pTab->GetNext() ) + { + SwRect aRect( pTab->GetUpper()->GetPaintArea() ); + // vertical layout support + aRectFnSet.SetTop( aRect, aRectFnSet.GetPrtBottom(*pTab) ); + if ( !m_pImp->GetShell()->AddPaintRect( aRect ) ) + pTab->ResetRetouche(); + } + } + else + bAddRect = false; + + if ( pTab->IsCompletePaint() && !m_pOptTab ) + m_pOptTab = pTab; + pTab->ResetCompletePaint(); + } + if ( IsPaint() && bAddRect && pTab->IsRetouche() && !pTab->GetNext() ) + { + // set correct rectangle for retouche: area between bottom of table frame + // and bottom of paint area of the upper frame. + SwRect aRect( pTab->GetUpper()->GetPaintArea() ); + // vertical layout support + aRectFnSet.SetTop( aRect, aRectFnSet.GetPrtBottom(*pTab) ); + if ( !m_pImp->GetShell()->AddPaintRect( aRect ) ) + pTab->ResetRetouche(); + } + + CheckWaitCursor(); + + rTimerAccess.UnblockIdling(); + + // Ugly shortcut! + if ( pTab->IsLowersFormatted() && + (bPainted || !m_pImp->GetShell()->VisArea().Overlaps( pTab->getFrameArea())) ) + return false; + + // Now, deal with the lowers + if ( IsAgain() ) + return false; + + // for safety reasons: + // check page number before formatting lowers. + if ( pOldPage->GetPhyPageNum() > (pTab->FindPageFrame()->GetPhyPageNum() + 1) ) + SetNextCycle( true ); + + // format lowers, only if table frame is valid + if ( pTab->isFrameAreaDefinitionValid() ) + { + FlowFrameJoinLockGuard tabG(pTab); // tdf#124675 prevent Join() if pTab becomes empty + SwLayoutFrame *pLow = static_cast<SwLayoutFrame*>(pTab->Lower()); + while ( pLow ) + { + SwFrameDeleteGuard rowG(pLow); // tdf#124675 prevent RemoveFollowFlowLine() + bChanged |= FormatLayout( m_pImp->GetShell()->GetOut(), pLow, bAddRect ); + if ( IsAgain() ) + return false; + pLow = static_cast<SwLayoutFrame*>(pLow->GetNext()); + } + } + + return bChanged; +} + +bool SwLayAction::FormatContent(SwPageFrame *const pPage) +{ + ::comphelper::ScopeGuard g([this, pPage]() { + if (IsAgain()) + { + return; // pPage probably deleted + } + if (auto const* pObjs = pPage->GetSortedObjs()) + { + std::vector<std::pair<SwAnchoredObject*, SwPageFrame*>> moved; + for (auto const pObj : *pObjs) + { + assert(!pObj->AnchorFrame()->IsTextFrame() + || !static_cast<SwTextFrame const*>(pObj->AnchorFrame())->IsFollow()); + SwPageFrame *const pAnchorPage(pObj->AnchorFrame()->FindPageFrame()); + assert(pAnchorPage); + if (pAnchorPage != pPage + && pPage->GetPhyPageNum() < pAnchorPage->GetPhyPageNum() + && pObj->GetFrameFormat().GetAnchor().GetAnchorId() + != RndStdIds::FLY_AS_CHAR) + { + moved.emplace_back(pObj, pAnchorPage); + } + } + for (auto const& [pObj, pAnchorPage] : moved) + { + SAL_INFO("sw.layout", "SwLayAction::FormatContent: move anchored " << pObj << " from " << pPage->GetPhyPageNum() << " to " << pAnchorPage->GetPhyPageNum()); + pObj->RegisterAtPage(*pAnchorPage); + // tdf#143239 if the position remains valid, it may not be + // positioned again so would remain on the wrong page! + pObj->InvalidateObjPos(); + ::Notify_Background(pObj->GetDrawObj(), pPage, + pObj->GetObjRect(), PrepareHint::FlyFrameLeave, false); + } + if (!moved.empty()) + { + pPage->InvalidateFlyLayout(); + if (auto *const pContent = pPage->FindLastBodyContent()) + { + pContent->InvalidateSize(); + } + } + } + }); + + const SwContentFrame *pContent = pPage->ContainsContent(); + const SwViewShell *pSh = m_pRoot->GetCurrShell(); + const bool bBrowse = pSh && pSh->GetViewOptions()->getBrowseMode(); + + while ( pContent && pPage->IsAnLower( pContent ) ) + { + // If the content didn't change, we can use a few shortcuts. + const bool bFull = !pContent->isFrameAreaDefinitionValid() || pContent->IsCompletePaint() || + pContent->IsRetouche() || pContent->GetDrawObjs(); + if ( bFull ) + { + // We do this so we don't have to search later on. + const bool bNxtCnt = IsCalcLayout() && !pContent->GetFollow(); + const SwContentFrame *pContentNext = bNxtCnt ? pContent->GetNextContentFrame() : nullptr; + SwContentFrame* const pContentPrev = pContent->GetPrev() ? pContent->GetPrevContentFrame() : nullptr; + std::optional<SfxDeleteListener> oPrevDeleteListener; + if (pContentPrev) + oPrevDeleteListener.emplace(*pContentPrev); + + const SwLayoutFrame*pOldUpper = pContent->GetUpper(); + const SwTabFrame *pTab = pContent->FindTabFrame(); + const bool bInValid = !pContent->isFrameAreaDefinitionValid() || pContent->IsCompletePaint(); + const bool bOldPaint = IsPaint(); + m_bPaint = bOldPaint && !(pTab && pTab == m_pOptTab); + FormatContent_( pContent, pPage ); + // reset <bPaint> before format objects + m_bPaint = bOldPaint; + + // format floating screen object at content frame. + // No format, if action flag <bAgain> is set or action is interrupted. + // allow format on interruption of action, if + // it's the format for this interrupt + // pass correct page frame + // to the object formatter. + if ( !IsAgain() && + ( !IsInterrupt() || mbFormatContentOnInterrupt ) && + pContent->IsTextFrame() && + !SwObjectFormatter::FormatObjsAtFrame( *const_cast<SwContentFrame*>(pContent), + *(pContent->FindPageFrame()), this ) ) + { + return false; + } + + if ( !pContent->GetValidLineNumFlag() && pContent->IsTextFrame() ) + { + const sal_uLong nAllLines = static_cast<const SwTextFrame*>(pContent)->GetAllLines(); + const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pContent))->RecalcAllLines(); + if ( IsPaintExtraData() && IsPaint() && + nAllLines != static_cast<const SwTextFrame*>(pContent)->GetAllLines() ) + m_pImp->GetShell()->AddPaintRect( pContent->getFrameArea() ); + } + + if ( IsAgain() ) + return false; + + // Temporarily interrupt processing if layout or Flys become invalid again. + // However not for the BrowseView: The layout is getting invalid + // all the time because the page height gets adjusted. + // The same applies if the user wants to continue working and at least one + // paragraph has been processed. + if (!pTab || !bInValid) + { + CheckIdleEnd(); + // consider interrupt formatting. + if ( ( IsInterrupt() && !mbFormatContentOnInterrupt ) || + ( !bBrowse && pPage->IsInvalidLayout() ) || + // consider interrupt formatting + ( pPage->GetSortedObjs() && pPage->IsInvalidFly() && !mbFormatContentOnInterrupt ) + ) + return false; + } + if ( pOldUpper != pContent->GetUpper() ) + { + const sal_uInt16 nCurNum = pContent->FindPageFrame()->GetPhyPageNum(); + if ( nCurNum < pPage->GetPhyPageNum() ) + m_nPreInvaPage = nCurNum; + + // If the frame flowed backwards more than one page, we need to + // start over again from the beginning, so nothing gets left out. + if ( !IsCalcLayout() && pPage->GetPhyPageNum() > nCurNum+1 ) + { + SetNextCycle( true ); + // consider interrupt formatting + if ( !mbFormatContentOnInterrupt ) + { + return false; + } + } + } + // If the frame moved forwards to the next page, we re-run through + // the predecessor. + // This way, we catch predecessors which are now responsible for + // retouching, but the footers will be touched also. + bool bSetContent = true; + if ( pContentPrev ) + { + if (oPrevDeleteListener->WasDeleted()) + { + SAL_WARN("sw", "ContentPrev was deleted"); + return false; + } + + if ( !pContentPrev->isFrameAreaDefinitionValid() && pPage->IsAnLower( pContentPrev ) ) + { + pPage->InvalidateContent(); + } + + if ( pOldUpper != pContent->GetUpper() && + pPage->GetPhyPageNum() < pContent->FindPageFrame()->GetPhyPageNum() ) + { + pContent = pContentPrev; + bSetContent = false; + } + } + if ( bSetContent ) + { + if ( bBrowse && !IsIdle() && !IsCalcLayout() && !IsComplete() && + pContent->getFrameArea().Top() > m_pImp->GetShell()->VisArea().Bottom()) + { + const tools::Long nBottom = m_pImp->GetShell()->VisArea().Bottom(); + const SwFrame *pTmp = lcl_FindFirstInvaContent( pPage, + nBottom, pContent ); + if ( !pTmp ) + { + if ( (!(pPage->GetSortedObjs() && pPage->IsInvalidFly()) || + !lcl_FindFirstInvaObj( pPage, nBottom )) && + (!pPage->IsInvalidLayout() || + !lcl_FindFirstInvaLay( pPage, nBottom ))) + SetBrowseActionStop( true ); + // consider interrupt formatting. + if ( !mbFormatContentOnInterrupt ) + { + return false; + } + } + } + pContent = bNxtCnt ? pContentNext : pContent->GetNextContentFrame(); + } + + if (IsReschedule()) + { + ::RescheduleProgress(m_pImp->GetShell()->GetDoc()->GetDocShell()); + } + } + else + { + if ( !pContent->GetValidLineNumFlag() && pContent->IsTextFrame() ) + { + const sal_uLong nAllLines = static_cast<const SwTextFrame*>(pContent)->GetAllLines(); + const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pContent))->RecalcAllLines(); + if ( IsPaintExtraData() && IsPaint() && + nAllLines != static_cast<const SwTextFrame*>(pContent)->GetAllLines() ) + m_pImp->GetShell()->AddPaintRect( pContent->getFrameArea() ); + } + + // Do this if the frame has been formatted before. + if ( pContent->IsTextFrame() && static_cast<const SwTextFrame*>(pContent)->HasRepaint() && + IsPaint() ) + PaintContent( pContent, pPage, pContent->getFrameArea(), pContent->getFrameArea().Bottom()); + if ( IsIdle() ) + { + CheckIdleEnd(); + // consider interrupt formatting. + if ( IsInterrupt() && !mbFormatContentOnInterrupt ) + return false; + } + if ( bBrowse && !IsIdle() && !IsCalcLayout() && !IsComplete() && + pContent->getFrameArea().Top() > m_pImp->GetShell()->VisArea().Bottom()) + { + const tools::Long nBottom = m_pImp->GetShell()->VisArea().Bottom(); + const SwFrame *pTmp = lcl_FindFirstInvaContent( pPage, + nBottom, pContent ); + if ( !pTmp ) + { + if ( (!(pPage->GetSortedObjs() && pPage->IsInvalidFly()) || + !lcl_FindFirstInvaObj( pPage, nBottom )) && + (!pPage->IsInvalidLayout() || + !lcl_FindFirstInvaLay( pPage, nBottom ))) + SetBrowseActionStop( true ); + // consider interrupt formatting. + if ( !mbFormatContentOnInterrupt ) + { + return false; + } + } + } + pContent = pContent->GetNextContentFrame(); + } + } + CheckWaitCursor(); + // consider interrupt formatting. + return !IsInterrupt() || mbFormatContentOnInterrupt; +} + +void SwLayAction::FormatContent_( const SwContentFrame *pContent, const SwPageFrame *pPage ) +{ + // We probably only ended up here because the Content holds DrawObjects. + const bool bDrawObjsOnly = pContent->isFrameAreaDefinitionValid() && !pContent->IsCompletePaint() && !pContent->IsRetouche(); + SwRectFnSet aRectFnSet(pContent); + if ( !bDrawObjsOnly && IsPaint() ) + { + const SwRect aOldRect( pContent->UnionFrame() ); + const tools::Long nOldBottom = aRectFnSet.GetPrtBottom(*pContent); + pContent->OptCalc(); + if( IsAgain() ) + return; + if( aRectFnSet.YDiff( aRectFnSet.GetBottom(pContent->getFrameArea()), + aRectFnSet.GetBottom(aOldRect) ) < 0 ) + { + pContent->SetRetouche(); + } + PaintContent( pContent, pContent->FindPageFrame(), aOldRect, nOldBottom); + } + else + { + if ( IsPaint() && pContent->IsTextFrame() && static_cast<const SwTextFrame*>(pContent)->HasRepaint() ) + PaintContent( pContent, pPage, pContent->getFrameArea(), + aRectFnSet.GetBottom(pContent->getFrameArea()) ); + pContent->OptCalc(); + } +} + +void SwLayAction::FormatFlyContent( const SwFlyFrame *pFly ) +{ + const SwContentFrame *pContent = pFly->ContainsContent(); + + while ( pContent ) + { + FormatContent_( pContent, pContent->FindPageFrame() ); + + // format floating screen objects at content text frame + // pass correct page frame to the object formatter. + if ( pContent->IsTextFrame() && + !SwObjectFormatter::FormatObjsAtFrame( + *const_cast<SwContentFrame*>(pContent), + *(pContent->FindPageFrame()), this ) ) + { + // restart format with first content + pContent = pFly->ContainsContent(); + continue; + } + + if ( !pContent->GetValidLineNumFlag() && pContent->IsTextFrame() ) + { + const sal_uLong nAllLines = static_cast<const SwTextFrame*>(pContent)->GetAllLines(); + const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pContent))->RecalcAllLines(); + if ( IsPaintExtraData() && IsPaint() && + nAllLines != static_cast<const SwTextFrame*>(pContent)->GetAllLines() ) + m_pImp->GetShell()->AddPaintRect( pContent->getFrameArea() ); + } + + if ( IsAgain() ) + return; + + // If there's input, we interrupt processing. + if ( !pFly->IsFlyInContentFrame() ) + { + CheckIdleEnd(); + // consider interrupt formatting. + if ( IsInterrupt() && !mbFormatContentOnInterrupt ) + return; + } + pContent = pContent->GetNextContentFrame(); + } + CheckWaitCursor(); +} + +bool SwLayIdle::DoIdleJob_( const SwContentFrame *pCnt, IdleJobType eJob ) +{ + OSL_ENSURE( pCnt->IsTextFrame(), "NoText neighbour of Text" ); + // robust against misuse by e.g. #i52542# + if( !pCnt->IsTextFrame() ) + return false; + + SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(pCnt)); + // sw_redlinehide: spell check only the nodes with visible content? + SwTextNode* pTextNode = const_cast<SwTextNode*>(pTextFrame->GetTextNodeForParaProps()); + + bool bProcess = false; + for (size_t i = 0; pTextNode; ) + { + switch ( eJob ) + { + case ONLINE_SPELLING : + bProcess = pTextNode->IsWrongDirty(); break; + case AUTOCOMPLETE_WORDS : + bProcess = pTextNode->IsAutoCompleteWordDirty(); break; + case WORD_COUNT : + bProcess = pTextNode->IsWordCountDirty(); break; + case SMART_TAGS : + bProcess = pTextNode->IsSmartTagDirty(); break; + } + if (bProcess) + { + break; + } + if (sw::MergedPara const* pMerged = pTextFrame->GetMergedPara()) + { + while (true) + { + ++i; + if (i < pMerged->extents.size()) + { + if (pMerged->extents[i].pNode != pTextNode) + { + pTextNode = pMerged->extents[i].pNode; + break; + } + } + else + { + pTextNode = nullptr; + break; + } + } + } + else + pTextNode = nullptr; + } + + if( bProcess ) + { + assert(pTextNode); + SwViewShell *pSh = m_pImp->GetShell(); + if( COMPLETE_STRING == m_nTextPos ) + { + --m_nTextPos; + if( auto pCursorShell = dynamic_cast<SwCursorShell *>( pSh ) ) + if( !pCursorShell->IsTableMode() ) + { + SwPaM *pCursor = pCursorShell->GetCursor(); + if( !pCursor->HasMark() && !pCursor->IsMultiSelection() ) + { + m_pContentNode = pCursor->GetContentNode(); + m_nTextPos = pCursor->GetPoint()->nContent.GetIndex(); + } + } + } + sal_Int32 const nPos((m_pContentNode && pTextNode == m_pContentNode) + ? m_nTextPos + : COMPLETE_STRING); + + switch ( eJob ) + { + case ONLINE_SPELLING : + { + SwRect aRepaint( const_cast<SwTextFrame*>(pTextFrame)->AutoSpell_(*pTextNode, nPos) ); + // PENDING should stop idle spell checking + m_bPageValid = m_bPageValid && (SwTextNode::WrongState::TODO != pTextNode->GetWrongDirty()); + if ( aRepaint.HasArea() ) + m_pImp->GetShell()->InvalidateWindows( aRepaint ); + if (Application::AnyInput(VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER))) + return true; + break; + } + case AUTOCOMPLETE_WORDS : + const_cast<SwTextFrame*>(pTextFrame)->CollectAutoCmplWrds(*pTextNode, nPos); + // note: bPageValid remains true here even if the cursor + // position is skipped, so no PENDING state needed currently + if (Application::AnyInput(VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER))) + return true; + break; + case WORD_COUNT : + { + const sal_Int32 nEnd = pTextNode->GetText().getLength(); + SwDocStat aStat; + pTextNode->CountWords( aStat, 0, nEnd ); + if ( Application::AnyInput() ) + return true; + break; + } + case SMART_TAGS : + { + try { + const SwRect aRepaint( const_cast<SwTextFrame*>(pTextFrame)->SmartTagScan(*pTextNode) ); + m_bPageValid = m_bPageValid && !pTextNode->IsSmartTagDirty(); + if ( aRepaint.HasArea() ) + m_pImp->GetShell()->InvalidateWindows( aRepaint ); + } catch( const css::uno::RuntimeException&) { + // handle smarttag problems gracefully and provide diagnostics + TOOLS_WARN_EXCEPTION( "sw.core", "SMART_TAGS"); + } + if (Application::AnyInput(VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER))) + return true; + break; + } + } + } + + // The Flys that are anchored to the paragraph need to be considered too. + if ( pCnt->GetDrawObjs() ) + { + const SwSortedObjs &rObjs = *pCnt->GetDrawObjs(); + for (SwAnchoredObject* pObj : rObjs) + { + if ( auto pFly = pObj->DynCastFlyFrame() ) + { + if ( pFly->IsFlyInContentFrame() ) + { + const SwContentFrame *pC = pFly->ContainsContent(); + while( pC ) + { + if ( pC->IsTextFrame() ) + { + if ( DoIdleJob_( pC, eJob ) ) + return true; + } + pC = pC->GetNextContentFrame(); + } + } + } + } + } + return false; +} + +bool SwLayIdle::DoIdleJob( IdleJobType eJob, bool bVisAreaOnly ) +{ + // Spellcheck all contents of the pages. Either only the + // visible ones or all of them. + const SwViewShell* pViewShell = m_pImp->GetShell(); + const SwViewOption* pViewOptions = pViewShell->GetViewOptions(); + const SwDoc* pDoc = pViewShell->GetDoc(); + + switch ( eJob ) + { + case ONLINE_SPELLING : + if( !pViewOptions->IsOnlineSpell() ) + return false; + break; + case AUTOCOMPLETE_WORDS : + if( !SwViewOption::IsAutoCompleteWords() || + SwDoc::GetAutoCompleteWords().IsLockWordLstLocked()) + return false; + break; + case WORD_COUNT : + if ( !pViewShell->getIDocumentStatistics().GetDocStat().bModified ) + return false; + break; + case SMART_TAGS : + if ( pDoc->GetDocShell()->IsHelpDocument() || + pDoc->isXForms() || + !SwSmartTagMgr::Get().IsSmartTagsEnabled() ) + return false; + break; + default: OSL_FAIL( "Unknown idle job type" ); + } + + SwPageFrame *pPage; + if ( bVisAreaOnly ) + pPage = m_pImp->GetFirstVisPage(pViewShell->GetOut()); + else + pPage = static_cast<SwPageFrame*>(m_pRoot->Lower()); + + m_pContentNode = nullptr; + m_nTextPos = COMPLETE_STRING; + + while ( pPage ) + { + m_bPageValid = true; + const SwContentFrame *pCnt = pPage->ContainsContent(); + while( pCnt && pPage->IsAnLower( pCnt ) ) + { + if ( DoIdleJob_( pCnt, eJob ) ) + { + SAL_INFO("sw.idle", "DoIdleJob " << eJob << " interrupted on page " << pPage->GetPhyPageNum()); + return true; + } + pCnt = pCnt->GetNextContentFrame(); + } + if ( pPage->GetSortedObjs() ) + { + for ( size_t i = 0; pPage->GetSortedObjs() && + i < pPage->GetSortedObjs()->size(); ++i ) + { + const SwAnchoredObject* pObj = (*pPage->GetSortedObjs())[i]; + if ( auto pFly = pObj->DynCastFlyFrame() ) + { + const SwContentFrame *pC = pFly->ContainsContent(); + while( pC ) + { + if ( pC->IsTextFrame() ) + { + if ( DoIdleJob_( pC, eJob ) ) + { + SAL_INFO("sw.idle", "DoIdleJob " << eJob << " interrupted on page " << pPage->GetPhyPageNum()); + return true; + } + } + pC = pC->GetNextContentFrame(); + } + } + } + } + + if( m_bPageValid ) + { + switch ( eJob ) + { + case ONLINE_SPELLING : pPage->ValidateSpelling(); break; + case AUTOCOMPLETE_WORDS : pPage->ValidateAutoCompleteWords(); break; + case WORD_COUNT : pPage->ValidateWordCount(); break; + case SMART_TAGS : pPage->ValidateSmartTags(); break; + } + } + + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + if ( pPage && bVisAreaOnly && + !pPage->getFrameArea().Overlaps( m_pImp->GetShell()->VisArea())) + break; + } + return false; +} + +#if HAVE_FEATURE_DESKTOP && defined DBG_UTIL +void SwLayIdle::ShowIdle( Color eColor ) +{ + if ( m_bIndicator ) + return; + + m_bIndicator = true; + vcl::Window *pWin = m_pImp->GetShell()->GetWin(); + if (pWin && !pWin->SupportsDoubleBuffering()) // FIXME make this work with double-buffering + { + tools::Rectangle aRect( 0, 0, 5, 5 ); + aRect = pWin->PixelToLogic( aRect ); + // Depending on if idle layout is in progress or not, draw a "red square" or a "green square". + pWin->GetOutDev()->Push( vcl::PushFlags::FILLCOLOR|vcl::PushFlags::LINECOLOR ); + pWin->GetOutDev()->SetFillColor( eColor ); + pWin->GetOutDev()->SetLineColor(); + pWin->GetOutDev()->DrawRect( aRect ); + pWin->GetOutDev()->Pop(); + } +} +#define SHOW_IDLE( Color ) ShowIdle( Color ) +#else +#define SHOW_IDLE( Color ) +#endif // DBG_UTIL + +SwLayIdle::SwLayIdle( SwRootFrame *pRt, SwViewShellImp *pI ) : + m_pRoot( pRt ), + m_pImp( pI ) +#ifdef DBG_UTIL + , m_bIndicator( false ) +#endif +{ + SAL_INFO("sw.idle", "SwLayIdle() entry"); + + m_pImp->m_pIdleAct = this; + + SHOW_IDLE( COL_LIGHTRED ); + + m_pImp->GetShell()->EnableSmooth( false ); + + // First, spellcheck the visible area. Only if there's nothing + // to do there, we trigger the IdleFormat. + if ( !DoIdleJob( SMART_TAGS, true ) && + !DoIdleJob( ONLINE_SPELLING, true ) && + !DoIdleJob( AUTOCOMPLETE_WORDS, true ) ) + { + // Format, then register repaint rectangles with the SwViewShell if necessary. + // This requires running artificial actions, so we don't get undesired + // effects when for instance the page count gets changed. + // We remember the shells where the cursor is visible, so we can make + // it visible again if needed after a document change. + std::vector<bool> aBools; + for(SwViewShell& rSh : m_pImp->GetShell()->GetRingContainer()) + { + ++rSh.mnStartAction; + bool bVis = false; + if ( auto pCursorShell = dynamic_cast<SwCursorShell*>( &rSh) ) + { + bVis = pCursorShell->GetCharRect().Overlaps(rSh.VisArea()); + } + aBools.push_back( bVis ); + } + + bool bInterrupt(false); + { + SwLayAction aAction( m_pRoot, m_pImp ); + aAction.SetInputType( VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER) ); + aAction.SetIdle( true ); + aAction.SetWaitAllowed( false ); + aAction.Action(m_pImp->GetShell()->GetOut()); + bInterrupt = aAction.IsInterrupt(); + } + + // Further start/end actions only happen if there were paints started + // somewhere or if the visibility of the CharRects has changed. + bool bActions = false; + size_t nBoolIdx = 0; + for(SwViewShell& rSh : m_pImp->GetShell()->GetRingContainer()) + { + --rSh.mnStartAction; + + if ( rSh.Imp()->HasPaintRegion() ) + bActions = true; + else + { + SwRect aTmp( rSh.VisArea() ); + rSh.UISizeNotify(); + + // Are we supposed to crash if rSh isn't a cursor shell?! + // bActions |= aTmp != rSh.VisArea() || + // aBools[nBoolIdx] != ((SwCursorShell*)&rSh)->GetCharRect().IsOver( rSh.VisArea() ); + + // aBools[ i ] is true, if the i-th shell is a cursor shell (!!!) + // and the cursor is visible. + bActions |= aTmp != rSh.VisArea(); + if ( aTmp == rSh.VisArea() ) + if ( auto pCursorShell = dynamic_cast< SwCursorShell*>( &rSh) ) + bActions |= aBools[nBoolIdx] != pCursorShell->GetCharRect().Overlaps( rSh.VisArea() ); + } + + ++nBoolIdx; + } + + if ( bActions ) + { + // Prepare start/end actions via CursorShell, so the cursor, selection + // and VisArea can be set correctly. + nBoolIdx = 0; + for(SwViewShell& rSh : m_pImp->GetShell()->GetRingContainer()) + { + SwCursorShell* pCursorShell = dynamic_cast<SwCursorShell*>( &rSh); + + if ( pCursorShell ) + pCursorShell->SttCursorMove(); + + // If there are accrued paints, it's best to simply invalidate + // the whole window. Otherwise there would arise paint problems whose + // solution would be disproportionally expensive. + SwViewShellImp *pViewImp = rSh.Imp(); + bool bUnlock = false; + if ( pViewImp->HasPaintRegion() ) + { + pViewImp->DeletePaintRegion(); + + // Cause a repaint with virtual device. + rSh.LockPaint(); + bUnlock = true; + } + + if ( pCursorShell ) + // If the Cursor was visible, we need to make it visible again. + // Otherwise, EndCursorMove with true for IdleEnd + pCursorShell->EndCursorMove( !aBools[nBoolIdx] ); + if( bUnlock ) + { + if( pCursorShell ) + { + // UnlockPaint overwrite the selection from the + // CursorShell and calls the virtual method paint + // to fill the virtual device. This fill don't have + // paint the selection! -> Set the focus flag at + // CursorShell and it doesn't paint the selection. + pCursorShell->ShellLoseFocus(); + pCursorShell->UnlockPaint( true ); + pCursorShell->ShellGetFocus(); + } + else + rSh.UnlockPaint( true ); + } + ++nBoolIdx; + + } + } + + if (!bInterrupt) + { + if ( !DoIdleJob( WORD_COUNT, false ) ) + if ( !DoIdleJob( SMART_TAGS, false ) ) + if ( !DoIdleJob( ONLINE_SPELLING, false ) ) + DoIdleJob( AUTOCOMPLETE_WORDS, false ); + } + + bool bInValid = false; + const SwViewOption& rVOpt = *m_pImp->GetShell()->GetViewOptions(); + const SwViewShell* pViewShell = m_pImp->GetShell(); + // See conditions in DoIdleJob() + const bool bSpell = rVOpt.IsOnlineSpell(); + const bool bACmplWrd = SwViewOption::IsAutoCompleteWords(); + const bool bWordCount = pViewShell->getIDocumentStatistics().GetDocStat().bModified; + const bool bSmartTags = !pViewShell->GetDoc()->GetDocShell()->IsHelpDocument() && + !pViewShell->GetDoc()->isXForms() && + SwSmartTagMgr::Get().IsSmartTagsEnabled(); + + SwPageFrame *pPg = static_cast<SwPageFrame*>(m_pRoot->Lower()); + do + { + bInValid = pPg->IsInvalidContent() || pPg->IsInvalidLayout() || + pPg->IsInvalidFlyContent() || pPg->IsInvalidFlyLayout() || + pPg->IsInvalidFlyInCnt() || + (bSpell && pPg->IsInvalidSpelling()) || + (bACmplWrd && pPg->IsInvalidAutoCompleteWords()) || + (bWordCount && pPg->IsInvalidWordCount()) || + (bSmartTags && pPg->IsInvalidSmartTags()); + + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + + } while ( pPg && !bInValid ); + + if ( !bInValid ) + { + m_pRoot->ResetIdleFormat(); + SfxObjectShell* pDocShell = m_pImp->GetShell()->GetDoc()->GetDocShell(); + pDocShell->Broadcast( SfxEventHint( SfxEventHintId::SwEventLayoutFinished, SwDocShell::GetEventName(STR_SW_EVENT_LAYOUT_FINISHED), pDocShell ) ); + } + } + + m_pImp->GetShell()->EnableSmooth( true ); + +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if( m_pImp->IsAccessible() ) + m_pImp->FireAccessibleEvents(); +#endif + + SAL_INFO("sw.idle", "SwLayIdle() return"); + +#ifdef DBG_UTIL + if ( m_bIndicator && m_pImp->GetShell()->GetWin() ) + { + // Do not invalidate indicator, this may cause an endless loop. Instead, just repaint it + // This should be replaced by an overlay object in the future, anyways. Since it's only for debug + // purposes, it is not urgent. + m_bIndicator = false; SHOW_IDLE( COL_LIGHTGREEN ); + } +#endif +} + +SwLayIdle::~SwLayIdle() +{ + m_pImp->m_pIdleAct = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/laycache.cxx b/sw/source/core/layout/laycache.cxx new file mode 100644 index 000000000..e5b1dc341 --- /dev/null +++ b/sw/source/core/layout/laycache.cxx @@ -0,0 +1,1200 @@ +/* -*- 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 <editeng/formatbreakitem.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/stream.hxx> +#include <doc.hxx> +#include <IDocumentStatistics.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <docstat.hxx> +#include <fmtpdsc.hxx> +#include <laycache.hxx> +#include "layhelp.hxx" +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> +#include <swtable.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <sectfrm.hxx> +#include <fmtcntnt.hxx> +#include <pagedesc.hxx> +#include <frmtool.hxx> +#include <dflyobj.hxx> +#include <dcontact.hxx> +#include <viewopt.hxx> +#include <flyfrm.hxx> +#include <sortedobjs.hxx> +#include <ndindex.hxx> +#include <node.hxx> +#include <ndtxt.hxx> +#include <frameformats.hxx> + +#include <limits> + +using namespace ::com::sun::star; + +SwLayoutCache::SwLayoutCache() : m_nLockCount( 0 ) {} + +/* + * Reading and writing of the layout cache. + * The layout cache is not necessary, but it improves + * the performance and reduces the text flow during + * the formatting. + * The layout cache contains the index of the paragraphs/tables + * at the top of every page, so it's possible to create + * the right count of pages and to distribute the document content + * to this pages before the formatting starts. + */ + +void SwLayoutCache::Read( SvStream &rStream ) +{ + if( !m_pImpl ) + { + m_pImpl.reset( new SwLayCacheImpl ); + if( !m_pImpl->Read( rStream ) ) + { + m_pImpl.reset(); + } + } +} + +void SwLayCacheImpl::Insert( sal_uInt16 nType, SwNodeOffset nIndex, sal_Int32 nOffset ) +{ + m_aType.push_back( nType ); + mIndices.push_back( nIndex ); + m_aOffset.push_back( nOffset ); +} + +bool SwLayCacheImpl::Read( SvStream& rStream ) +{ + SwLayCacheIoImpl aIo( rStream, false ); + if( aIo.GetMajorVersion() > SW_LAYCACHE_IO_VERSION_MAJOR ) + return false; + + // Due to an evil bug in the layout cache (#102759#), we cannot trust the + // sizes of fly frames which have been written using the "old" layout cache. + // This flag should indicate that we do not want to trust the width and + // height of fly frames + m_bUseFlyCache = aIo.GetMinorVersion() >= 1; + + aIo.OpenRec( SW_LAYCACHE_IO_REC_PAGES ); + aIo.OpenFlagRec(); + aIo.CloseFlagRec(); + while( aIo.BytesLeft() && !aIo.HasError() ) + { + sal_uInt32 nIndex(0), nOffset(0); + + switch( aIo.Peek() ) + { + case SW_LAYCACHE_IO_REC_PARA: + { + aIo.OpenRec( SW_LAYCACHE_IO_REC_PARA ); + sal_uInt8 cFlags = aIo.OpenFlagRec(); + aIo.GetStream().ReadUInt32( nIndex ); + if( (cFlags & 0x01) != 0 ) + aIo.GetStream().ReadUInt32( nOffset ); + else + nOffset = COMPLETE_STRING; + aIo.CloseFlagRec(); + Insert( SW_LAYCACHE_IO_REC_PARA, SwNodeOffset(nIndex), static_cast<sal_Int32>(nOffset) ); + aIo.CloseRec(); + break; + } + case SW_LAYCACHE_IO_REC_TABLE: + aIo.OpenRec( SW_LAYCACHE_IO_REC_TABLE ); + aIo.OpenFlagRec(); + aIo.GetStream().ReadUInt32( nIndex ) + .ReadUInt32( nOffset ); + Insert( SW_LAYCACHE_IO_REC_TABLE, SwNodeOffset(nIndex), static_cast<sal_Int32>(nOffset) ); + aIo.CloseFlagRec(); + aIo.CloseRec(); + break; + case SW_LAYCACHE_IO_REC_FLY: + { + aIo.OpenRec( SW_LAYCACHE_IO_REC_FLY ); + aIo.OpenFlagRec(); + aIo.CloseFlagRec(); + sal_Int32 nX(0), nY(0), nW(0), nH(0); + sal_uInt16 nPgNum(0); + aIo.GetStream().ReadUInt16( nPgNum ).ReadUInt32( nIndex ) + .ReadInt32( nX ).ReadInt32( nY ).ReadInt32( nW ).ReadInt32( nH ); + m_FlyCache.emplace_back( nPgNum, nIndex, nX, nY, nW, nH ); + aIo.CloseRec(); + break; + } + default: + aIo.SkipRec(); + break; + } + } + aIo.CloseRec(); + + return !aIo.HasError(); +} + +/** writes the index (more precise: the difference between + * the index and the first index of the document content) + * of the first paragraph/table at the top of every page. + * If at the top of a page is the rest of a paragraph/table + * from the bottom of the previous page, the character/row + * number is stored, too. + * The position, size and page number of the text frames + * are stored, too + */ +void SwLayoutCache::Write( SvStream &rStream, const SwDoc& rDoc ) +{ + if( !rDoc.getIDocumentLayoutAccess().GetCurrentLayout() ) // the layout itself .. + return; + + SwLayCacheIoImpl aIo( rStream, true ); + // We want to save the relative index, so we need the index + // of the first content + SwNodeOffset nStartOfContent = rDoc.GetNodes().GetEndOfContent(). + StartOfSectionNode()->GetIndex(); + // The first page... + SwPageFrame* pPage = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(rDoc.getIDocumentLayoutAccess().GetCurrentLayout()->Lower())); + + aIo.OpenRec( SW_LAYCACHE_IO_REC_PAGES ); + aIo.OpenFlagRec( 0, 0 ); + aIo.CloseFlagRec(); + while( pPage ) + { + if( pPage->GetPrev() ) + { + SwLayoutFrame* pLay = pPage->FindBodyCont(); + SwFrame* pTmp = pLay ? pLay->ContainsAny() : nullptr; + // We are only interested in paragraph or table frames, + // a section frames contains paragraphs/tables. + if( pTmp && pTmp->IsSctFrame() ) + pTmp = static_cast<SwSectionFrame*>(pTmp)->ContainsAny(); + + if( pTmp ) // any content + { + if( pTmp->IsTextFrame() ) + { + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTmp)); + assert(!pFrame->GetMergedPara()); + SwNodeOffset nNdIdx = pFrame->GetTextNodeFirst()->GetIndex(); + if( nNdIdx > nStartOfContent ) + { + /* Open Paragraph Record */ + aIo.OpenRec( SW_LAYCACHE_IO_REC_PARA ); + bool bFollow = static_cast<SwTextFrame*>(pTmp)->IsFollow(); + aIo.OpenFlagRec( bFollow ? 0x01 : 0x00, + bFollow ? 8 : 4 ); + nNdIdx -= nStartOfContent; + aIo.GetStream().WriteUInt32( sal_Int32(nNdIdx) ); + if( bFollow ) + aIo.GetStream().WriteUInt32( sal_Int32(static_cast<SwTextFrame*>(pTmp)->GetOffset()) ); + aIo.CloseFlagRec(); + /* Close Paragraph Record */ + aIo.CloseRec(); + } + } + else if( pTmp->IsTabFrame() ) + { + SwTabFrame* pTab = static_cast<SwTabFrame*>(pTmp); + sal_uLong nOfst = COMPLETE_STRING; + if( pTab->IsFollow() ) + { + // If the table is a follow, we have to look for the + // master and to count all rows to get the row number + nOfst = 0; + if( pTab->IsFollow() ) + pTab = pTab->FindMaster( true ); + while( pTab != pTmp ) + { + SwFrame* pSub = pTab->Lower(); + while( pSub ) + { + ++nOfst; + pSub = pSub->GetNext(); + } + pTab = pTab->GetFollow(); + assert(pTab && "Table follow without master"); + } + } + while (true) + { + SwNodeOffset nNdIdx = + pTab->GetTable()->GetTableNode()->GetIndex(); + if( nNdIdx > nStartOfContent ) + { + /* Open Table Record */ + aIo.OpenRec( SW_LAYCACHE_IO_REC_TABLE ); + aIo.OpenFlagRec( 0, 8 ); + nNdIdx -= nStartOfContent; + aIo.GetStream().WriteUInt32( sal_Int32(nNdIdx) ) + .WriteUInt32( nOfst ); + aIo.CloseFlagRec(); + /* Close Table Record */ + aIo.CloseRec(); + } + // If the table has a follow on the next page, + // we know already the row number and store this + // immediately. + if( pTab->GetFollow() ) + { + if( nOfst == sal_uLong(COMPLETE_STRING) ) + nOfst = 0; + do + { + SwFrame* pSub = pTab->Lower(); + while( pSub ) + { + ++nOfst; + pSub = pSub->GetNext(); + } + pTab = pTab->GetFollow(); + SwPageFrame *pTabPage = pTab->FindPageFrame(); + if( pTabPage != pPage ) + { + OSL_ENSURE( pPage->GetPhyPageNum() < + pTabPage->GetPhyPageNum(), + "Looping Tableframes" ); + pPage = pTabPage; + break; + } + } while ( pTab->GetFollow() ); + } + else + break; + } + } + } + } + if( pPage->GetSortedObjs() ) + { + SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if (SwFlyFrame *pFly = pAnchoredObj->DynCastFlyFrame()) + { + if( pFly->getFrameArea().Left() != FAR_AWAY && + !pFly->GetAnchorFrame()->FindFooterOrHeader() ) + { + const SwContact *pC = + ::GetUserCall(pAnchoredObj->GetDrawObj()); + if( pC ) + { + sal_uInt32 nOrdNum = pAnchoredObj->GetDrawObj()->GetOrdNum(); + sal_uInt16 nPageNum = pPage->GetPhyPageNum(); + /* Open Fly Record */ + aIo.OpenRec( SW_LAYCACHE_IO_REC_FLY ); + aIo.OpenFlagRec( 0, 0 ); + aIo.CloseFlagRec(); + const SwRect& rRct = pFly->getFrameArea(); + sal_Int32 nX = rRct.Left() - pPage->getFrameArea().Left(); + sal_Int32 nY = rRct.Top() - pPage->getFrameArea().Top(); + aIo.GetStream().WriteUInt16( nPageNum ).WriteUInt32( nOrdNum ) + .WriteInt32( nX ).WriteInt32( nY ) + .WriteInt32( rRct.Width() ) + .WriteInt32( rRct.Height() ); + /* Close Fly Record */ + aIo.CloseRec(); + } + } + } + } + } + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + aIo.CloseRec(); +} + +#ifdef DBG_UTIL +bool SwLayoutCache::CompareLayout( const SwDoc& rDoc ) const +{ + if( !m_pImpl ) + return true; + const SwRootFrame *pRootFrame = rDoc.getIDocumentLayoutAccess().GetCurrentLayout(); + if( pRootFrame ) + { + size_t nIndex = 0; + SwNodeOffset nStartOfContent = rDoc.GetNodes().GetEndOfContent(). + StartOfSectionNode()->GetIndex(); + const SwPageFrame* pPage = static_cast<const SwPageFrame*>(pRootFrame->Lower()); + if( pPage ) + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + while( pPage ) + { + if( nIndex >= m_pImpl->size() ) + return false; + + const SwLayoutFrame* pLay = pPage->FindBodyCont(); + const SwFrame* pTmp = pLay ? pLay->ContainsAny() : nullptr; + if( pTmp && pTmp->IsSctFrame() ) + pTmp = static_cast<const SwSectionFrame*>(pTmp)->ContainsAny(); + if( pTmp ) + { + if( pTmp->IsTextFrame() ) + { + + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTmp)); + assert(!pFrame->GetMergedPara()); + SwNodeOffset nNdIdx = pFrame->GetTextNodeFirst()->GetIndex(); + if( nNdIdx > nStartOfContent ) + { + bool bFollow = static_cast<const SwTextFrame*>(pTmp)->IsFollow(); + nNdIdx -= nStartOfContent; + if( m_pImpl->GetBreakIndex( nIndex ) != nNdIdx || + SW_LAYCACHE_IO_REC_PARA != + m_pImpl->GetBreakType( nIndex ) || + (bFollow + ? sal_Int32(static_cast<const SwTextFrame*>(pTmp)->GetOffset()) + : COMPLETE_STRING) != m_pImpl->GetBreakOfst(nIndex)) + { + return false; + } + ++nIndex; + } + } + else if( pTmp->IsTabFrame() ) + { + const SwTabFrame* pTab = static_cast<const SwTabFrame*>(pTmp); + sal_Int32 nOfst = COMPLETE_STRING; + if( pTab->IsFollow() ) + { + nOfst = 0; + if( pTab->IsFollow() ) + pTab = pTab->FindMaster( true ); + while( pTab != pTmp ) + { + const SwFrame* pSub = pTab->Lower(); + while( pSub ) + { + ++nOfst; + pSub = pSub->GetNext(); + } + pTab = pTab->GetFollow(); + } + } + do + { + SwNodeOffset nNdIdx = + pTab->GetTable()->GetTableNode()->GetIndex(); + if( nNdIdx > nStartOfContent ) + { + nNdIdx -= nStartOfContent; + if( m_pImpl->GetBreakIndex( nIndex ) != nNdIdx || + SW_LAYCACHE_IO_REC_TABLE != + m_pImpl->GetBreakType( nIndex ) || + nOfst != m_pImpl->GetBreakOfst( nIndex ) ) + { + return false; + } + ++nIndex; + } + if( pTab->GetFollow() ) + { + if( nOfst == COMPLETE_STRING ) + nOfst = 0; + do + { + const SwFrame* pSub = pTab->Lower(); + while( pSub ) + { + ++nOfst; + pSub = pSub->GetNext(); + } + pTab = pTab->GetFollow(); + const SwPageFrame *pTabPage = pTab->FindPageFrame(); + if( pTabPage != pPage ) + { + pPage = pTabPage; + break; + } + } while ( pTab->GetFollow() ); + } + else + break; + } while( pTab ); + } + } + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + } + } + return true; +} +#endif + +void SwLayoutCache::ClearImpl() +{ + if( !IsLocked() ) + { + m_pImpl.reset(); + } +} + +SwLayoutCache::~SwLayoutCache() +{ + OSL_ENSURE( !m_nLockCount, "Deleting a locked SwLayoutCache!?" ); +} + +/// helper class to create not nested section frames for nested sections. +SwActualSection::SwActualSection( SwActualSection *pUp, + SwSectionFrame *pSect, + SwSectionNode *pNd ) : + m_pUpper( pUp ), + m_pSectFrame( pSect ), + m_pSectNode( pNd ) +{ + if ( !m_pSectNode ) + { + const SwNodeIndex *pIndex = pSect->GetFormat()->GetContent().GetContentIdx(); + m_pSectNode = pIndex->GetNode().FindSectionNode(); + } +} + +namespace { + +bool sanityCheckLayoutCache(SwLayCacheImpl const& rCache, + SwNodes const& rNodes, SwNodeOffset nNodeIndex) +{ + auto const nStartOfContent(rNodes.GetEndOfContent().StartOfSectionNode()->GetIndex()); + nNodeIndex -= nStartOfContent; + auto const nMaxIndex(rNodes.GetEndOfContent().GetIndex() - nStartOfContent); + for (size_t nIndex = 0; nIndex < rCache.size(); ++nIndex) + { + auto const nBreakIndex(rCache.GetBreakIndex(nIndex)); + if (nBreakIndex < nNodeIndex || nMaxIndex <= nBreakIndex) + { + SAL_WARN("sw.layout", + "invalid node index in layout-cache: " << nBreakIndex); + return false; + } + auto const nBreakType(rCache.GetBreakType(nIndex)); + switch (nBreakType) + { + case SW_LAYCACHE_IO_REC_PARA: + if (!rNodes[nBreakIndex + nStartOfContent]->IsTextNode()) + { + SAL_WARN("sw.layout", + "invalid node of type 'P' in layout-cache"); + return false; + } + break; + case SW_LAYCACHE_IO_REC_TABLE: + if (!rNodes[nBreakIndex + nStartOfContent]->IsTableNode()) + { + SAL_WARN("sw.layout", + "invalid node of type 'T' in layout-cache"); + return false; + } + break; + default: + assert(false); // Read shouldn't have inserted that + } + } + return true; +} + +} // namespace + +/** helper class, which utilizes the layout cache information + * to distribute the document content to the right pages. + * It's used by the InsertCnt_(..)-function. + * If there's no layout cache, the distribution to the pages is more + * a guess, but a guess with statistical background. + */ +SwLayHelper::SwLayHelper( SwDoc *pD, SwFrame* &rpF, SwFrame* &rpP, SwPageFrame* &rpPg, + SwLayoutFrame* &rpL, std::unique_ptr<SwActualSection> &rpA, + SwNodeOffset nNodeIndex, bool bCache ) + : mrpFrame( rpF ) + , mrpPrv( rpP ) + , mrpPage( rpPg ) + , mrpLay( rpL ) + , mrpActualSection( rpA ) + , mbBreakAfter(false) + , mpDoc(pD) + , mnMaxParaPerPage( 25 ) + , mnParagraphCnt( bCache ? 0 : USHRT_MAX ) + , mnFlyIdx( 0 ) + , mbFirst( bCache ) +{ + mpImpl = mpDoc->GetLayoutCache() ? mpDoc->GetLayoutCache()->LockImpl() : nullptr; + if( mpImpl ) + { + SwNodes const& rNodes(mpDoc->GetNodes()); + if (sanityCheckLayoutCache(*mpImpl, rNodes, nNodeIndex)) + { + mnIndex = 0; + mnStartOfContent = rNodes.GetEndOfContent().StartOfSectionNode()->GetIndex(); + mnMaxParaPerPage = 1000; + } + else + { + mpDoc->GetLayoutCache()->UnlockImpl(); + mpImpl = nullptr; + mnIndex = std::numeric_limits<size_t>::max(); + mnStartOfContent = SwNodeOffset(USHRT_MAX); + } + } + else + { + mnIndex = std::numeric_limits<size_t>::max(); + mnStartOfContent = NODE_OFFSET_MAX; + } +} + +SwLayHelper::~SwLayHelper() +{ + if( mpImpl ) + { + OSL_ENSURE( mpDoc && mpDoc->GetLayoutCache(), "Missing layoutcache" ); + mpDoc->GetLayoutCache()->UnlockImpl(); + } +} + +/** Does NOT really calculate the page count, + * it returns the page count value from the layout cache, if available, + * otherwise it estimates the page count. + */ +sal_uLong SwLayHelper::CalcPageCount() +{ + sal_uLong nPgCount; + SwLayCacheImpl *pCache = mpDoc->GetLayoutCache() ? + mpDoc->GetLayoutCache()->LockImpl() : nullptr; + if( pCache ) + { + nPgCount = pCache->size() + 1; + mpDoc->GetLayoutCache()->UnlockImpl(); + } + else + { + nPgCount = mpDoc->getIDocumentStatistics().GetDocStat().nPage; + if ( nPgCount <= 10 ) // no page insertion for less than 10 pages + nPgCount = 0; + sal_Int32 nNdCount = mpDoc->getIDocumentStatistics().GetDocStat().nPara; + if ( nNdCount <= 1 ) + { + //Estimates the number of paragraphs. + SwNodeOffset nTmp = mpDoc->GetNodes().GetEndOfContent().GetIndex() - + mpDoc->GetNodes().GetEndOfExtras().GetIndex(); + //Tables have a little overhead... + nTmp -= SwNodeOffset(mpDoc->GetTableFrameFormats()->size() * 25); + //Fly frames, too .. + nTmp -= (mpDoc->GetNodes().GetEndOfAutotext().GetIndex() - + mpDoc->GetNodes().GetEndOfInserts().GetIndex()) / SwNodeOffset(3 * 5); + if ( nTmp > SwNodeOffset(0) ) + nNdCount = sal_Int32(nTmp); + } + if ( nNdCount > 100 ) // no estimation below this value + { + if ( nPgCount > 0 ) + { // tdf#129529 avoid 0... + mnMaxParaPerPage = std::max<sal_uLong>(3, nNdCount / nPgCount); + } + else + { + mnMaxParaPerPage = std::max( sal_uLong(20), + sal_uLong(20 + nNdCount / 1000 * 3) ); + const sal_uLong nMax = 53; + mnMaxParaPerPage = std::min( mnMaxParaPerPage, nMax ); + nPgCount = nNdCount / mnMaxParaPerPage; + } + if ( nNdCount < 1000 ) + nPgCount = 0;// no progress bar for small documents + SwViewShell *pSh = nullptr; + if( mrpLay && mrpLay->getRootFrame() ) + pSh = mrpLay->getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + mnMaxParaPerPage *= 6; + } + } + return nPgCount; +} + +/** + * inserts a page and return true, if + * - the break after flag is set + * - the actual content wants a break before + * - the maximum count of paragraph/rows is reached + * + * The break after flag is set, if the actual content + * wants a break after. + */ +bool SwLayHelper::CheckInsertPage() +{ + bool bEnd = nullptr == mrpPage->GetNext(); + const SvxFormatBreakItem& rBrk = mrpFrame->GetBreakItem(); + const SwFormatPageDesc& rDesc = mrpFrame->GetPageDescItem(); + // #118195# Do not evaluate page description if frame + // is a follow frame! + const SwPageDesc* pDesc = mrpFrame->IsFlowFrame() && + SwFlowFrame::CastFlowFrame( mrpFrame )->IsFollow() ? + nullptr : + rDesc.GetPageDesc(); + + bool bBrk = mnParagraphCnt > mnMaxParaPerPage || mbBreakAfter; + mbBreakAfter = rBrk.GetBreak() == SvxBreak::PageAfter || + rBrk.GetBreak() == SvxBreak::PageBoth; + if ( !bBrk ) + bBrk = rBrk.GetBreak() == SvxBreak::PageBefore || + rBrk.GetBreak() == SvxBreak::PageBoth; + + if ( bBrk || pDesc ) + { + ::std::optional<sal_uInt16> oPgNum; + if ( !pDesc ) + { + pDesc = mrpPage->GetPageDesc()->GetFollow(); + } + else + { + oPgNum = rDesc.GetNumOffset(); + if ( oPgNum ) + static_cast<SwRootFrame*>(mrpPage->GetUpper())->SetVirtPageNum(true); + } + bool bNextPageRight = !mrpPage->OnRightPage(); + bool bInsertEmpty = false; + assert(mrpPage->GetUpper()->GetLower()); + if (oPgNum && bNextPageRight != IsRightPageByNumber( + *static_cast<SwRootFrame*>(mrpPage->GetUpper()), *oPgNum)) + { + bNextPageRight = !bNextPageRight; + bInsertEmpty = true; + } + // If the page style is changing, we'll have a first page. + bool bNextPageFirst = pDesc != mrpPage->GetPageDesc(); + ::InsertNewPage( const_cast<SwPageDesc&>(*pDesc), mrpPage->GetUpper(), + bNextPageRight, bNextPageFirst, bInsertEmpty, false, mrpPage->GetNext()); + if ( bEnd ) + { + OSL_ENSURE( mrpPage->GetNext(), "No new page?" ); + do + { mrpPage = static_cast<SwPageFrame*>(mrpPage->GetNext()); + } while ( mrpPage->GetNext() ); + } + else + { + OSL_ENSURE( mrpPage->GetNext(), "No new page?" ); + mrpPage = static_cast<SwPageFrame*>(mrpPage->GetNext()); + if ( mrpPage->IsEmptyPage() ) + { + OSL_ENSURE( mrpPage->GetNext(), "No new page?" ); + mrpPage = static_cast<SwPageFrame*>(mrpPage->GetNext()); + } + } + mrpLay = mrpPage->FindBodyCont(); + while( mrpLay->Lower() ) + mrpLay = static_cast<SwLayoutFrame*>(mrpLay->Lower()); + return true; + } + return false; +} + +/** entry point for the InsertCnt_-function. + * The document content index is checked either it is + * in the layout cache either it's time to insert a page + * cause the maximal estimation of content per page is reached. + * A really big table or long paragraph may contains more than + * one page, in this case the needed count of pages will inserted. + */ +bool SwLayHelper::CheckInsert( SwNodeOffset nNodeIndex ) +{ + bool bRet = false; + bool bLongTab = false; + sal_uLong nMaxRowPerPage( 0 ); + nNodeIndex -= mnStartOfContent; + sal_uInt16 nRows( 0 ); + if( mrpFrame->IsTabFrame() ) + { + //Inside a table counts every row as a paragraph + SwFrame *pLow = static_cast<SwTabFrame*>(mrpFrame)->Lower(); + nRows = 0; + do + { + ++nRows; + pLow = pLow->GetNext(); + } while ( pLow ); + mnParagraphCnt += nRows; + if( !mpImpl && mnParagraphCnt > mnMaxParaPerPage + 10 ) + { + // OD 09.04.2003 #108698# - improve heuristics: + // Assume that a table, which has more than three times the quantity + // of maximal paragraphs per page rows, consists of rows, which have + // the height of a normal paragraph. Thus, allow as much rows per page + // as much paragraphs are allowed. + if ( nRows > ( 3*mnMaxParaPerPage ) ) + { + nMaxRowPerPage = mnMaxParaPerPage; + } + else + { + SwFrame *pTmp = static_cast<SwTabFrame*>(mrpFrame)->Lower(); + if( pTmp->GetNext() ) + pTmp = pTmp->GetNext(); + pTmp = static_cast<SwRowFrame*>(pTmp)->Lower(); + sal_uInt16 nCnt = 0; + do + { + ++nCnt; + pTmp = pTmp->GetNext(); + } while( pTmp ); + nMaxRowPerPage = std::max( sal_uLong(2), mnMaxParaPerPage / nCnt ); + } + bLongTab = true; + } + } + else + ++mnParagraphCnt; + if( mbFirst && mpImpl && mnIndex < mpImpl->size() && + mpImpl->GetBreakIndex( mnIndex ) == nNodeIndex && + ( mpImpl->GetBreakOfst( mnIndex ) < COMPLETE_STRING || + ( ++mnIndex < mpImpl->size() && + mpImpl->GetBreakIndex( mnIndex ) == nNodeIndex ) ) ) + mbFirst = false; + // OD 09.04.2003 #108698# - always split a big tables. + if ( !mbFirst || + ( mrpFrame->IsTabFrame() && bLongTab ) + ) + { + sal_Int32 nRowCount = 0; + do + { + if( mpImpl || bLongTab ) + { + sal_Int32 nOfst = COMPLETE_STRING; + sal_uInt16 nType = SW_LAYCACHE_IO_REC_PAGES; + if( bLongTab ) + { + mbBreakAfter = true; + nOfst = static_cast<sal_Int32>(nRowCount + nMaxRowPerPage); + } + else + { + while( mnIndex < mpImpl->size() && + mpImpl->GetBreakIndex(mnIndex) < nNodeIndex) + ++mnIndex; + if( mnIndex < mpImpl->size() && + mpImpl->GetBreakIndex(mnIndex) == nNodeIndex ) + { + nType = mpImpl->GetBreakType( mnIndex ); + nOfst = mpImpl->GetBreakOfst( mnIndex++ ); + mbBreakAfter = true; + } + } + + if( nOfst < COMPLETE_STRING ) + { + bool bSplit = false; + sal_uInt16 nRepeat( 0 ); + if( !bLongTab && mrpFrame->IsTextFrame() && + SW_LAYCACHE_IO_REC_PARA == nType && + nOfst < static_cast<SwTextFrame*>(mrpFrame)->GetText().getLength()) + bSplit = true; + else if( mrpFrame->IsTabFrame() && nRowCount < nOfst && + ( bLongTab || SW_LAYCACHE_IO_REC_TABLE == nType ) ) + { + nRepeat = static_cast<SwTabFrame*>(mrpFrame)-> + GetTable()->GetRowsToRepeat(); + bSplit = nOfst < nRows && nRowCount + nRepeat < nOfst; + bLongTab = bLongTab && bSplit; + } + if( bSplit ) + { + mrpFrame->InsertBehind( mrpLay, mrpPrv ); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*mrpFrame); + aFrm.Pos() = mrpLay->getFrameArea().Pos(); + aFrm.Pos().AdjustY(1 ); + } + + mrpPrv = mrpFrame; + if( mrpFrame->IsTabFrame() ) + { + SwTabFrame* pTab = static_cast<SwTabFrame*>(mrpFrame); + // #i33629#, #i29955# + ::RegistFlys( pTab->FindPageFrame(), pTab ); + SwFrame *pRow = pTab->Lower(); + SwTabFrame *pFoll = new SwTabFrame( *pTab ); + + SwFrame *pPrv; + if( nRepeat > 0 ) + { + sw::FlyCreationSuppressor aSuppressor; + // Insert new headlines: + sal_uInt16 nRowIdx = 0; + SwRowFrame* pHeadline = nullptr; + while( nRowIdx < nRepeat ) + { + OSL_ENSURE( pTab->GetTable()->GetTabLines()[ nRowIdx ], "Table without rows?" ); + pHeadline = + new SwRowFrame( *pTab->GetTable()->GetTabLines()[ nRowIdx ], pTab ); + pHeadline->SetRepeatedHeadline( true ); + pHeadline->InsertBefore( pFoll, nullptr ); + pHeadline->RegistFlys(); + + ++nRowIdx; + } + pPrv = pHeadline; + nRows = nRows + nRepeat; + } + else + pPrv = nullptr; + while( pRow && nRowCount < nOfst ) + { + pRow = pRow->GetNext(); + ++nRowCount; + } + while ( pRow ) + { + SwFrame* pNxt = pRow->GetNext(); + pRow->RemoveFromLayout(); + pRow->InsertBehind( pFoll, pPrv ); + pPrv = pRow; + pRow = pNxt; + } + mrpFrame = pFoll; + } + else + { + SwTextFrame *const pNew = static_cast<SwTextFrame*>( + static_cast<SwTextFrame*>(mrpFrame) + ->GetTextNodeFirst()->MakeFrame(mrpFrame)); + pNew->ManipOfst( TextFrameIndex(nOfst) ); + pNew->SetFollow( static_cast<SwTextFrame*>(mrpFrame)->GetFollow() ); + static_cast<SwTextFrame*>(mrpFrame)->SetFollow( pNew ); + mrpFrame = pNew; + } + } + } + } + + SwPageFrame* pLastPage = mrpPage; + if( CheckInsertPage() ) + { + CheckFlyCache_( pLastPage ); + if( mrpPrv && mrpPrv->IsTextFrame() && !mrpPrv->isFrameAreaSizeValid() ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*mrpPrv); + aFrm.Height( mrpPrv->GetUpper()->getFramePrintArea().Height() ); + } + + bRet = true; + mrpPrv = nullptr; + mnParagraphCnt = 0; + + if ( mrpActualSection ) + { + //Did the SectionFrame even have a content? If not, we can + //directly put it somewhere else + SwSectionFrame *pSct; + bool bInit = false; + if ( !mrpActualSection->GetSectionFrame()->ContainsContent()) + { + pSct = mrpActualSection->GetSectionFrame(); + pSct->RemoveFromLayout(); + } + else + { + pSct = new SwSectionFrame( + *mrpActualSection->GetSectionFrame(), false ); + mrpActualSection->GetSectionFrame()->SimpleFormat(); + bInit = true; + } + mrpActualSection->SetSectionFrame( pSct ); + pSct->InsertBehind( mrpLay, nullptr ); + + if( bInit ) + { + pSct->Init(); + } + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pSct); + aFrm.Pos() = mrpLay->getFrameArea().Pos(); + aFrm.Pos().AdjustY(1 ); //because of the notifications + } + + mrpLay = pSct; + if ( mrpLay->Lower() && mrpLay->Lower()->IsLayoutFrame() ) + mrpLay = mrpLay->GetNextLayoutLeaf(); + } + } + } while( bLongTab || ( mpImpl && mnIndex < mpImpl->size() && + mpImpl->GetBreakIndex( mnIndex ) == nNodeIndex ) ); + } + mbFirst = false; + return bRet; +} + +namespace { + +struct SdrObjectCompare +{ + bool operator()( const SdrObject* pF1, const SdrObject* pF2 ) const + { + return pF1->GetOrdNum() < pF2->GetOrdNum(); + } +}; + +struct FlyCacheCompare +{ + bool operator()( const SwFlyCache* pC1, const SwFlyCache* pC2 ) const + { + return pC1->nOrdNum < pC2->nOrdNum; + } +}; + +} + +/** + * If a new page is inserted, the last page is analysed. + * If there are text frames with default position, the fly cache + * is checked, if these frames are stored in the cache. + */ +void SwLayHelper::CheckFlyCache_( SwPageFrame* pPage ) +{ + if( !mpImpl || !pPage ) + return; + const size_t nFlyCount = mpImpl->GetFlyCount(); + // Any text frames at the page, fly cache available? + if( !(pPage->GetSortedObjs() && mnFlyIdx < nFlyCount) ) + return; + + SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + sal_uInt16 nPgNum = pPage->GetPhyPageNum(); + + // NOTE: Here we do not use the absolute ordnums but + // relative ordnums for the objects on this page. + + // skip fly frames from pages before the current page + while( mnFlyIdx < nFlyCount && + mpImpl->GetFlyCache(mnFlyIdx).nPageNum < nPgNum ) + ++mnFlyIdx; + + // sort cached objects on this page by ordnum + o3tl::sorted_vector< const SwFlyCache*, FlyCacheCompare > aFlyCacheSet; + size_t nIdx = mnFlyIdx; + + SwFlyCache* pFlyC; + while( nIdx < nFlyCount && + ( pFlyC = &mpImpl->GetFlyCache( nIdx ) )->nPageNum == nPgNum ) + { + aFlyCacheSet.insert( pFlyC ); + ++nIdx; + } + + // sort objects on this page by ordnum + o3tl::sorted_vector< const SdrObject*, SdrObjectCompare > aFlySet; + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if (SwFlyFrame *pFly = pAnchoredObj->DynCastFlyFrame()) // a text frame? + { + if( pFly->GetAnchorFrame() && + !pFly->GetAnchorFrame()->FindFooterOrHeader() ) + { + const SwContact *pC = ::GetUserCall( pAnchoredObj->GetDrawObj() ); + if( pC ) + { + aFlySet.insert( pAnchoredObj->GetDrawObj() ); + } + } + } + } + + if ( aFlyCacheSet.size() != aFlySet.size() ) + return; + + auto aFlySetIt = aFlySet.begin(); + + for ( const SwFlyCache* pFlyCache : aFlyCacheSet ) + { + SwFlyFrame* pFly = const_cast<SwVirtFlyDrawObj*>(static_cast<const SwVirtFlyDrawObj*>(*aFlySetIt))->GetFlyFrame(); + + if ( pFly->getFrameArea().Left() == FAR_AWAY ) + { + // we get the stored information + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFly); + aFrm.Pos().setX( pFlyCache->Left() + pPage->getFrameArea().Left() ); + aFrm.Pos().setY( pFlyCache->Top() + pPage->getFrameArea().Top() ); + + if ( mpImpl->IsUseFlyCache() ) + { + aFrm.Width( pFlyCache->Width() ); + aFrm.Height( pFlyCache->Height() ); + } + } + + ++aFlySetIt; + } +} + +SwLayCacheIoImpl::SwLayCacheIoImpl( SvStream& rStrm, bool bWrtMd ) : + m_pStream( &rStrm ), + m_nFlagRecEnd ( 0 ), + m_nMajorVersion(SW_LAYCACHE_IO_VERSION_MAJOR), + m_nMinorVersion(SW_LAYCACHE_IO_VERSION_MINOR), + m_bWriteMode( bWrtMd ), + m_bError( false ) +{ + if( m_bWriteMode ) + m_pStream->WriteUInt16( m_nMajorVersion ) + .WriteUInt16( m_nMinorVersion ); + + else + m_pStream->ReadUInt16( m_nMajorVersion ) + .ReadUInt16( m_nMinorVersion ); +} + +void SwLayCacheIoImpl::OpenRec( sal_uInt8 cType ) +{ + sal_uInt32 nPos = m_pStream->Tell(); + if( m_bWriteMode ) + { + m_aRecords.emplace_back(cType, nPos ); + m_pStream->WriteUInt32( 0 ); + } + else + { + sal_uInt32 nVal(0); + m_pStream->ReadUInt32( nVal ); + sal_uInt8 cRecTyp = static_cast<sal_uInt8>(nVal); + if (!nVal || cRecTyp != cType || !m_pStream->good()) + { + OSL_ENSURE( nVal, "OpenRec: Record-Header is 0" ); + OSL_ENSURE( cRecTyp == cType, "OpenRec: Wrong Record Type" ); + m_aRecords.emplace_back(0, m_pStream->Tell() ); + m_bError = true; + } + else + { + sal_uInt32 nSize = nVal >> 8; + m_aRecords.emplace_back(cRecTyp, nPos+nSize ); + } + } +} + +// Close record +void SwLayCacheIoImpl::CloseRec() +{ + bool bRes = true; + OSL_ENSURE( !m_aRecords.empty(), "CloseRec: no levels" ); + if( !m_aRecords.empty() ) + { + sal_uInt32 nPos = m_pStream->Tell(); + if( m_bWriteMode ) + { + sal_uInt32 nBgn = m_aRecords.back().size; + m_pStream->Seek( nBgn ); + sal_uInt32 nSize = nPos - nBgn; + sal_uInt32 nVal = ( nSize << 8 ) | m_aRecords.back().type; + m_pStream->WriteUInt32( nVal ); + m_pStream->Seek( nPos ); + if( m_pStream->GetError() != ERRCODE_NONE ) + bRes = false; + } + else + { + sal_uInt32 n = m_aRecords.back().size; + OSL_ENSURE( n >= nPos, "CloseRec: too much data read" ); + if( n != nPos ) + { + m_pStream->Seek( n ); + if( n < nPos ) + bRes = false; + } + if( m_pStream->GetErrorCode() != ERRCODE_NONE ) + bRes = false; + } + m_aRecords.pop_back(); + } + + if( !bRes ) + m_bError = true; +} + +sal_uInt32 SwLayCacheIoImpl::BytesLeft() +{ + sal_uInt32 n = 0; + if( !m_bError && !m_aRecords.empty() ) + { + sal_uInt32 nEndPos = m_aRecords.back().size; + sal_uInt32 nPos = m_pStream->Tell(); + if( nEndPos > nPos ) + n = nEndPos - nPos; + } + return n; +} + +sal_uInt8 SwLayCacheIoImpl::Peek() +{ + sal_uInt8 c(0); + if( !m_bError ) + { + sal_uInt32 nPos = m_pStream->Tell(); + m_pStream->ReadUChar( c ); + m_pStream->Seek( nPos ); + if( m_pStream->GetErrorCode() != ERRCODE_NONE ) + { + c = 0; + m_bError = true; + } + } + return c; +} + +void SwLayCacheIoImpl::SkipRec() +{ + sal_uInt8 c = Peek(); + OpenRec( c ); + m_pStream->Seek( m_aRecords.back().size ); + CloseRec(); +} + +sal_uInt8 SwLayCacheIoImpl::OpenFlagRec() +{ + OSL_ENSURE( !m_bWriteMode, "OpenFlagRec illegal in write mode" ); + sal_uInt8 cFlags(0); + m_pStream->ReadUChar( cFlags ); + m_nFlagRecEnd = m_pStream->Tell() + ( cFlags & 0x0F ); + return (cFlags >> 4); +} + +void SwLayCacheIoImpl::OpenFlagRec( sal_uInt8 nFlags, sal_uInt8 nLen ) +{ + OSL_ENSURE( m_bWriteMode, "OpenFlagRec illegal in read mode" ); + OSL_ENSURE( (nFlags & 0xF0) == 0, "illegal flags set" ); + OSL_ENSURE( nLen < 16, "wrong flag record length" ); + sal_uInt8 cFlags = (nFlags << 4) + nLen; + m_pStream->WriteUChar( cFlags ); + m_nFlagRecEnd = m_pStream->Tell() + nLen; +} + +void SwLayCacheIoImpl::CloseFlagRec() +{ + if( m_bWriteMode ) + { + OSL_ENSURE( m_pStream->Tell() == m_nFlagRecEnd, "Wrong amount of data written" ); + } + else + { + OSL_ENSURE( m_pStream->Tell() <= m_nFlagRecEnd, "Too many data read" ); + if( m_pStream->Tell() != m_nFlagRecEnd ) + m_pStream->Seek( m_nFlagRecEnd ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/layhelp.hxx b/sw/source/core/layout/layhelp.hxx new file mode 100644 index 000000000..17c5cc324 --- /dev/null +++ b/sw/source/core/layout/layhelp.hxx @@ -0,0 +1,224 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SW_SOURCE_CORE_LAYOUT_LAYHELP_HXX +#define INCLUDED_SW_SOURCE_CORE_LAYOUT_LAYHELP_HXX + +#include <swrect.hxx> +#include <nodeoffset.hxx> + +#include <tools/solar.h> + +#include <memory> +#include <vector> +#include <deque> + +class SwDoc; +class SwFrame; +class SwLayoutFrame; +class SwPageFrame; +class SwSectionFrame; +class SwSectionNode; +class SvStream; + +/* + * Contains the page break information and the text frame positions + * of the document (after loading) + * and is used inside the constructor of the layout rootframe to + * insert content and text frames at the right pages. + * For every page of the main text (body content, no footnotes, text frames etc.) + * we have the nodeindex of the first content at the page, + * the type of content ( table or paragraph ) + * and if it's not the first part of the table/paragraph, + * the row/character-offset inside the table/paragraph. + * The text frame positions are stored in the SwPageFlyCache array. + */ + +class SwFlyCache; +typedef std::vector<SwFlyCache> SwPageFlyCache; + +class SwLayCacheImpl +{ + std::vector<SwNodeOffset> mIndices; + /// either a textframe character offset, or a row index inside a table + std::deque<sal_Int32> m_aOffset; + std::vector<sal_uInt16> m_aType; + SwPageFlyCache m_FlyCache; + bool m_bUseFlyCache; + void Insert( sal_uInt16 nType, SwNodeOffset nIndex, sal_Int32 nOffset ); + +public: + inline SwLayCacheImpl(); + + size_t size() const { return mIndices.size(); } + + bool Read( SvStream& rStream ); + + SwNodeOffset GetBreakIndex( size_t nIdx ) const { return mIndices[ nIdx ]; } + sal_Int32 GetBreakOfst( size_t nIdx ) const { return m_aOffset[ nIdx ]; } + sal_uInt16 GetBreakType( size_t nIdx ) const { return m_aType[ nIdx ]; } + + inline size_t GetFlyCount() const; + inline SwFlyCache& GetFlyCache( size_t nIdx ); + + bool IsUseFlyCache() const { return m_bUseFlyCache; } +}; + +// Helps to create the sectionframes during the InsertCnt_-function +// by controlling nested sections. +class SwActualSection +{ + SwActualSection *m_pUpper; + SwSectionFrame *m_pSectFrame; + SwSectionNode *m_pSectNode; +public: + SwActualSection( SwActualSection *pUpper, + SwSectionFrame *pSect, + SwSectionNode *pNd ); + + SwSectionFrame *GetSectionFrame() { return m_pSectFrame; } + void SetSectionFrame( SwSectionFrame *p ) { m_pSectFrame = p; } + SwSectionNode *GetSectionNode() { return m_pSectNode;} + void SetUpper(SwActualSection *p) { m_pUpper = p; } + SwActualSection *GetUpper() { return m_pUpper; } +}; + +/// Helps during the InsertCnt_ function to create new pages. +/// If there's a layout cache available, this information is used. +class SwLayHelper +{ + SwFrame* &mrpFrame; + SwFrame* &mrpPrv; + SwPageFrame* &mrpPage; + SwLayoutFrame* &mrpLay; + std::unique_ptr<SwActualSection> &mrpActualSection; + bool mbBreakAfter; + SwDoc* mpDoc; + SwLayCacheImpl* mpImpl; + sal_uLong mnMaxParaPerPage; + sal_uLong mnParagraphCnt; + SwNodeOffset mnStartOfContent; + size_t mnIndex; ///< the index in the page break array + size_t mnFlyIdx; ///< the index in the fly cache array + bool mbFirst : 1; + void CheckFlyCache_( SwPageFrame* pPage ); +public: + SwLayHelper( SwDoc *pD, SwFrame* &rpF, SwFrame* &rpP, SwPageFrame* &rpPg, + SwLayoutFrame* &rpL, std::unique_ptr<SwActualSection> &rpA, + SwNodeOffset nNodeIndex, bool bCache ); + ~SwLayHelper(); + sal_uLong CalcPageCount(); + bool CheckInsert( SwNodeOffset nNodeIndex ); + + bool CheckInsertPage(); + + /// Look for fresh text frames at this (new) page and set them to the right + /// position, if they are in the fly cache. + void CheckFlyCache( SwPageFrame* pPage ) + { if( mpImpl && mnFlyIdx < mpImpl->GetFlyCount() ) CheckFlyCache_( pPage ); } +}; + +// Contains the data structures that are required to read and write a layout cache. +#define SW_LAYCACHE_IO_REC_PAGES 'p' +#define SW_LAYCACHE_IO_REC_PARA 'P' +#define SW_LAYCACHE_IO_REC_TABLE 'T' +#define SW_LAYCACHE_IO_REC_FLY 'F' + +#define SW_LAYCACHE_IO_VERSION_MAJOR 1 +#define SW_LAYCACHE_IO_VERSION_MINOR 1 + +class SwLayCacheIoImpl +{ +private: + struct RecTypeSize { + sal_uInt8 type; + sal_uLong size; + RecTypeSize(sal_uInt8 typ, sal_uLong siz) : type(typ), size(siz) {} + }; + std::vector<RecTypeSize> m_aRecords; + + SvStream *m_pStream; + + sal_uLong m_nFlagRecEnd; + + sal_uInt16 m_nMajorVersion; + sal_uInt16 m_nMinorVersion; + + bool m_bWriteMode : 1; + bool m_bError : 1; + +public: + SwLayCacheIoImpl( SvStream& rStrm, bool bWrtMd ); + + /// Get input or output stream + SvStream& GetStream() const { return *m_pStream; } + + /// Open a record of type "nType" + void OpenRec( sal_uInt8 nType ); + + /// Close a record. This skips any unread data that + /// remains in the record. + void CloseRec(); + + /// Return the number of bytes contained in the current record that + /// haven't been read by now. + sal_uInt32 BytesLeft(); + + /// Return the current record's type + sal_uInt8 Peek(); + + /// Skip the current record + void SkipRec(); + + /// Open a flag record for reading. The uppermost four bits are flags, + /// while the lowermost are the flag record's size. Flag records cannot + /// be nested. + sal_uInt8 OpenFlagRec(); + + /// Open flag record for writing; + void OpenFlagRec( sal_uInt8 nFlags, sal_uInt8 nLen ); + + /// Close a flag record. Any bytes left are skipped. + void CloseFlagRec(); + + bool HasError() const { return m_bError; } + + sal_uInt16 GetMajorVersion() const { return m_nMajorVersion; } + sal_uInt16 GetMinorVersion() const { return m_nMinorVersion; } +}; + +// Stored information about text frames: +class SwFlyCache : public SwRect // position and size +{ +public: + sal_uLong nOrdNum; ///< Id to recognize text frames + sal_uInt16 nPageNum; ///< page number + SwFlyCache( sal_uInt16 nP, sal_uLong nO, tools::Long nXL, tools::Long nYL, tools::Long nWL, tools::Long nHL ) : + SwRect( nXL, nYL, nWL, nHL ), nOrdNum( nO ), nPageNum( nP ){} +}; + +SwLayCacheImpl::SwLayCacheImpl() : m_bUseFlyCache(false) {} + +size_t SwLayCacheImpl::GetFlyCount() const { return m_FlyCache.size(); } + +SwFlyCache& SwLayCacheImpl::GetFlyCache( size_t nIdx ) { return m_FlyCache[ nIdx ]; } + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/layouter.cxx b/sw/source/core/layout/layouter.cxx new file mode 100644 index 000000000..57f2ee0ae --- /dev/null +++ b/sw/source/core/layout/layouter.cxx @@ -0,0 +1,483 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <layouter.hxx> +#include <doc.hxx> +#include <sectfrm.hxx> +#include <pagefrm.hxx> +#include <ftnfrm.hxx> +#include <txtfrm.hxx> +#include <IDocumentLayoutAccess.hxx> + +#include <movedfwdfrmsbyobjpos.hxx> +#include "objstmpconsiderwrapinfl.hxx" +#include <osl/diagnose.h> + +#define LOOP_DETECT 250 + +class SwLooping +{ + sal_uInt16 mnMinPage; + sal_uInt16 mnMaxPage; + sal_uInt16 mnCount; + sal_uInt16 mnLoopControlStage; +public: + explicit SwLooping( SwPageFrame const * pPage ); + void Control( SwPageFrame* pPage ); + void Drastic( SwFrame* pFrame ); + bool IsLoopingLouieLight() const { return mnCount > LOOP_DETECT - 30; }; +}; + +class SwEndnoter +{ + SwLayouter* m_pMaster; + SwSectionFrame* m_pSect; + std::unique_ptr<SwFootnoteFrames> m_pEndArr; +public: + explicit SwEndnoter( SwLayouter* pLay ) + : m_pMaster( pLay ), m_pSect( nullptr ) {} + void CollectEndnotes( SwSectionFrame* pSct ); + void CollectEndnote( SwFootnoteFrame* pFootnote ); + const SwSectionFrame* GetSect() const { return m_pSect; } + void InsertEndnotes(); + bool HasEndnotes() const { return m_pEndArr && !m_pEndArr->empty(); } +}; + +void SwEndnoter::CollectEndnotes( SwSectionFrame* pSct ) +{ + OSL_ENSURE( pSct, "CollectEndnotes: Which section?" ); + if( !m_pSect ) + m_pSect = pSct; + else if( pSct != m_pSect ) + return; + m_pSect->CollectEndnotes( m_pMaster ); +} + +void SwEndnoter::CollectEndnote( SwFootnoteFrame* pFootnote ) +{ + if( m_pEndArr && m_pEndArr->end() != std::find( m_pEndArr->begin(), m_pEndArr->end(), pFootnote ) ) + return; + + if( pFootnote->GetUpper() ) + { + // pFootnote is the master, he incorporates its follows + SwFootnoteFrame *pNxt = pFootnote->GetFollow(); + while ( pNxt ) + { + SwFrame *pCnt = pNxt->ContainsAny(); + if ( pCnt ) + { + do + { SwFrame *pNxtCnt = pCnt->GetNext(); + pCnt->Cut(); + pCnt->Paste( pFootnote ); + pCnt = pNxtCnt; + } while ( pCnt ); + } + else + { + OSL_ENSURE( pNxt->Lower() && pNxt->Lower()->IsSctFrame(), + "Endnote without content?" ); + pNxt->Cut(); + SwFrame::DestroyFrame(pNxt); + } + pNxt = pFootnote->GetFollow(); + } + if( pFootnote->GetMaster() ) + return; + pFootnote->Cut(); + } + else if( m_pEndArr ) + { + for (SwFootnoteFrame* pEndFootnote : *m_pEndArr) + { + if( pEndFootnote->GetAttr() == pFootnote->GetAttr() ) + { + SwFrame::DestroyFrame(pFootnote); + return; + } + } + } + if( !m_pEndArr ) + m_pEndArr.reset( new SwFootnoteFrames ); // deleted from the SwLayouter + m_pEndArr->push_back( pFootnote ); +} + +void SwEndnoter::InsertEndnotes() +{ + if( !m_pSect ) + return; + if( !m_pEndArr || m_pEndArr->empty() ) + { + m_pSect = nullptr; + return; + } + OSL_ENSURE( m_pSect->Lower() && m_pSect->Lower()->IsFootnoteBossFrame(), + "InsertEndnotes: Where's my column?" ); + SwFrame* pRef = m_pSect->FindLastContent( SwFindMode::MyLast ); + SwFootnoteBossFrame *pBoss = pRef ? pRef->FindFootnoteBossFrame() + : static_cast<SwFootnoteBossFrame*>(m_pSect->Lower()); + pBoss->MoveFootnotes_( *m_pEndArr ); + m_pEndArr.reset(); + m_pSect = nullptr; +} + +SwLooping::SwLooping( SwPageFrame const * pPage ) +{ + OSL_ENSURE( pPage, "Where's my page?" ); + mnMinPage = pPage->GetPhyPageNum(); + mnMaxPage = mnMinPage; + mnCount = 0; + mnLoopControlStage = 0; +} + +void SwLooping::Drastic( SwFrame* pFrame ) +{ + while( pFrame ) + { + pFrame->ValidateThisAndAllLowers( mnLoopControlStage ); + pFrame = pFrame->GetNext(); + } +} + +void SwLooping::Control( SwPageFrame* pPage ) +{ + if( !pPage ) + return; + const sal_uInt16 nNew = pPage->GetPhyPageNum(); + if (nNew > mnMaxPage) + mnMaxPage = nNew; + if (nNew < mnMinPage) + { + mnMinPage = nNew; + mnMaxPage = nNew; + mnCount = 0; + mnLoopControlStage = 0; + } + else if (nNew > mnMinPage + 2) + { + mnMinPage = nNew - 2; + mnMaxPage = nNew; + mnCount = 0; + mnLoopControlStage = 0; + } + else if (++mnCount > LOOP_DETECT) + { +#if OSL_DEBUG_LEVEL > 1 + static bool bNoLouie = false; + if( bNoLouie ) + return; + + // FME 2007-08-30 #i81146# new loop control + OSL_ENSURE( 0 != mnLoopControlStage, "Looping Louie: Stage 1!" ); + OSL_ENSURE( 1 != mnLoopControlStage, "Looping Louie: Stage 2!!" ); + OSL_ENSURE( 2 > mnLoopControlStage, "Looping Louie: Stage 3!!!" ); +#endif + + Drastic( pPage->Lower() ); + if (nNew > mnMinPage && pPage->GetPrev()) + Drastic( static_cast<SwPageFrame*>(pPage->GetPrev())->Lower() ); + if (nNew < mnMaxPage && pPage->GetNext()) + Drastic( static_cast<SwPageFrame*>(pPage->GetNext())->Lower() ); + + ++mnLoopControlStage; + mnCount = 0; + } +} + +SwLayouter::SwLayouter() +{ +} + +SwLayouter::~SwLayouter() +{ +} + +void SwLayouter::CollectEndnotes_( SwSectionFrame* pSect ) +{ + if( !mpEndnoter ) + mpEndnoter.reset(new SwEndnoter( this )); + mpEndnoter->CollectEndnotes( pSect ); +} + +bool SwLayouter::HasEndnotes() const +{ + return mpEndnoter->HasEndnotes(); +} + +void SwLayouter::CollectEndnote( SwFootnoteFrame* pFootnote ) +{ + mpEndnoter->CollectEndnote( pFootnote ); +} + +void SwLayouter::InsertEndnotes( SwSectionFrame const * pSect ) +{ + if( !mpEndnoter || mpEndnoter->GetSect() != pSect ) + return; + mpEndnoter->InsertEndnotes(); +} + +void SwLayouter::LoopControl( SwPageFrame* pPage ) +{ + OSL_ENSURE( mpLooping, "Looping: Lost control" ); + mpLooping->Control( pPage ); +} + +void SwLayouter::LoopingLouieLight( const SwDoc& rDoc, const SwTextFrame& rFrame ) +{ + if ( mpLooping && mpLooping->IsLoopingLouieLight() ) + { +#if OSL_DEBUG_LEVEL > 1 + OSL_FAIL( "Looping Louie (Light): Fixating fractious frame" ); +#endif + SwLayouter::InsertMovedFwdFrame( rDoc, rFrame, rFrame.FindPageFrame()->GetPhyPageNum() ); + } +} + +bool SwLayouter::StartLooping( SwPageFrame const * pPage ) +{ + if( mpLooping ) + return false; + mpLooping.reset(new SwLooping( pPage )); + return true; +} + +void SwLayouter::EndLoopControl() +{ + mpLooping.reset(); +} + +void SwLayouter::CollectEndnotes( SwDoc* pDoc, SwSectionFrame* pSect ) +{ + assert(pDoc && "No doc, no fun"); + if( !pDoc->getIDocumentLayoutAccess().GetLayouter() ) + pDoc->getIDocumentLayoutAccess().SetLayouter( new SwLayouter() ); + pDoc->getIDocumentLayoutAccess().GetLayouter()->CollectEndnotes_( pSect ); +} + +bool SwLayouter::Collecting( SwDoc* pDoc, SwSectionFrame const * pSect, SwFootnoteFrame* pFootnote ) +{ + if( !pDoc->getIDocumentLayoutAccess().GetLayouter() ) + return false; + SwLayouter *pLayouter = pDoc->getIDocumentLayoutAccess().GetLayouter(); + if( pLayouter->mpEndnoter && pLayouter->mpEndnoter->GetSect() && pSect && + ( pLayouter->mpEndnoter->GetSect()->IsAnFollow( pSect ) || + pSect->IsAnFollow( pLayouter->mpEndnoter->GetSect() ) ) ) + { + if( pFootnote ) + pLayouter->CollectEndnote( pFootnote ); + return true; + } + return false; +} + +bool SwLayouter::StartLoopControl( SwDoc* pDoc, SwPageFrame const *pPage ) +{ + OSL_ENSURE( pDoc, "No doc, no fun" ); + if( !pDoc->getIDocumentLayoutAccess().GetLayouter() ) + pDoc->getIDocumentLayoutAccess().SetLayouter( new SwLayouter() ); + return !pDoc->getIDocumentLayoutAccess().GetLayouter()->mpLooping && + pDoc->getIDocumentLayoutAccess().GetLayouter()->StartLooping( pPage ); +} + +// #i28701# +// methods to manage text frames, which are moved forward by the positioning +// of its anchored objects +void SwLayouter::ClearMovedFwdFrames( const SwDoc& _rDoc ) +{ + if ( _rDoc.getIDocumentLayoutAccess().GetLayouter() && + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames ) + { + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames->Clear(); + } +} + +void SwLayouter::InsertMovedFwdFrame( const SwDoc& _rDoc, + const SwTextFrame& _rMovedFwdFrameByObjPos, + const sal_uInt32 _nToPageNum ) +{ + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter() ) + { + const_cast<SwDoc&>(_rDoc).getIDocumentLayoutAccess().SetLayouter( new SwLayouter() ); + } + + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames ) + { + const_cast<SwDoc&>(_rDoc).getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames.reset( + new SwMovedFwdFramesByObjPos()); + } + + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames->Insert( _rMovedFwdFrameByObjPos, + _nToPageNum ); +} + +// #i40155# +void SwLayouter::RemoveMovedFwdFrame( const SwDoc& _rDoc, + const SwTextFrame& _rTextFrame ) +{ + sal_uInt32 nDummy; + if ( SwLayouter::FrameMovedFwdByObjPos( _rDoc, _rTextFrame, nDummy ) ) + { + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames->Remove( _rTextFrame ); + } +} + +bool SwLayouter::FrameMovedFwdByObjPos( const SwDoc& _rDoc, + const SwTextFrame& _rTextFrame, + sal_uInt32& _ornToPageNum ) +{ + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter() ) + { + _ornToPageNum = 0; + return false; + } + else if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames ) + { + _ornToPageNum = 0; + return false; + } + else + { + return _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames-> + FrameMovedFwdByObjPos( _rTextFrame, _ornToPageNum ); + } +} + +// #i26945# +bool SwLayouter::DoesRowContainMovedFwdFrame( const SwDoc& _rDoc, + const SwRowFrame& _rRowFrame ) +{ + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter() ) + { + return false; + } + else if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter()->mpMovedFwdFrames ) + { + return false; + } + else + { + return _rDoc.getIDocumentLayoutAccess().GetLayouter()-> + mpMovedFwdFrames->DoesRowContainMovedFwdFrame( _rRowFrame ); + } +} + +// #i35911# +void SwLayouter::ClearObjsTmpConsiderWrapInfluence( const SwDoc& _rDoc ) +{ + if ( _rDoc.getIDocumentLayoutAccess().GetLayouter() && + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpObjsTmpConsiderWrapInfl ) + { + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpObjsTmpConsiderWrapInfl->Clear(); + } +} + +void SwLayouter::InsertObjForTmpConsiderWrapInfluence( + const SwDoc& _rDoc, + SwAnchoredObject& _rAnchoredObj ) +{ + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter() ) + { + const_cast<SwDoc&>(_rDoc).getIDocumentLayoutAccess().SetLayouter( new SwLayouter() ); + } + + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter()->mpObjsTmpConsiderWrapInfl ) + { + const_cast<SwDoc&>(_rDoc).getIDocumentLayoutAccess().GetLayouter()->mpObjsTmpConsiderWrapInfl.reset( + new SwObjsMarkedAsTmpConsiderWrapInfluence()); + } + + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpObjsTmpConsiderWrapInfl->Insert( _rAnchoredObj ); +} + +void SwLayouter::RemoveObjForTmpConsiderWrapInfluence( + const SwDoc& _rDoc, + SwAnchoredObject& _rAnchoredObj ) +{ + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter() ) + return; + + if ( !_rDoc.getIDocumentLayoutAccess().GetLayouter()->mpObjsTmpConsiderWrapInfl ) + return; + + _rDoc.getIDocumentLayoutAccess().GetLayouter()->mpObjsTmpConsiderWrapInfl->Remove( _rAnchoredObj ); +} + + +void LOOPING_LOUIE_LIGHT( bool bCondition, const SwTextFrame& rTextFrame ) +{ + if ( bCondition ) + { + const SwDoc& rDoc = *rTextFrame.GetAttrSet()->GetDoc(); + if ( rDoc.getIDocumentLayoutAccess().GetLayouter() ) + { + const_cast<SwDoc&>(rDoc).getIDocumentLayoutAccess().GetLayouter()->LoopingLouieLight( rDoc, rTextFrame ); + } + } +} + +// #i65250# +bool SwLayouter::MoveBwdSuppressed( const SwDoc& p_rDoc, + const SwFlowFrame& p_rFlowFrame, + const SwLayoutFrame& p_rNewUpperFrame ) +{ + bool bMoveBwdSuppressed( false ); + + if ( !p_rDoc.getIDocumentLayoutAccess().GetLayouter() ) + { + const_cast<SwDoc&>(p_rDoc).getIDocumentLayoutAccess().SetLayouter( new SwLayouter() ); + } + + // create hash map key + tMoveBwdLayoutInfoKey aMoveBwdLayoutInfo; + aMoveBwdLayoutInfo.mnFrameId = p_rFlowFrame.GetFrame().GetFrameId(); + aMoveBwdLayoutInfo.mnNewUpperPosX = p_rNewUpperFrame.getFrameArea().Pos().X(); + aMoveBwdLayoutInfo.mnNewUpperPosY = p_rNewUpperFrame.getFrameArea().Pos().Y(); + aMoveBwdLayoutInfo.mnNewUpperWidth = p_rNewUpperFrame.getFrameArea().Width(); + aMoveBwdLayoutInfo.mnNewUpperHeight = p_rNewUpperFrame.getFrameArea().Height(); + SwRectFnSet aRectFnSet(&p_rNewUpperFrame); + const SwFrame* pLastLower( p_rNewUpperFrame.Lower() ); + while ( pLastLower && pLastLower->GetNext() ) + { + pLastLower = pLastLower->GetNext(); + } + aMoveBwdLayoutInfo.mnFreeSpaceInNewUpper = + pLastLower + ? aRectFnSet.BottomDist( pLastLower->getFrameArea(), aRectFnSet.GetPrtBottom(p_rNewUpperFrame) ) + : aRectFnSet.GetHeight(p_rNewUpperFrame.getFrameArea()); + + // check for moving backward suppress threshold + const sal_uInt16 cMoveBwdCountSuppressThreshold = 20; + if ( ++const_cast<SwDoc&>(p_rDoc).getIDocumentLayoutAccess().GetLayouter()->maMoveBwdLayoutInfo[ aMoveBwdLayoutInfo ] > + cMoveBwdCountSuppressThreshold ) + { + bMoveBwdSuppressed = true; + } + + return bMoveBwdSuppressed; +} + +void SwLayouter::ClearMoveBwdLayoutInfo( const SwDoc& _rDoc ) +{ + if ( _rDoc.getIDocumentLayoutAccess().GetLayouter() ) + const_cast<SwDoc&>(_rDoc).getIDocumentLayoutAccess().GetLayouter()->maMoveBwdLayoutInfo.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/legacyitem.cxx b/sw/source/core/layout/legacyitem.cxx new file mode 100644 index 000000000..281b71d83 --- /dev/null +++ b/sw/source/core/layout/legacyitem.cxx @@ -0,0 +1,78 @@ +/* -*- 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 <legacyitem.hxx> +#include <tools/stream.hxx> +#include <sal/log.hxx> +#include <fmtornt.hxx> + +namespace legacy::SwFormatVert +{ + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SwFormatVertOrient& rItem, SvStream& rStrm, sal_uInt16 nVersionAbusedAsSize) + { + SwTwips yPos(0); + sal_Int16 orient(0); + sal_Int16 relation(0); + + switch (nVersionAbusedAsSize) + { + // compatibility hack for Table Auto Format: SwTwips is "long" :( + // (this means that the file format is platform dependent) + case 14: + { + sal_Int64 n(0); + rStrm.ReadInt64(n); + yPos = n; + break; + } + case 10: + { + sal_Int32 n(0); + rStrm.ReadInt32(n); + yPos = n; + break; + } + default: + SAL_WARN("sw.core", "SwFormatVertOrient::Create: unknown size"); + } + + rStrm.ReadInt16( orient ).ReadInt16( relation ); + + rItem.SetPos(yPos); + rItem.SetVertOrient(orient); + rItem.SetRelationOrient(relation); + } + + SvStream& Store(const SwFormatVertOrient& rItem, SvStream& rStrm, sal_uInt16) + { + if constexpr(sizeof(rItem.GetPos()) >= 8) + rStrm.WriteInt64(rItem.GetPos()); + else + rStrm.WriteInt32(rItem.GetPos()); + rStrm.WriteInt16(rItem.GetVertOrient()).WriteInt16(rItem.GetRelationOrient()); + return rStrm; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/movedfwdfrmsbyobjpos.cxx b/sw/source/core/layout/movedfwdfrmsbyobjpos.cxx new file mode 100644 index 000000000..7aba4b74a --- /dev/null +++ b/sw/source/core/layout/movedfwdfrmsbyobjpos.cxx @@ -0,0 +1,90 @@ +/* -*- 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 <movedfwdfrmsbyobjpos.hxx> +#include <txtfrm.hxx> +#include <rowfrm.hxx> +#include <pagefrm.hxx> +#include <calbck.hxx> +#include <ndtxt.hxx> + +SwMovedFwdFramesByObjPos::SwMovedFwdFramesByObjPos() +{ +} + +SwMovedFwdFramesByObjPos::~SwMovedFwdFramesByObjPos() +{ + Clear(); +} + +void SwMovedFwdFramesByObjPos::Insert( const SwTextFrame& _rMovedFwdFrameByObjPos, + const sal_uInt32 _nToPageNum ) +{ + maMovedFwdFrames.emplace(_rMovedFwdFrameByObjPos.GetTextNodeFirst(), _nToPageNum); +} + +void SwMovedFwdFramesByObjPos::Remove( const SwTextFrame& _rTextFrame ) +{ + maMovedFwdFrames.erase(_rTextFrame.GetTextNodeFirst()); +} + +bool SwMovedFwdFramesByObjPos::FrameMovedFwdByObjPos( const SwTextFrame& _rTextFrame, + sal_uInt32& _ornToPageNum ) const +{ + // sw_redlinehide: assumption: this wants to uniquely identify all + // SwTextFrame belonging to the same paragraph, so just use first one as key + auto aIter = maMovedFwdFrames.find( _rTextFrame.GetTextNodeFirst() ); + if ( maMovedFwdFrames.end() != aIter ) + { + _ornToPageNum = (*aIter).second; + return true; + } + + return false; +} + +// #i26945# +bool SwMovedFwdFramesByObjPos::DoesRowContainMovedFwdFrame( const SwRowFrame& _rRowFrame ) const +{ + bool bDoesRowContainMovedFwdFrame( false ); + + const sal_uInt32 nPageNumOfRow = _rRowFrame.FindPageFrame()->GetPhyPageNum(); + + for ( const auto & rEntry : maMovedFwdFrames ) + { + if ( rEntry.second >= nPageNumOfRow ) + { + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aFrameIter(*rEntry.first); + for( SwTextFrame* pTextFrame = aFrameIter.First(); pTextFrame; pTextFrame = aFrameIter.Next() ) + { + // #115759# - assure that found text frame + // is the first one. + if ( _rRowFrame.IsAnLower( pTextFrame ) && !pTextFrame->GetIndPrev() ) + { + bDoesRowContainMovedFwdFrame = true; + break; + } + } + } + } + + return bDoesRowContainMovedFwdFrame; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/newfrm.cxx b/sw/source/core/layout/newfrm.cxx new file mode 100644 index 000000000..c09c55779 --- /dev/null +++ b/sw/source/core/layout/newfrm.cxx @@ -0,0 +1,618 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/safeint.hxx> +#include <svx/svdpage.hxx> +#include <osl/diagnose.h> +#include <drawdoc.hxx> +#include <fmtpdsc.hxx> +#include <swtable.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <dflyobj.hxx> +#include <frmtool.hxx> +#include "virtoutp.hxx" +#include <notxtfrm.hxx> +#include <pagedesc.hxx> +#include <viewimp.hxx> +#include <hints.hxx> +#include <viewopt.hxx> +#include <set> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <DocumentLayoutManager.hxx> +#include <DocumentRedlineManager.hxx> +#include <ndindex.hxx> + +SwLayVout *SwRootFrame::s_pVout = nullptr; +bool SwRootFrame::s_isInPaint = false; +bool SwRootFrame::s_isNoVirDev = false; + +SwCache *SwFrame::spCache = nullptr; + +static tools::Long FirstMinusSecond( tools::Long nFirst, tools::Long nSecond ) + { return nFirst - nSecond; } +static tools::Long SecondMinusFirst( tools::Long nFirst, tools::Long nSecond ) + { return nSecond - nFirst; } +static tools::Long SwIncrement( tools::Long nA, tools::Long nAdd ) + { return nA + nAdd; } +static tools::Long SwDecrement( tools::Long nA, tools::Long nSub ) + { return nA - nSub; } + +static SwRectFnCollection aHorizontal = { + /*.fnGetTop =*/&SwRect::Top_, + /*.fnGetBottom =*/&SwRect::Bottom_, + /*.fnGetLeft =*/&SwRect::Left_, + /*.fnGetRight =*/&SwRect::Right_, + /*.fnGetWidth =*/&SwRect::Width_, + /*.fnGetHeight =*/&SwRect::Height_, + /*.fnGetPos =*/&SwRect::TopLeft, + /*.fnGetSize =*/&SwRect::Size_, + + /*.fnSetTop =*/&SwRect::Top_, + /*.fnSetBottom =*/&SwRect::Bottom_, + /*.fnSetLeft =*/&SwRect::Left_, + /*.fnSetRight =*/&SwRect::Right_, + /*.fnSetWidth =*/&SwRect::Width_, + /*.fnSetHeight =*/&SwRect::Height_, + + /*.fnSubTop =*/&SwRect::SubTop, + /*.fnAddBottom =*/&SwRect::AddBottom, + /*.fnSubLeft =*/&SwRect::SubLeft, + /*.fnAddRight =*/&SwRect::AddRight, + /*.fnAddWidth =*/&SwRect::AddWidth, + /*.fnAddHeight =*/&SwRect::AddHeight, + + /*.fnSetPosX =*/&SwRect::SetPosX, + /*.fnSetPosY =*/&SwRect::SetPosY, + + /*.fnGetTopMargin =*/&SwFrame::GetTopMargin, + /*.fnGetBottomMargin =*/&SwFrame::GetBottomMargin, + /*.fnGetLeftMargin =*/&SwFrame::GetLeftMargin, + /*.fnGetRightMargin =*/&SwFrame::GetRightMargin, + /*.fnSetXMargins =*/&SwFrame::SetLeftRightMargins, + /*.fnSetYMargins =*/&SwFrame::SetTopBottomMargins, + /*.fnGetPrtTop =*/&SwFrame::GetPrtTop, + /*.fnGetPrtBottom =*/&SwFrame::GetPrtBottom, + /*.fnGetPrtLeft =*/&SwFrame::GetPrtLeft, + /*.fnGetPrtRight =*/&SwFrame::GetPrtRight, + /*.fnTopDist =*/&SwRect::GetTopDistance, + /*.fnBottomDist =*/&SwRect::GetBottomDistance, + /*.fnLeftDist =*/&SwRect::GetLeftDistance, + /*.fnRightDist =*/&SwRect::GetRightDistance, + /*.fnSetLimit =*/&SwFrame::SetMaxBottom, + /*.fnOverStep =*/&SwRect::OverStepBottom, + + /*.fnSetPos =*/&SwRect::SetUpperLeftCorner, + /*.fnMakePos =*/&SwFrame::MakeBelowPos, + /*.fnXDiff =*/&FirstMinusSecond, + /*.fnYDiff =*/&FirstMinusSecond, + /*.fnXInc =*/&SwIncrement, + /*.fnYInc =*/&o3tl::saturating_add<tools::Long>, + + /*.fnSetLeftAndWidth =*/&SwRect::SetLeftAndWidth, + /*.fnSetTopAndHeight =*/&SwRect::SetTopAndHeight +}; + +static SwRectFnCollection aVertical = { + /*.fnGetTop =*/&SwRect::Right_, + /*.fnGetBottom =*/&SwRect::Left_, + /*.fnGetLeft =*/&SwRect::Top_, + /*.fnGetRight =*/&SwRect::Bottom_, + /*.fnGetWidth =*/&SwRect::Height_, + /*.fnGetHeight =*/&SwRect::Width_, + /*.fnGetPos =*/&SwRect::TopRight, + /*.fnGetSize =*/&SwRect::SwappedSize, + + /*.fnSetTop =*/&SwRect::Right_, + /*.fnSetBottom =*/&SwRect::Left_, + /*.fnSetLeft =*/&SwRect::Top_, + /*.fnSetRight =*/&SwRect::Bottom_, + /*.fnSetWidth =*/&SwRect::Height_, + /*.fnSetHeight =*/&SwRect::Width_, + + /*.fnSubTop =*/&SwRect::AddRight, + /*.fnAddBottom =*/&SwRect::SubLeft, + /*.fnSubLeft =*/&SwRect::SubTop, + /*.fnAddRight =*/&SwRect::AddBottom, + /*.fnAddWidth =*/&SwRect::AddHeight, + /*.fnAddHeight =*/&SwRect::AddWidth, + + /*.fnSetPosX =*/&SwRect::SetPosY, + /*.fnSetPosY =*/&SwRect::SetPosX, + + /*.fnGetTopMargin =*/&SwFrame::GetRightMargin, + /*.fnGetBottomMargin =*/&SwFrame::GetLeftMargin, + /*.fnGetLeftMargin =*/&SwFrame::GetTopMargin, + /*.fnGetRightMargin =*/&SwFrame::GetBottomMargin, + /*.fnSetXMargins =*/&SwFrame::SetTopBottomMargins, + /*.fnSetYMargins =*/&SwFrame::SetRightLeftMargins, + /*.fnGetPrtTop =*/&SwFrame::GetPrtRight, + /*.fnGetPrtBottom =*/&SwFrame::GetPrtLeft, + /*.fnGetPrtLeft =*/&SwFrame::GetPrtTop, + /*.fnGetPrtRight =*/&SwFrame::GetPrtBottom, + /*.fnTopDist =*/&SwRect::GetRightDistance, + /*.fnBottomDist =*/&SwRect::GetLeftDistance, + /*.fnLeftDist =*/&SwRect::GetTopDistance, + /*.fnRightDist =*/&SwRect::GetBottomDistance, + /*.fnSetLimit =*/&SwFrame::SetMinLeft, + /*.fnOverStep =*/&SwRect::OverStepLeft, + + /*.fnSetPos =*/&SwRect::SetUpperRightCorner, + /*.fnMakePos =*/&SwFrame::MakeLeftPos, + /*.fnXDiff =*/&FirstMinusSecond, + /*.fnYDiff =*/&SecondMinusFirst, + /*.fnXInc =*/&SwIncrement, + /*.fnYInc =*/&SwDecrement, + + /*.fnSetLeftAndWidth =*/&SwRect::SetTopAndHeight, + /*.fnSetTopAndHeight =*/&SwRect::SetRightAndWidth +}; + +static SwRectFnCollection aVerticalLeftToRight = { + /*.fnGetTop =*/&SwRect::Left_, + /*.fnGetBottom =*/&SwRect::Right_, + /*.fnGetLeft =*/&SwRect::Top_, + /*.fnGetRight =*/&SwRect::Bottom_, + /*.fnGetWidth =*/&SwRect::Height_, + /*.fnGetHeight =*/&SwRect::Width_, + /*.fnGetPos =*/&SwRect::TopLeft, + /*.fnGetSize =*/&SwRect::SwappedSize, + + /*.fnSetTop =*/&SwRect::Left_, + /*.fnSetBottom =*/&SwRect::Right_, + /*.fnSetLeft =*/&SwRect::Top_, + /*.fnSetRight =*/&SwRect::Bottom_, + /*.fnSetWidth =*/&SwRect::Height_, + /*.fnSetHeight =*/&SwRect::Width_, + + /*.fnSubTop =*/&SwRect::SubLeft, + /*.fnAddBottom =*/&SwRect::AddRight, + /*.fnSubLeft =*/&SwRect::SubTop, + /*.fnAddRight =*/&SwRect::AddBottom, + /*.fnAddWidth =*/&SwRect::AddHeight, + /*.fnAddHeight =*/&SwRect::AddWidth, + + /*.fnSetPosX =*/&SwRect::SetPosY, + /*.fnSetPosY =*/&SwRect::SetPosX, + + /*.fnGetTopMargin =*/&SwFrame::GetLeftMargin, + /*.fnGetBottomMargin =*/&SwFrame::GetRightMargin, + /*.fnGetLeftMargin =*/&SwFrame::GetTopMargin, + /*.fnGetRightMargin =*/&SwFrame::GetBottomMargin, + /*.fnSetXMargins =*/&SwFrame::SetTopBottomMargins, + /*.fnSetYMargins =*/&SwFrame::SetLeftRightMargins, + /*.fnGetPrtTop =*/&SwFrame::GetPrtLeft, + /*.fnGetPrtBottom =*/&SwFrame::GetPrtRight, + /*.fnGetPrtLeft =*/&SwFrame::GetPrtTop, + /*.fnGetPrtRight =*/&SwFrame::GetPrtBottom, + /*.fnTopDist =*/&SwRect::GetLeftDistance, + /*.fnBottomDist =*/&SwRect::GetRightDistance, + /*.fnLeftDist =*/&SwRect::GetTopDistance, + /*.fnRightDist =*/&SwRect::GetBottomDistance, + /*.fnSetLimit =*/&SwFrame::SetMaxRight, + /*.fnOverStep =*/&SwRect::OverStepRight, + + /*.fnSetPos =*/&SwRect::SetUpperLeftCorner, + /*.fnMakePos =*/&SwFrame::MakeRightPos, + /*.fnXDiff =*/&FirstMinusSecond, + /*.fnYDiff =*/&FirstMinusSecond, + /*.fnXInc =*/&SwIncrement, + /*.fnYInc =*/&SwIncrement, + + /*.fnSetLeftAndWidth =*/&SwRect::SetTopAndHeight, + /*.fnSetTopAndHeight =*/&SwRect::SetLeftAndWidth +}; + +/** + * This is the same as horizontal, but rotated counter-clockwise by 90 degrees. + * This means logical top is physical left, bottom is right, left is bottom, + * finally right is top. Values map from logical to physical. + */ +static SwRectFnCollection aVerticalLeftToRightBottomToTop = { + /*.fnGetTop =*/&SwRect::Left_, + /*.fnGetBottom =*/&SwRect::Right_, + /*.fnGetLeft =*/&SwRect::Bottom_, + /*.fnGetRight =*/&SwRect::Top_, + /*.fnGetWidth =*/&SwRect::Height_, + /*.fnGetHeight =*/&SwRect::Width_, + /*.fnGetPos =*/&SwRect::BottomLeft, + /*.fnGetSize =*/&SwRect::SwappedSize, + + /*.fnSetTop =*/&SwRect::Left_, + /*.fnSetBottom =*/&SwRect::Right_, + /*.fnSetLeft =*/&SwRect::Bottom_, + /*.fnSetRight =*/&SwRect::Top_, + /*.fnSetWidth =*/&SwRect::Height_, + /*.fnSetHeight =*/&SwRect::Width_, + + /*.fnSubTop =*/&SwRect::SubLeft, + /*.fnAddBottom =*/&SwRect::AddRight, + /*.fnSubLeft =*/&SwRect::AddBottom, + /*.fnAddRight =*/&SwRect::SubTop, + /*.fnAddWidth =*/&SwRect::AddHeight, + /*.fnAddHeight =*/&SwRect::AddWidth, + + /*.fnSetPosX =*/&SwRect::SetPosY, + /*.fnSetPosY =*/&SwRect::SetPosX, + + /*.fnGetTopMargin =*/&SwFrame::GetLeftMargin, + /*.fnGetBottomMargin =*/&SwFrame::GetRightMargin, + /*.fnGetLeftMargin =*/&SwFrame::GetBottomMargin, + /*.fnGetRightMargin =*/&SwFrame::GetTopMargin, + /*.fnSetXMargins =*/&SwFrame::SetTopBottomMargins, + /*.fnSetYMargins =*/&SwFrame::SetLeftRightMargins, + /*.fnGetPrtTop =*/&SwFrame::GetPrtLeft, + /*.fnGetPrtBottom =*/&SwFrame::GetPrtRight, + /*.fnGetPrtLeft =*/&SwFrame::GetPrtBottom, + /*.fnGetPrtRight =*/&SwFrame::GetPrtTop, + /*.fnTopDist =*/&SwRect::GetLeftDistance, + /*.fnBottomDist =*/&SwRect::GetRightDistance, + /*.fnLeftDist =*/&SwRect::GetBottomDistance, + /*.fnRightDist =*/&SwRect::GetTopDistance, + /*.fnSetLimit =*/&SwFrame::SetMaxRight, + /*.fnOverStep =*/&SwRect::OverStepRight, + + /*.fnSetPos =*/&SwRect::SetLowerLeftCorner, + /*.fnMakePos =*/&SwFrame::MakeRightPos, + /*.fnXDiff =*/&SecondMinusFirst, + /*.fnYDiff =*/&FirstMinusSecond, + /*.fnXInc =*/&SwDecrement, + /*.fnYInc =*/&SwIncrement, + + /*.fnSetLeftAndWidth =*/&SwRect::SetBottomAndHeight, + /*.fnSetTopAndHeight =*/&SwRect::SetLeftAndWidth +}; + +SwRectFn fnRectHori = &aHorizontal; +SwRectFn fnRectVert = &aVertical; +SwRectFn fnRectVertL2R = &aVerticalLeftToRight; +SwRectFn fnRectVertL2RB2T = &aVerticalLeftToRightBottomToTop; + +// #i65250# +sal_uInt32 SwFrameAreaDefinition::snLastFrameId=0; + + +void FrameInit() +{ + SwRootFrame::s_pVout = new SwLayVout(); + SwCache *pNew = new SwCache( 100 +#ifdef DBG_UTIL + , "static SwBorderAttrs::pCache" +#endif + ); + SwFrame::SetCache( pNew ); +} + +void FrameFinit() +{ +#if OSL_DEBUG_LEVEL > 0 + // The cache may only contain null pointers at this time. + for( size_t n = SwFrame::GetCachePtr()->size(); n; ) + if( (*SwFrame::GetCachePtr())[ --n ] ) + { + SwCacheObj* pObj = (*SwFrame::GetCachePtr())[ n ]; + OSL_ENSURE( !pObj, "Who didn't deregister?"); + } +#endif + delete SwRootFrame::s_pVout; + delete SwFrame::GetCachePtr(); +} + +// RootFrame::Everything that belongs to CurrShell + +CurrShell::CurrShell( SwViewShell *pNew ) +{ + OSL_ENSURE( pNew, "insert 0-Shell?" ); + pRoot = pNew->GetLayout(); + if ( pRoot ) + { + pPrev = pRoot->mpCurrShell; + pRoot->mpCurrShell = pNew; + pRoot->mpCurrShells->insert( this ); + } + else + pPrev = nullptr; +} + +CurrShell::~CurrShell() +{ + if ( pRoot ) + { + pRoot->mpCurrShells->erase( this ); + if ( pPrev ) + pRoot->mpCurrShell = pPrev; + if ( pRoot->mpCurrShells->empty() && pRoot->mpWaitingCurrShell ) + { + pRoot->mpCurrShell = pRoot->mpWaitingCurrShell; + pRoot->mpWaitingCurrShell = nullptr; + } + } +} + +void SetShell( SwViewShell *pSh ) +{ + SwRootFrame *pRoot = pSh->GetLayout(); + if ( pRoot->mpCurrShells->empty() ) + pRoot->mpCurrShell = pSh; + else + pRoot->mpWaitingCurrShell = pSh; +} + +void SwRootFrame::DeRegisterShell( SwViewShell *pSh ) +{ + // Activate some shell if possible + if ( mpCurrShell == pSh ) + { + mpCurrShell = nullptr; + for(SwViewShell& rShell : pSh->GetRingContainer()) + { + if(&rShell != pSh) + { + mpCurrShell = &rShell; + break; + } + } + } + + // Doesn't matter anymore + if ( mpWaitingCurrShell == pSh ) + mpWaitingCurrShell = nullptr; + + // Remove references + for ( CurrShell *pC : *mpCurrShells ) + { + if (pC->pPrev == pSh) + pC->pPrev = nullptr; + } +} + +void InitCurrShells( SwRootFrame *pRoot ) +{ + pRoot->mpCurrShells.reset( new SwCurrShells ); +} + +/* +|* The RootFrame requests an own FrameFormat from the document, which it is +|* going to delete again in the dtor. The own FrameFormat is derived from +|* the passed FrameFormat. +|*/ +SwRootFrame::SwRootFrame( SwFrameFormat *pFormat, SwViewShell * pSh ) : + SwLayoutFrame( pFormat->GetDoc()->MakeFrameFormat( + "Root", pFormat ), nullptr ), + mnViewWidth( -1 ), + mnColumns( 0 ), + mbBookMode( false ), + mbSidebarChanged( false ), + mbNeedGrammarCheck( false ), + mbCheckSuperfluous( false ), + mbIdleFormat( true ), + mbBrowseWidthValid( false ), + mbTurboAllowed( true ), + mbAssertFlyPages( true ), + mbTableUpdateInProgress( false ), + mbIsVirtPageNum( false ), + mbIsNewLayout( true ), + mbCallbackActionEnabled ( false ), + mbLayoutFreezed ( false ), + mbHideRedlines(pFormat->GetDoc()->GetDocumentRedlineManager().IsHideRedlines()), + m_FieldmarkMode(pSh->GetViewOptions()->IsFieldName() + ? sw::FieldmarkMode::ShowCommand + : sw::FieldmarkMode::ShowResult), + mnBrowseWidth(MIN_BROWSE_WIDTH), + mpTurbo( nullptr ), + mpLastPage( nullptr ), + mpCurrShell( pSh ), + mpWaitingCurrShell( nullptr ), + mpDrawPage( nullptr ), + mnPhyPageNums( 0 ), + mnAccessibleShells( 0 ) +{ + mnFrameType = SwFrameType::Root; + setRootFrame( this ); +} + +void SwRootFrame::Init( SwFrameFormat* pFormat ) +{ + InitCurrShells( this ); + + IDocumentTimerAccess& rTimerAccess = pFormat->getIDocumentTimerAccess(); + IDocumentLayoutAccess& rLayoutAccess = pFormat->getIDocumentLayoutAccess(); + IDocumentFieldsAccess& rFieldsAccess = pFormat->getIDocumentFieldsAccess(); + const IDocumentSettingAccess& rSettingAccess = pFormat->getIDocumentSettingAccess(); + rTimerAccess.StopIdling(); + // For creating the Flys by MakeFrames() + rLayoutAccess.SetCurrentViewShell( GetCurrShell() ); + mbCallbackActionEnabled = false; // needs to be set to true before leaving! + + SwDrawModel* pMd = pFormat->getIDocumentDrawModelAccess().GetDrawModel(); + if ( pMd ) + { + // Disable "multiple layout" + mpDrawPage = pMd->GetPage(0); + + mpDrawPage->SetSize( getFrameArea().SSize() ); + } + + // Initialize the layout: create pages, link content with Content etc. + // First, initialize some stuff, then get hold of the first + // node (which will be needed for the PageDesc). + + SwDoc* pDoc = pFormat->GetDoc(); + SwNodeIndex aIndex( *pDoc->GetNodes().GetEndOfContent().StartOfSectionNode() ); + SwContentNode *pNode = pDoc->GetNodes().GoNextSection( &aIndex, true, false ); + // #123067# pNode = 0 can really happen + SwTableNode *pTableNd= pNode ? pNode->FindTableNode() : nullptr; + + // Get hold of PageDesc (either via FrameFormat of the first node or the initial one). + SwPageDesc *pDesc = nullptr; + ::std::optional<sal_uInt16> oPgNum; + + if ( pTableNd ) + { + const SwFormatPageDesc &rDesc = pTableNd->GetTable().GetFrameFormat()->GetPageDesc(); + pDesc = const_cast<SwPageDesc*>(rDesc.GetPageDesc()); + //#19104# respect the page number offset!! + oPgNum = rDesc.GetNumOffset(); + if (oPgNum) + mbIsVirtPageNum = true; + } + else if ( pNode ) + { + const SwFormatPageDesc &rDesc = pNode->GetSwAttrSet().GetPageDesc(); + pDesc = const_cast<SwPageDesc*>(rDesc.GetPageDesc()); + //#19104# respect the page number offset!! + oPgNum = rDesc.GetNumOffset(); + if (oPgNum) + mbIsVirtPageNum = true; + } + else + mbIsVirtPageNum = false; + if ( !pDesc ) + pDesc = &pDoc->GetPageDesc( 0 ); + + // Create a page and put it in the layout + // The first page is always a right-page and always a first-page + SwPageFrame* pPage = ::InsertNewPage( + *pDesc, /*pUpper=*/this, /*isRightPage=*/true, /*bFirst=*/true, /*bInsertEmpty=*/false, + /*bFootnote=*/false, /*pSibling=*/nullptr, /*bVeryFirstPage=*/true); + + // Find the first page in the Bodytext section. + SwLayoutFrame *pLay = pPage->FindBodyCont(); + while( pLay->Lower() ) + pLay = static_cast<SwLayoutFrame*>(pLay->Lower()); + + SwNodeIndex aTmp( *pDoc->GetNodes().GetEndOfContent().StartOfSectionNode(), 1 ); + ::InsertCnt_( pLay, pDoc, aTmp.GetIndex(), true ); + + if( rSettingAccess.get(DocumentSettingId::GLOBAL_DOCUMENT) ) + rFieldsAccess.UpdateRefFields(); + //b6433357: Update page fields after loading + if ( !mpCurrShell || !mpCurrShell->Imp()->IsUpdateExpFields() ) + { + SwDocPosUpdate aMsgHint( pPage->getFrameArea().Top() ); + rFieldsAccess.UpdatePageFields( &aMsgHint ); + } + + rTimerAccess.StartIdling(); + mbCallbackActionEnabled = true; + + SwViewShell *pViewSh = GetCurrShell(); + if (pViewSh) + mbNeedGrammarCheck = pViewSh->GetViewOptions()->IsOnlineSpell(); +} + +void SwRootFrame::DestroyImpl() +{ + mbTurboAllowed = false; + mpTurbo = nullptr; + + SwFrameFormat *pRegisteredInNonConst = static_cast<SwFrameFormat*>(GetDep()); + if ( pRegisteredInNonConst ) + { + SwDoc *pDoc = pRegisteredInNonConst->GetDoc(); + pDoc->DelFrameFormat( pRegisteredInNonConst ); + // do this before calling RemoveFootnotes() because footnotes + // can contain anchored objects + pDoc->GetDocumentLayoutManager().ClearSwLayouterEntries(); + } + + mpDestroy.reset(); + + // Remove references + for ( auto& rpCurrShell : *mpCurrShells ) + rpCurrShell->pRoot = nullptr; + + mpCurrShells.reset(); + + // Some accessible shells are left => problems on second SwFrame::Destroy call + assert(0 == mnAccessibleShells); + + // fdo#39510 crash on document close with footnotes + // Object ownership in writer and esp. in layout are a mess: Before the + // document/layout split SwDoc and SwRootFrame were essentially one object + // and magically/uncleanly worked around their common destruction by call + // to SwDoc::IsInDtor() -- even from the layout. As of now destruction of + // the layout proceeds forward through the frames. Since SwTextFootnote::DelFrames + // also searches backwards to find the master of footnotes, they must be + // considered to be owned by the SwRootFrame and also be destroyed here, + // before tearing down the (now footnote free) rest of the layout. + RemoveFootnotes(nullptr, false, true); + + SwLayoutFrame::DestroyImpl(); +} + +SwRootFrame::~SwRootFrame() +{ +} + +void SwRootFrame::RemoveMasterObjs( SdrPage *pPg ) +{ + // Remove all master objects from the Page. But don't delete! + for( size_t i = pPg ? pPg->GetObjCount() : 0; i; ) + { + SdrObject* pObj = pPg->GetObj( --i ); + if( dynamic_cast< const SwFlyDrawObj *>( pObj ) != nullptr ) + pPg->RemoveObject( i ); + } +} + +void SwRootFrame::AllCheckPageDescs() const +{ + if ( !IsLayoutFreezed() ) + CheckPageDescs( const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(Lower())) ); +} + +void SwRootFrame::AllInvalidateAutoCompleteWords() const +{ + SwPageFrame *pPage = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(Lower())); + while ( pPage ) + { + pPage->InvalidateAutoCompleteWords(); + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } +} + +void SwRootFrame::AllAddPaintRect() const +{ + GetCurrShell()->AddPaintRect( getFrameArea() ); +} + +void SwRootFrame::AllRemoveFootnotes() +{ + RemoveFootnotes(); +} + +void SwRootFrame::AllInvalidateSmartTagsOrSpelling(bool bSmartTags) const +{ + SwPageFrame *pPage = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(Lower())); + while ( pPage ) + { + if ( bSmartTags ) + pPage->InvalidateSmartTags(); + + pPage->InvalidateSpelling(); + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/objectformatter.cxx b/sw/source/core/layout/objectformatter.cxx new file mode 100644 index 000000000..15ca544a2 --- /dev/null +++ b/sw/source/core/layout/objectformatter.cxx @@ -0,0 +1,478 @@ +/* -*- 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 "objectformattertxtfrm.hxx" +#include "objectformatterlayfrm.hxx" +#include <anchoreddrawobject.hxx> +#include <sortedobjs.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <flyfrms.hxx> +#include <txtfrm.hxx> +#include <layact.hxx> +#include <IDocumentSettingAccess.hxx> +#include <osl/diagnose.h> + +#include <vector> + +// --> #i26945# - Additionally the type of the anchor text frame +// is collected - by type is meant 'master' or 'follow'. +class SwPageNumAndTypeOfAnchors +{ + private: + struct tEntry + { + SwAnchoredObject* mpAnchoredObj; + sal_uInt32 mnPageNumOfAnchor; + bool mbAnchoredAtMaster; + }; + + std::vector< tEntry > maObjList; + + public: + SwPageNumAndTypeOfAnchors() + { + } + + void Collect( SwAnchoredObject& _rAnchoredObj ) + { + tEntry aNewEntry; + aNewEntry.mpAnchoredObj = &_rAnchoredObj; + // #i33751#, #i34060# - method <GetPageFrameOfAnchor()> + // is replaced by method <FindPageFrameOfAnchor()>. It's return value + // have to be checked. + SwPageFrame* pPageFrameOfAnchor = _rAnchoredObj.FindPageFrameOfAnchor(); + if ( pPageFrameOfAnchor ) + { + aNewEntry.mnPageNumOfAnchor = pPageFrameOfAnchor->GetPhyPageNum(); + } + else + { + aNewEntry.mnPageNumOfAnchor = 0; + } + // --> #i26945# - collect type of anchor + SwTextFrame* pAnchorCharFrame = _rAnchoredObj.FindAnchorCharFrame(); + if ( pAnchorCharFrame ) + { + aNewEntry.mbAnchoredAtMaster = !pAnchorCharFrame->IsFollow(); + } + else + { + aNewEntry.mbAnchoredAtMaster = true; + } + maObjList.push_back( aNewEntry ); + } + + SwAnchoredObject* operator[]( sal_uInt32 _nIndex ) + { + return maObjList[_nIndex].mpAnchoredObj; + } + + sal_uInt32 GetPageNum( sal_uInt32 _nIndex ) const + { + return maObjList[_nIndex].mnPageNumOfAnchor; + } + + // --> #i26945# + bool AnchoredAtMaster( sal_uInt32 _nIndex ) + { + return maObjList[_nIndex].mbAnchoredAtMaster; + } + + sal_uInt32 Count() const + { + return maObjList.size(); + } +}; + +SwObjectFormatter::SwObjectFormatter( const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction, + const bool _bCollectPgNumOfAnchors ) + : mrPageFrame( _rPageFrame ), + mbConsiderWrapOnObjPos( _rPageFrame.GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ), + mpLayAction( _pLayAction ), + // --> #i26945# + mpPgNumAndTypeOfAnchors( _bCollectPgNumOfAnchors ? new SwPageNumAndTypeOfAnchors() : nullptr ) +{ +} + +SwObjectFormatter::~SwObjectFormatter() +{ +} + +std::unique_ptr<SwObjectFormatter> SwObjectFormatter::CreateObjFormatter( + SwFrame& _rAnchorFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ) +{ + std::unique_ptr<SwObjectFormatter> pObjFormatter; + if ( _rAnchorFrame.IsTextFrame() ) + { + pObjFormatter = SwObjectFormatterTextFrame::CreateObjFormatter( + static_cast<SwTextFrame&>(_rAnchorFrame), + _rPageFrame, _pLayAction ); + } + else if ( _rAnchorFrame.IsLayoutFrame() ) + { + pObjFormatter = SwObjectFormatterLayFrame::CreateObjFormatter( + static_cast<SwLayoutFrame&>(_rAnchorFrame), + _rPageFrame, _pLayAction ); + } + else + { + OSL_FAIL( "<SwObjectFormatter::CreateObjFormatter(..)> - unexpected type of anchor frame" ); + } + + return pObjFormatter; +} + +/** method to format all floating screen objects at the given anchor frame +*/ +bool SwObjectFormatter::FormatObjsAtFrame( SwFrame& _rAnchorFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ) +{ + bool bSuccess( true ); + + // create corresponding object formatter + std::unique_ptr<SwObjectFormatter> pObjFormatter = + SwObjectFormatter::CreateObjFormatter( _rAnchorFrame, _rPageFrame, _pLayAction ); + + if ( pObjFormatter ) + { + // format anchored floating screen objects + bSuccess = pObjFormatter->DoFormatObjs(); + } + + return bSuccess; +} + +/** method to format a given floating screen object +*/ +bool SwObjectFormatter::FormatObj( SwAnchoredObject& _rAnchoredObj, + SwFrame* _pAnchorFrame, + const SwPageFrame* _pPageFrame ) +{ + bool bSuccess( true ); + + OSL_ENSURE( _pAnchorFrame || _rAnchoredObj.GetAnchorFrame(), + "<SwObjectFormatter::FormatObj(..)> - missing anchor frame" ); + SwFrame& rAnchorFrame = _pAnchorFrame ? *_pAnchorFrame : *(_rAnchoredObj.AnchorFrame()); + + OSL_ENSURE( _pPageFrame || rAnchorFrame.FindPageFrame(), + "<SwObjectFormatter::FormatObj(..)> - missing page frame" ); + const SwPageFrame& rPageFrame = _pPageFrame ? *_pPageFrame : *(rAnchorFrame.FindPageFrame()); + + // create corresponding object formatter + std::unique_ptr<SwObjectFormatter> pObjFormatter = + SwObjectFormatter::CreateObjFormatter( rAnchorFrame, rPageFrame, nullptr/*_pLayAction*/ ); + + if ( pObjFormatter ) + { + // format given floating screen object + // --> #i40147# - check for moved forward anchor frame + bSuccess = pObjFormatter->DoFormatObj( _rAnchoredObj, true ); + } + + return bSuccess; +} + +/** helper method for method <FormatObj_(..)> - performs the intrinsic format + of the layout of the given layout frame and all its lower layout frames. + + #i28701# + IMPORTANT NOTE: + Method corresponds to methods <SwLayAction::FormatLayoutFly(..)> and + <SwLayAction::FormatLayout(..)>. Thus, its code for the formatting have + to be synchronised. +*/ +void SwObjectFormatter::FormatLayout_( SwLayoutFrame& _rLayoutFrame ) +{ + _rLayoutFrame.Calc(_rLayoutFrame.getRootFrame()->GetCurrShell()->GetOut()); + + SwFrame* pLowerFrame = _rLayoutFrame.Lower(); + while ( pLowerFrame ) + { + if ( pLowerFrame->IsLayoutFrame() ) + { + FormatLayout_( *static_cast<SwLayoutFrame*>(pLowerFrame) ); + } + pLowerFrame = pLowerFrame->GetNext(); + } +} + +/** helper method for method <FormatObj_(..)> - performs the intrinsic + format of the content of the given floating screen object. + + #i28701# +*/ +void SwObjectFormatter::FormatObjContent( SwAnchoredObject& _rAnchoredObj ) +{ + if ( !_rAnchoredObj.DynCastFlyFrame() ) + { + // only Writer fly frames have content + return; + } + + SwFlyFrame& rFlyFrame = static_cast<SwFlyFrame&>(_rAnchoredObj); + SwContentFrame* pContent = rFlyFrame.ContainsContent(); + + while ( pContent ) + { + // format content + pContent->OptCalc(); + + // format floating screen objects at content text frame + // #i23129#, #i36347# - pass correct page frame to + // the object formatter + if ( pContent->IsTextFrame() && + !SwObjectFormatter::FormatObjsAtFrame( *pContent, + *(pContent->FindPageFrame()), + GetLayAction() ) ) + { + // restart format with first content + pContent = rFlyFrame.ContainsContent(); + continue; + } + + // continue with next content + pContent = pContent->GetNextContentFrame(); + } +} + +/** performs the intrinsic format of a given floating screen object and its content. + + #i28701# +*/ +void SwObjectFormatter::FormatObj_( SwAnchoredObject& _rAnchoredObj ) +{ + // collect anchor object and its 'anchor' page number, if requested + if ( mpPgNumAndTypeOfAnchors ) + { + mpPgNumAndTypeOfAnchors->Collect( _rAnchoredObj ); + } + + if ( auto pFlyFrame = _rAnchoredObj.DynCastFlyFrame() ) + { + // --> #i34753# - reset flag, which prevents a positioning + if ( pFlyFrame->IsFlyLayFrame() ) + { + static_cast<SwFlyLayFrame*>(pFlyFrame)->SetNoMakePos( false ); + } + + // #i81146# new loop control + int nLoopControlRuns = 0; + const int nLoopControlMax = 15; + + do { + if ( mpLayAction ) + { + mpLayAction->FormatLayoutFly( pFlyFrame ); + // --> consider, if the layout action + // has to be restarted due to a delete of a page frame. + if ( mpLayAction->IsAgain() ) + { + break; + } + } + else + { + FormatLayout_( *pFlyFrame ); + } + // --> #i34753# - prevent further positioning, if + // to-page|to-fly anchored Writer fly frame is already clipped. + if ( pFlyFrame->IsFlyLayFrame() && pFlyFrame->IsClipped() ) + { + static_cast<SwFlyLayFrame*>(pFlyFrame)->SetNoMakePos( true ); + } + // #i23129#, #i36347# - pass correct page frame + // to the object formatter + SwObjectFormatter::FormatObjsAtFrame( *pFlyFrame, + *(pFlyFrame->FindPageFrame()), + mpLayAction ); + if ( mpLayAction ) + { + mpLayAction->FormatFlyContent( pFlyFrame ); + // --> consider, if the layout action + // has to be restarted due to a delete of a page frame. + if ( mpLayAction->IsAgain() ) + { + break; + } + } + else + { + FormatObjContent( *pFlyFrame ); + } + + if ( ++nLoopControlRuns >= nLoopControlMax ) + { + OSL_FAIL( "LoopControl in SwObjectFormatter::FormatObj_: Stage 3!!!" ); + pFlyFrame->ValidateThisAndAllLowers( 2 ); + nLoopControlRuns = 0; + } + + // --> #i57917# + // stop formatting of anchored object, if restart of layout process is requested. + } while ( !pFlyFrame->isFrameAreaDefinitionValid() && + !_rAnchoredObj.RestartLayoutProcess() && + pFlyFrame->GetAnchorFrame() == &GetAnchorFrame() ); + } + else if ( dynamic_cast<const SwAnchoredDrawObject*>( &_rAnchoredObj) != nullptr ) + { + _rAnchoredObj.MakeObjPos(); + } +} + +/** invokes the intrinsic format method for all floating screen objects, + anchored at anchor frame on the given page frame + + #i28701# + #i26945# - for format of floating screen objects for + follow text frames, the 'master' text frame is passed to the method. + Thus, the objects, whose anchor character is inside the follow text + frame can be formatted. +*/ +bool SwObjectFormatter::FormatObjsAtFrame_( SwTextFrame* _pMasterTextFrame ) +{ + // --> #i26945# + SwFrame* pAnchorFrame( nullptr ); + if ( GetAnchorFrame().IsTextFrame() && + static_cast<SwTextFrame&>(GetAnchorFrame()).IsFollow() && + _pMasterTextFrame ) + { + pAnchorFrame = _pMasterTextFrame; + } + else + { + pAnchorFrame = &GetAnchorFrame(); + } + if ( !pAnchorFrame->GetDrawObjs() ) + { + // nothing to do, if no floating screen object is registered at the anchor frame. + return true; + } + + bool bSuccess( true ); + + for ( size_t i = 0; i < pAnchorFrame->GetDrawObjs()->size(); ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pAnchorFrame->GetDrawObjs())[i]; + + // check, if object's anchor is on the given page frame or + // object is registered at the given page frame. + // --> #i26945# - check, if the anchor character of the + // anchored object is located in a follow text frame. If this anchor + // follow text frame differs from the given anchor frame, the given + // anchor frame is a 'master' text frame of the anchor follow text frame. + // If the anchor follow text frame is in the same body as its 'master' + // text frame, do not format the anchored object. + // E.g., this situation can occur during the table row splitting algorithm. + SwTextFrame* pAnchorCharFrame = pAnchoredObj->FindAnchorCharFrame(); + const bool bAnchoredAtFollowInSameBodyAsMaster = + pAnchorCharFrame && pAnchorCharFrame->IsFollow() && + pAnchorCharFrame != pAnchoredObj->GetAnchorFrame() && + pAnchorCharFrame->FindBodyFrame() == + static_cast<SwTextFrame*>(pAnchoredObj->AnchorFrame())->FindBodyFrame(); + if ( bAnchoredAtFollowInSameBodyAsMaster ) + { + continue; + } + // #i33751#, #i34060# - method <GetPageFrameOfAnchor()> + // is replaced by method <FindPageFrameOfAnchor()>. It's return value + // have to be checked. + SwPageFrame* pPageFrameOfAnchor = pAnchoredObj->FindPageFrameOfAnchor(); + OSL_ENSURE( pPageFrameOfAnchor, + "<SwObjectFormatter::FormatObjsAtFrame_()> - missing page frame." ); + // --> #i26945# + if ( pPageFrameOfAnchor && pPageFrameOfAnchor == &mrPageFrame ) + { + // if format of object fails, stop formatting and pass fail to + // calling method via the return value. + if ( !DoFormatObj( *pAnchoredObj ) ) + { + bSuccess = false; + break; + } + + // considering changes at <pAnchorFrame->GetDrawObjs()> during + // format of the object. + if ( !pAnchorFrame->GetDrawObjs() || + i > pAnchorFrame->GetDrawObjs()->size() ) + { + break; + } + else + { + const size_t nActPosOfObj = + pAnchorFrame->GetDrawObjs()->ListPosOf( *pAnchoredObj ); + if ( nActPosOfObj == pAnchorFrame->GetDrawObjs()->size() || + nActPosOfObj > i ) + { + --i; + } + else if ( nActPosOfObj < i ) + { + i = nActPosOfObj; + } + } + } + } // end of loop on <pAnchorFrame->.GetDrawObjs()> + + return bSuccess; +} + +/** accessor to collected anchored object + + #i28701# +*/ +SwAnchoredObject* SwObjectFormatter::GetCollectedObj( const sal_uInt32 _nIndex ) +{ + return mpPgNumAndTypeOfAnchors ? (*mpPgNumAndTypeOfAnchors)[_nIndex] : nullptr; +} + +/** accessor to 'anchor' page number of collected anchored object + + #i28701# +*/ +sal_uInt32 SwObjectFormatter::GetPgNumOfCollected( const sal_uInt32 _nIndex ) +{ + return mpPgNumAndTypeOfAnchors ? mpPgNumAndTypeOfAnchors->GetPageNum(_nIndex) : 0; +} + +/** accessor to 'anchor' type of collected anchored object + + #i26945# +*/ +bool SwObjectFormatter::IsCollectedAnchoredAtMaster( const sal_uInt32 _nIndex ) +{ + return mpPgNumAndTypeOfAnchors == nullptr + || mpPgNumAndTypeOfAnchors->AnchoredAtMaster(_nIndex); +} + +/** accessor to total number of collected anchored objects + + #i28701# +*/ +sal_uInt32 SwObjectFormatter::CountOfCollected() +{ + return mpPgNumAndTypeOfAnchors ? mpPgNumAndTypeOfAnchors->Count() : 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/objectformatterlayfrm.cxx b/sw/source/core/layout/objectformatterlayfrm.cxx new file mode 100644 index 000000000..a9b5201b3 --- /dev/null +++ b/sw/source/core/layout/objectformatterlayfrm.cxx @@ -0,0 +1,188 @@ +/* -*- 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 "objectformatterlayfrm.hxx" +#include <anchoredobject.hxx> +#include <sortedobjs.hxx> +#include <pagefrm.hxx> + +#include <layact.hxx> +#include <osl/diagnose.h> + +SwObjectFormatterLayFrame::SwObjectFormatterLayFrame( SwLayoutFrame& _rAnchorLayFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ) + : SwObjectFormatter( _rPageFrame, _pLayAction ), + mrAnchorLayFrame( _rAnchorLayFrame ) +{ +} + +SwObjectFormatterLayFrame::~SwObjectFormatterLayFrame() +{ +} + +std::unique_ptr<SwObjectFormatterLayFrame> SwObjectFormatterLayFrame::CreateObjFormatter( + SwLayoutFrame& _rAnchorLayFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ) +{ + if ( !_rAnchorLayFrame.IsPageFrame() && + !_rAnchorLayFrame.IsFlyFrame() ) + { + OSL_FAIL( "<SwObjectFormatterLayFrame::CreateObjFormatter(..)> - unexpected type of anchor frame " ); + return nullptr; + } + + std::unique_ptr<SwObjectFormatterLayFrame> pObjFormatter; + + // create object formatter, if floating screen objects are registered at + // given anchor layout frame. + if ( _rAnchorLayFrame.GetDrawObjs() || + ( _rAnchorLayFrame.IsPageFrame() && + static_cast<SwPageFrame&>(_rAnchorLayFrame).GetSortedObjs() ) ) + { + pObjFormatter.reset( + new SwObjectFormatterLayFrame( _rAnchorLayFrame, _rPageFrame, _pLayAction )); + } + + return pObjFormatter; +} + +SwFrame& SwObjectFormatterLayFrame::GetAnchorFrame() +{ + return mrAnchorLayFrame; +} + +// #i40147# - add parameter <_bCheckForMovedFwd>. +// Not relevant for objects anchored at layout frame. +bool SwObjectFormatterLayFrame::DoFormatObj( SwAnchoredObject& _rAnchoredObj, + const bool ) +{ + FormatObj_( _rAnchoredObj ); + + // #124218# - consider that the layout action has to be + // restarted due to a deleted page frame. + return GetLayAction() == nullptr || !GetLayAction()->IsAgain(); +} + +bool SwObjectFormatterLayFrame::DoFormatObjs() +{ + bool bSuccess = FormatObjsAtFrame_(); + + if ( bSuccess && GetAnchorFrame().IsPageFrame() ) + { + // anchor layout frame is a page frame. + // Thus, format also all anchored objects, which are registered at + // this page frame, whose 'anchor' isn't on this page frame and whose + // anchor frame is valid. + bSuccess = AdditionalFormatObjsOnPage(); + } + + return bSuccess; +} + +/** method to format all anchored objects, which are registered at + the page frame, whose 'anchor' isn't on this page frame and whose + anchor frame is valid. + + OD 2004-07-02 #i28701# +*/ +bool SwObjectFormatterLayFrame::AdditionalFormatObjsOnPage() +{ + if ( !GetAnchorFrame().IsPageFrame() ) + { + OSL_FAIL( "<SwObjectFormatterLayFrame::AdditionalFormatObjsOnPage()> - mis-usage of method, call only for anchor frames of type page frame" ); + return true; + } + + // #124218# - consider, if the layout action + // has to be restarted due to a delete of a page frame. + if ( GetLayAction() && GetLayAction()->IsAgain() ) + { + return false; + } + + SwPageFrame& rPageFrame = static_cast<SwPageFrame&>(GetAnchorFrame()); + + if ( !rPageFrame.GetSortedObjs() ) + { + // nothing to do, if no floating screen object is registered at the anchor frame. + return true; + } + + bool bSuccess( true ); + + for ( size_t i = 0; i < rPageFrame.GetSortedObjs()->size(); ++i ) + { + SwAnchoredObject* pAnchoredObj = (*rPageFrame.GetSortedObjs())[i]; + + // #i51941# - do not format object, which are anchored + // inside or at fly frame. + if ( pAnchoredObj->GetAnchorFrame()->FindFlyFrame() ) + { + continue; + } + // #i33751#, #i34060# - method <GetPageFrameOfAnchor()> + // is replaced by method <FindPageFrameOfAnchor()>. It's return value + // have to be checked. + SwPageFrame* pPageFrameOfAnchor = pAnchoredObj->FindPageFrameOfAnchor(); + // #i26945# - check, if the page frame of the + // object's anchor frame isn't the given page frame + OSL_ENSURE( pPageFrameOfAnchor, + "<SwObjectFormatterLayFrame::AdditionalFormatObjsOnPage()> - missing page frame" ); + if ( pPageFrameOfAnchor && + // #i35911# + pPageFrameOfAnchor->GetPhyPageNum() < rPageFrame.GetPhyPageNum() ) + { + // if format of object fails, stop formatting and pass fail to + // calling method via the return value. + if ( !DoFormatObj( *pAnchoredObj ) ) + { + bSuccess = false; + break; + } + + // considering changes at <GetAnchorFrame().GetDrawObjs()> during + // format of the object. + if ( !rPageFrame.GetSortedObjs() || + i > rPageFrame.GetSortedObjs()->size() ) + { + break; + } + else + { + const size_t nActPosOfObj = + rPageFrame.GetSortedObjs()->ListPosOf( *pAnchoredObj ); + if ( nActPosOfObj == rPageFrame.GetSortedObjs()->size() || + nActPosOfObj > i ) + { + --i; + } + else if ( nActPosOfObj < i ) + { + i = nActPosOfObj; + } + } + } + } // end of loop on <rPageFrame.GetSortedObjs()> + + return bSuccess; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/objectformatterlayfrm.hxx b/sw/source/core/layout/objectformatterlayfrm.hxx new file mode 100644 index 000000000..9ece6f281 --- /dev/null +++ b/sw/source/core/layout/objectformatterlayfrm.hxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_LAYOUT_OBJECTFORMATTERLAYFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_LAYOUT_OBJECTFORMATTERLAYFRM_HXX + +#include <objectformatter.hxx> + +class SwLayoutFrame; + +// Format floating screen objects, which are anchored at a given anchor text frame +// and registered at the given page frame. +class SwObjectFormatterLayFrame : public SwObjectFormatter +{ + private: + // anchor layout frame + SwLayoutFrame& mrAnchorLayFrame; + + SwObjectFormatterLayFrame( SwLayoutFrame& _rAnchorLayFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ); + + /** method to format all anchored objects, which are registered at + the page frame, whose 'anchor' isn't on this page frame and whose + anchor frame is valid. + + OD 2004-07-02 #i28701# + + @return boolean + indicates, if format was successful + */ + bool AdditionalFormatObjsOnPage(); + + protected: + + virtual SwFrame& GetAnchorFrame() override; + + public: + virtual ~SwObjectFormatterLayFrame() override; + + // #i40147# - add parameter <_bCheckForMovedFwd>. + // Not relevant for objects anchored at layout frame. + virtual bool DoFormatObj( SwAnchoredObject& _rAnchoredObj, + const bool _bCheckForMovedFwd = false ) override; + virtual bool DoFormatObjs() override; + + static std::unique_ptr<SwObjectFormatterLayFrame> CreateObjFormatter( + SwLayoutFrame& _rAnchorLayFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/objectformattertxtfrm.cxx b/sw/source/core/layout/objectformattertxtfrm.cxx new file mode 100644 index 000000000..6b2503d40 --- /dev/null +++ b/sw/source/core/layout/objectformattertxtfrm.cxx @@ -0,0 +1,971 @@ +/* -*- 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 "objectformattertxtfrm.hxx" +#include <sortedobjs.hxx> +#include <rootfrm.hxx> +#include <anchoredobject.hxx> +#include <txtfrm.hxx> +#include <pagefrm.hxx> +#include <rowfrm.hxx> +#include <layouter.hxx> +#include <fmtanchr.hxx> +#include <fmtwrapinfluenceonobjpos.hxx> +#include <fmtfollowtextflow.hxx> +#include <layact.hxx> +#include <flyfrm.hxx> +#include <ftnfrm.hxx> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; + +namespace { + +// little helper class to forbid follow formatting for the given text frame +class SwForbidFollowFormat +{ +private: + SwTextFrame& mrTextFrame; + const bool bOldFollowFormatAllowed; + +public: + explicit SwForbidFollowFormat( SwTextFrame& _rTextFrame ) + : mrTextFrame( _rTextFrame ), + bOldFollowFormatAllowed( _rTextFrame.FollowFormatAllowed() ) + { + mrTextFrame.ForbidFollowFormat(); + } + + ~SwForbidFollowFormat() + { + if ( bOldFollowFormatAllowed ) + { + mrTextFrame.AllowFollowFormat(); + } + } +}; + +} + +SwObjectFormatterTextFrame::SwObjectFormatterTextFrame( SwTextFrame& _rAnchorTextFrame, + const SwPageFrame& _rPageFrame, + SwTextFrame* _pMasterAnchorTextFrame, + SwLayAction* _pLayAction ) + : SwObjectFormatter( _rPageFrame, _pLayAction, true ), + mrAnchorTextFrame( _rAnchorTextFrame ), + mpMasterAnchorTextFrame( _pMasterAnchorTextFrame ) +{ +} + +SwObjectFormatterTextFrame::~SwObjectFormatterTextFrame() +{ +} + +std::unique_ptr<SwObjectFormatterTextFrame> SwObjectFormatterTextFrame::CreateObjFormatter( + SwTextFrame& _rAnchorTextFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ) +{ + std::unique_ptr<SwObjectFormatterTextFrame> pObjFormatter; + + // determine 'master' of <_rAnchorTextFrame>, if anchor frame is a follow text frame. + SwTextFrame* pMasterOfAnchorFrame = nullptr; + if ( _rAnchorTextFrame.IsFollow() ) + { + pMasterOfAnchorFrame = _rAnchorTextFrame.FindMaster(); + while ( pMasterOfAnchorFrame && pMasterOfAnchorFrame->IsFollow() ) + { + pMasterOfAnchorFrame = pMasterOfAnchorFrame->FindMaster(); + } + } + + // create object formatter, if floating screen objects are registered + // at anchor frame (or at 'master' anchor frame) + if ( _rAnchorTextFrame.GetDrawObjs() || + ( pMasterOfAnchorFrame && pMasterOfAnchorFrame->GetDrawObjs() ) ) + { + pObjFormatter.reset( + new SwObjectFormatterTextFrame( _rAnchorTextFrame, _rPageFrame, + pMasterOfAnchorFrame, _pLayAction )); + } + + return pObjFormatter; +} + +SwFrame& SwObjectFormatterTextFrame::GetAnchorFrame() +{ + return mrAnchorTextFrame; +} + +// #i40147# - add parameter <_bCheckForMovedFwd>. +bool SwObjectFormatterTextFrame::DoFormatObj( SwAnchoredObject& _rAnchoredObj, + const bool _bCheckForMovedFwd ) +{ + // consider, if the layout action has to be + // restarted due to a delete of a page frame. + if ( GetLayAction() && GetLayAction()->IsAgain() ) + { + return false; + } + + bool bSuccess( true ); + + if ( _rAnchoredObj.IsFormatPossible() ) + { + _rAnchoredObj.SetRestartLayoutProcess( false ); + + FormatObj_( _rAnchoredObj ); + // consider, if the layout action has to be + // restarted due to a delete of a page frame. + if ( GetLayAction() && GetLayAction()->IsAgain() ) + { + return false; + } + + // check, if layout process has to be restarted. + // if yes, perform needed invalidations. + + // no restart of layout process, + // if anchored object is anchored inside a Writer fly frame, + // its position is already locked, and it follows the text flow. + const bool bRestart = + _rAnchoredObj.RestartLayoutProcess() && + !( _rAnchoredObj.PositionLocked() && + _rAnchoredObj.GetAnchorFrame()->IsInFly() && + _rAnchoredObj.GetFrameFormat().GetFollowTextFlow().GetValue() ); + if ( bRestart ) + { + bSuccess = false; + InvalidatePrevObjs( _rAnchoredObj ); + InvalidateFollowObjs( _rAnchoredObj ); + } + + // format anchor text frame, if wrapping style influence of the object + // has to be considered and it's <NONE_SUCCESSIVE_POSITIONED> + // #i3317# - consider also anchored objects, whose + // wrapping style influence is temporarily considered. + // #i40147# - consider also anchored objects, for + // whose the check of a moved forward anchor frame is requested. + // revise decision made for i3317: + // anchored objects, whose wrapping style influence is temporarily considered, + // have to be considered in method <SwObjectFormatterTextFrame::DoFormatObjs()> + if ( bSuccess && + _rAnchoredObj.ConsiderObjWrapInfluenceOnObjPos() && + ( _bCheckForMovedFwd || + _rAnchoredObj.GetFrameFormat().GetWrapInfluenceOnObjPos(). + // #i35017# - handle ITERATIVE as ONCE_SUCCESSIVE + GetWrapInfluenceOnObjPos( true ) == + // #i35017# - constant name has changed + text::WrapInfluenceOnPosition::ONCE_SUCCESSIVE ) ) + { + // #i26945# - check conditions for move forward of + // anchor text frame + // determine, if anchor text frame has previous frame + const bool bDoesAnchorHadPrev = ( mrAnchorTextFrame.GetIndPrev() != nullptr ); + + // #i40141# - use new method - it also formats the + // section the anchor frame is in. + FormatAnchorFrameForCheckMoveFwd(); + + // #i35911# + if ( _rAnchoredObj.HasClearedEnvironment() ) + { + _rAnchoredObj.SetClearedEnvironment( true ); + // #i44049# - consider, that anchor frame + // could already been marked to move forward. + SwPageFrame* pAnchorPageFrame( mrAnchorTextFrame.FindPageFrame() ); + if ( pAnchorPageFrame != _rAnchoredObj.GetPageFrame() ) + { + bool bInsert( true ); + sal_uInt32 nToPageNum( 0 ); + const SwDoc& rDoc = *(GetPageFrame().GetFormat()->GetDoc()); + if ( SwLayouter::FrameMovedFwdByObjPos( + rDoc, mrAnchorTextFrame, nToPageNum ) ) + { + if ( nToPageNum < pAnchorPageFrame->GetPhyPageNum() ) + SwLayouter::RemoveMovedFwdFrame( rDoc, mrAnchorTextFrame ); + else + bInsert = false; + } + if ( bInsert ) + { + SwLayouter::InsertMovedFwdFrame( rDoc, mrAnchorTextFrame, + pAnchorPageFrame->GetPhyPageNum() ); + mrAnchorTextFrame.InvalidatePos(); + bSuccess = false; + InvalidatePrevObjs( _rAnchoredObj ); + InvalidateFollowObjs( _rAnchoredObj ); + } + else + { + OSL_FAIL( "<SwObjectFormatterTextFrame::DoFormatObj(..)> - anchor frame not marked to move forward" ); + } + } + } + else if ( !mrAnchorTextFrame.IsFollow() && bDoesAnchorHadPrev ) + { + // index of anchored object in collection of page numbers and + // anchor types + sal_uInt32 nIdx( CountOfCollected() ); + OSL_ENSURE( nIdx > 0, + "<SwObjectFormatterTextFrame::DoFormatObj(..)> - anchored object not collected!?" ); + --nIdx; + + sal_uInt32 nToPageNum( 0 ); + // #i43913# + bool bDummy( false ); + bool bPageHasFlysAnchoredBelowThis(false); + // see how SwObjectFormatter::FormatObjsAtFrame_() checks + // "pPageFrameOfAnchor == &mrPageFrame" - only caller relevant for + // this subclass + assert(GetPageFrame().GetPhyPageNum() == GetPgNumOfCollected(nIdx)); + if ( SwObjectFormatterTextFrame::CheckMovedFwdCondition( *GetCollectedObj( nIdx ), + GetPageFrame(), + IsCollectedAnchoredAtMaster( nIdx ), + nToPageNum, bDummy, + bPageHasFlysAnchoredBelowThis)) + { + // #i49987# - consider, that anchor frame + // could already been marked to move forward. + bool bInsert( true ); + sal_uInt32 nMovedFwdToPageNum( 0 ); + const SwDoc& rDoc = *(GetPageFrame().GetFormat()->GetDoc()); + if ( SwLayouter::FrameMovedFwdByObjPos( + rDoc, mrAnchorTextFrame, nMovedFwdToPageNum ) ) + { + if ( nMovedFwdToPageNum < nToPageNum ) + { + if (!bPageHasFlysAnchoredBelowThis) + { + SwLayouter::RemoveMovedFwdFrame(rDoc, mrAnchorTextFrame); + } + } + else + bInsert = false; + } + if ( bInsert ) + { + // Indicate that anchor text frame has to move forward and + // invalidate its position to force a re-format. + if (!bPageHasFlysAnchoredBelowThis) + { + SwLayouter::InsertMovedFwdFrame(rDoc, + mrAnchorTextFrame, nToPageNum); + } + mrAnchorTextFrame.InvalidatePos(); + + // Indicate restart of the layout process + bSuccess = false; + + // If needed, invalidate previous objects anchored at same anchor + // text frame. + InvalidatePrevObjs( _rAnchoredObj ); + + // Invalidate object and following objects for the restart of the + // layout process + InvalidateFollowObjs( _rAnchoredObj ); + } + else + { + OSL_FAIL( "<SwObjectFormatterTextFrame::DoFormatObj(..)> - anchor frame not marked to move forward" ); + } + } + } + // i40155# - mark anchor frame not to wrap around + // objects under the condition, that its follow contains all its text. + else if ( !mrAnchorTextFrame.IsFollow() && + mrAnchorTextFrame.GetFollow() && + mrAnchorTextFrame.GetFollow()->GetOffset() == TextFrameIndex(0)) + { + SwLayouter::RemoveMovedFwdFrame( + *(mrAnchorTextFrame.FindPageFrame()->GetFormat()->GetDoc()), + mrAnchorTextFrame ); + } + } + } + + return bSuccess; +} + +bool SwObjectFormatterTextFrame::DoFormatObjs() +{ + if ( !mrAnchorTextFrame.isFrameAreaDefinitionValid() ) + { + if ( GetLayAction() && + mrAnchorTextFrame.FindPageFrame() != &GetPageFrame() ) + { + // notify layout action, thus is can restart the layout process on + // a previous page. + GetLayAction()->SetAgain(true); + } + else + { + // the anchor text frame has to be valid, thus assert. + OSL_FAIL( "<SwObjectFormatterTextFrame::DoFormatObjs()> called for invalidate anchor text frame." ); + } + + return false; + } + + bool bSuccess( true ); + + if ( mrAnchorTextFrame.IsFollow() ) + { + // Only floating screen objects anchored as-character are directly + // registered at a follow text frame. The other floating screen objects + // are registered at the 'master' anchor text frame. + // Thus, format the other floating screen objects through the 'master' + // anchor text frame + OSL_ENSURE( mpMasterAnchorTextFrame, + "SwObjectFormatterTextFrame::DoFormatObjs() - missing 'master' anchor text frame" ); + bSuccess = FormatObjsAtFrame_( mpMasterAnchorTextFrame ); + + if ( bSuccess ) + { + // format of as-character anchored floating screen objects - no failure + // expected on the format of these objects. + bSuccess = FormatObjsAtFrame_(); + } + } + else + { + bSuccess = FormatObjsAtFrame_(); + } + + // consider anchored objects, whose wrapping style influence are temporarily + // considered. + if ( bSuccess && + ( ConsiderWrapOnObjPos() || + ( !mrAnchorTextFrame.IsFollow() && + AtLeastOneObjIsTmpConsiderWrapInfluence() ) ) ) + { + const bool bDoesAnchorHadPrev = ( mrAnchorTextFrame.GetIndPrev() != nullptr ); + + // Format anchor text frame after its objects are formatted. + // Note: The format of the anchor frame also formats the invalid + // previous frames of the anchor frame. The format of the previous + // frames is needed to get a correct result of format of the + // anchor frame for the following check for moved forward anchors + // #i40141# - use new method - it also formats the + // section the anchor frame is in. + FormatAnchorFrameForCheckMoveFwd(); + + sal_uInt32 nToPageNum( 0 ); + // #i43913# + bool bInFollow( false ); + bool bPageHasFlysAnchoredBelowThis(false); + SwAnchoredObject* pObj = nullptr; + if ( !mrAnchorTextFrame.IsFollow() ) + { + pObj = GetFirstObjWithMovedFwdAnchor( + // #i35017# - constant name has changed + text::WrapInfluenceOnPosition::ONCE_CONCURRENT, + nToPageNum, bInFollow, bPageHasFlysAnchoredBelowThis ); + } + // #i35911# + if ( pObj && pObj->HasClearedEnvironment() ) + { + pObj->SetClearedEnvironment( true ); + // #i44049# - consider, that anchor frame + // could already been marked to move forward. + SwPageFrame* pAnchorPageFrame( mrAnchorTextFrame.FindPageFrame() ); + // #i43913# - consider, that anchor frame + // is a follow or is in a follow row, which will move forward. + if ( pAnchorPageFrame != pObj->GetPageFrame() || + bInFollow ) + { + bool bInsert( true ); + sal_uInt32 nTmpToPageNum( 0 ); + const SwDoc& rDoc = *(GetPageFrame().GetFormat()->GetDoc()); + if ( SwLayouter::FrameMovedFwdByObjPos( + rDoc, mrAnchorTextFrame, nTmpToPageNum ) ) + { + if ( nTmpToPageNum < pAnchorPageFrame->GetPhyPageNum() ) + { + if (!bPageHasFlysAnchoredBelowThis) + { + SwLayouter::RemoveMovedFwdFrame(rDoc, mrAnchorTextFrame); + } + } + else + bInsert = false; + } + if ( bInsert ) + { + if (!bPageHasFlysAnchoredBelowThis) + { + SwLayouter::InsertMovedFwdFrame(rDoc, mrAnchorTextFrame, + pAnchorPageFrame->GetPhyPageNum()); + } + mrAnchorTextFrame.InvalidatePos(); + bSuccess = false; + InvalidatePrevObjs( *pObj ); + InvalidateFollowObjs( *pObj ); + } + else + { + OSL_FAIL( "<SwObjectFormatterTextFrame::DoFormatObjs(..)> - anchor frame not marked to move forward" ); + } + } + } + else if ( pObj && bDoesAnchorHadPrev ) + { + // Object found, whose anchor is moved forward + + // #i49987# - consider, that anchor frame + // could already been marked to move forward. + bool bInsert( true ); + sal_uInt32 nMovedFwdToPageNum( 0 ); + const SwDoc& rDoc = *(GetPageFrame().GetFormat()->GetDoc()); + if ( SwLayouter::FrameMovedFwdByObjPos( + rDoc, mrAnchorTextFrame, nMovedFwdToPageNum ) ) + { + if ( nMovedFwdToPageNum < nToPageNum ) + SwLayouter::RemoveMovedFwdFrame( rDoc, mrAnchorTextFrame ); + else + bInsert = false; + } + if ( bInsert ) + { + // Indicate that anchor text frame has to move forward and + // invalidate its position to force a re-format. + SwLayouter::InsertMovedFwdFrame( rDoc, mrAnchorTextFrame, nToPageNum ); + mrAnchorTextFrame.InvalidatePos(); + + // Indicate restart of the layout process + bSuccess = false; + + // If needed, invalidate previous objects anchored at same anchor + // text frame. + InvalidatePrevObjs( *pObj ); + + // Invalidate object and following objects for the restart of the + // layout process + InvalidateFollowObjs( *pObj ); + } + else + { + OSL_FAIL( "<SwObjectFormatterTextFrame::DoFormatObjs(..)> - anchor frame not marked to move forward" ); + } + } + // #i40155# - mark anchor frame not to wrap around + // objects under the condition, that its follow contains all its text. + else if ( !mrAnchorTextFrame.IsFollow() && + mrAnchorTextFrame.GetFollow() && + mrAnchorTextFrame.GetFollow()->GetOffset() == TextFrameIndex(0)) + { + SwLayouter::RemoveMovedFwdFrame( + *(mrAnchorTextFrame.FindPageFrame()->GetFormat()->GetDoc()), + mrAnchorTextFrame ); + } + } + + return bSuccess; +} + +void SwObjectFormatterTextFrame::InvalidatePrevObjs( SwAnchoredObject& _rAnchoredObj ) +{ + // invalidate all previous objects, whose wrapping influence on the object + // positioning is <NONE_CONCURRENT_POSITIONED>. + // Note: list of objects at anchor frame is sorted by this property. + if ( _rAnchoredObj.GetFrameFormat().GetWrapInfluenceOnObjPos(). + // #i35017# - handle ITERATIVE as ONCE_SUCCESSIVE + GetWrapInfluenceOnObjPos( true ) != + // #i35017# - constant name has changed + text::WrapInfluenceOnPosition::ONCE_CONCURRENT ) + return; + + const SwSortedObjs* pObjs = GetAnchorFrame().GetDrawObjs(); + if ( !pObjs ) + return; + + // determine start index + size_t i = pObjs->ListPosOf( _rAnchoredObj ); + while (i > 0) + { + --i; + SwAnchoredObject* pAnchoredObj = (*pObjs)[i]; + if ( pAnchoredObj->GetFrameFormat().GetWrapInfluenceOnObjPos(). + // #i35017# - handle ITERATIVE as ONCE_SUCCESSIVE + GetWrapInfluenceOnObjPos( true ) == + // #i35017# - constant name has changed + text::WrapInfluenceOnPosition::ONCE_CONCURRENT ) + { + pAnchoredObj->InvalidateObjPosForConsiderWrapInfluence(); + } + } +} + +void SwObjectFormatterTextFrame::InvalidateFollowObjs( SwAnchoredObject& _rAnchoredObj ) +{ + _rAnchoredObj.InvalidateObjPosForConsiderWrapInfluence(); + + const SwSortedObjs* pObjs = GetPageFrame().GetSortedObjs(); + if ( pObjs ) + { + // determine start index + for ( size_t i = pObjs->ListPosOf( _rAnchoredObj ) + 1; i < pObjs->size(); ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pObjs)[i]; + pAnchoredObj->InvalidateObjPosForConsiderWrapInfluence(); + } + } +} + +SwAnchoredObject* SwObjectFormatterTextFrame::GetFirstObjWithMovedFwdAnchor( + const sal_Int16 _nWrapInfluenceOnPosition, + sal_uInt32& _noToPageNum, + bool& _boInFollow, + bool& o_rbPageHasFlysAnchoredBelowThis) +{ + // #i35017# - constant names have changed + OSL_ENSURE( _nWrapInfluenceOnPosition == text::WrapInfluenceOnPosition::ONCE_SUCCESSIVE || + _nWrapInfluenceOnPosition == text::WrapInfluenceOnPosition::ONCE_CONCURRENT, + "<SwObjectFormatterTextFrame::GetFirstObjWithMovedFwdAnchor(..)> - invalid value for parameter <_nWrapInfluenceOnPosition>" ); + + SwAnchoredObject* pRetAnchoredObj = nullptr; + + sal_uInt32 i = 0; + for ( ; i < CountOfCollected(); ++i ) + { + SwAnchoredObject* pAnchoredObj = GetCollectedObj(i); + if ( pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos() && + pAnchoredObj->GetFrameFormat().GetWrapInfluenceOnObjPos(). + // #i35017# - handle ITERATIVE as ONCE_SUCCESSIVE + GetWrapInfluenceOnObjPos( true ) == _nWrapInfluenceOnPosition ) + { + // see how SwObjectFormatter::FormatObjsAtFrame_() checks + // "pPageFrameOfAnchor == &mrPageFrame" - only caller relevant for + // this subclass + assert(GetPageFrame().GetPhyPageNum() == GetPgNumOfCollected(i)); + // #i26945# - use new method <_CheckMovedFwdCondition(..)> + // #i43913# + if ( SwObjectFormatterTextFrame::CheckMovedFwdCondition( *GetCollectedObj( i ), + GetPageFrame(), + IsCollectedAnchoredAtMaster( i ), + _noToPageNum, _boInFollow, + o_rbPageHasFlysAnchoredBelowThis) ) + { + pRetAnchoredObj = pAnchoredObj; + break; + } + } + } + + return pRetAnchoredObj; +} + +static SwRowFrame const* FindTopLevelRowFrame(SwFrame const*const pFrame) +{ + SwRowFrame * pRow = const_cast<SwFrame*>(pFrame)->FindRowFrame(); + // looks like SwTabFrame has mbInfTab = true so go up 2 levels + while (pRow->GetUpper()->GetUpper()->IsInTab()) + { + pRow = pRow->GetUpper()->GetUpper()->FindRowFrame(); + } + return pRow; +} + +static SwContentFrame const* FindFrameInBody(SwAnchoredObject const& rAnchored) +{ + SwFrame const*const pAnchor(rAnchored.GetAnchorFrame()); + assert(pAnchor); + if (pAnchor->IsPageFrame() || pAnchor->FindFooterOrHeader()) + { + return nullptr; + } + if (pAnchor->IsInFly()) + { + return FindFrameInBody(*pAnchor->FindFlyFrame()); + } + if (pAnchor->IsInFootnote()) + { + return pAnchor->FindFootnoteFrame()->GetRef(); + } + assert(pAnchor->IsInDocBody()); + assert(pAnchor->IsContentFrame()); + return static_cast<SwContentFrame const*>(pAnchor); +} + +// #i58182# +// - replace private method by corresponding static public method +bool SwObjectFormatterTextFrame::CheckMovedFwdCondition( + SwAnchoredObject& _rAnchoredObj, + SwPageFrame const& rFromPageFrame, + const bool _bAnchoredAtMasterBeforeFormatAnchor, + sal_uInt32& _noToPageNum, + bool& _boInFollow, + bool& o_rbPageHasFlysAnchoredBelowThis) +{ + const sal_uInt32 _nFromPageNum(rFromPageFrame.GetPhyPageNum()); + bool bAnchorIsMovedForward( false ); + + SwPageFrame* pPageFrameOfAnchor = _rAnchoredObj.FindPageFrameOfAnchor(); + if ( pPageFrameOfAnchor ) + { + const sal_uInt32 nPageNum = pPageFrameOfAnchor->GetPhyPageNum(); + if ( nPageNum > _nFromPageNum ) + { + _noToPageNum = nPageNum; + // Handling of special case: + // If anchor frame is move forward into a follow flow row, + // <_noToPageNum> is set to <_nFromPageNum + 1>, because it is + // possible that the anchor page frame isn't valid, because the + // page distance between master row and follow flow row is greater + // than 1. + if ( _noToPageNum > (_nFromPageNum + 1) ) + { + SwFrame* pAnchorFrame = _rAnchoredObj.GetAnchorFrameContainingAnchPos(); + if ( pAnchorFrame->IsInTab() && + pAnchorFrame->IsInFollowFlowRow() ) + { + _noToPageNum = _nFromPageNum + 1; + } + } + bAnchorIsMovedForward = true; + } + } + // #i26945# - check, if an at-paragraph|at-character + // anchored object is now anchored at a follow text frame, which will be + // on the next page. Also check, if an at-character anchored object + // is now anchored at a text frame, which is in a follow flow row, + // which will be on the next page. + if ( !bAnchorIsMovedForward && + _bAnchoredAtMasterBeforeFormatAnchor && + ((_rAnchoredObj.GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_CHAR) || + (_rAnchoredObj.GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PARA))) + { + SwFrame* pAnchorFrame = _rAnchoredObj.GetAnchorFrameContainingAnchPos(); + OSL_ENSURE( pAnchorFrame->IsTextFrame(), + "<SwObjectFormatterTextFrame::CheckMovedFwdCondition(..) - wrong type of anchor frame>" ); + SwTextFrame* pAnchorTextFrame = static_cast<SwTextFrame*>(pAnchorFrame); + bool bCheck( false ); + if ( pAnchorTextFrame->IsFollow() ) + { + bCheck = true; + } + else if( pAnchorTextFrame->IsInTab() ) + { + const SwRowFrame* pMasterRow = pAnchorTextFrame->IsInFollowFlowRow(); + if ( pMasterRow && + pMasterRow->FindPageFrame() == pPageFrameOfAnchor ) + { + bCheck = true; + } + } + if ( bCheck ) + { + // check, if found text frame will be on the next page + // by checking, if it's in a column, which has no next. + SwFrame* pColFrame = pAnchorTextFrame->FindColFrame(); + while ( pColFrame && !pColFrame->GetNext() ) + { + pColFrame = pColFrame->FindColFrame(); + } + if ( !pColFrame || !pColFrame->GetNext() ) + { + _noToPageNum = _nFromPageNum + 1; + bAnchorIsMovedForward = true; + // #i43913# + _boInFollow = true; + } + } + } + + if (bAnchorIsMovedForward) + { + // tdf#138518 try to determine if there is a fly on page rFromPageFrame + // which is anchored in a frame that is "below" the anchor frame + // of _rAnchoredObj, such that it should move to the next page before + // _rAnchoredObj does + if (auto * pObjs = rFromPageFrame.GetSortedObjs()) + { + for (SwAnchoredObject *const pObj : *pObjs) + { + SwPageFrame const*const pObjAnchorPage(pObj->FindPageFrameOfAnchor()); + assert(pObjAnchorPage); + if ((pObjAnchorPage == &rFromPageFrame + ? _boInFollow // same-page but will move forward + : rFromPageFrame.GetPhyPageNum() < pObjAnchorPage->GetPhyPageNum()) + && pObj->GetFrameFormat().GetAnchor().GetAnchorId() + != RndStdIds::FLY_AS_CHAR) + { + if (pPageFrameOfAnchor->GetPhyPageNum() < pObjAnchorPage->GetPhyPageNum()) + { + SAL_INFO("sw.layout", "SwObjectFormatterTextFrame::CheckMovedFwdCondition(): o_rbPageHasFlysAnchoredBelowThis because next page"); + o_rbPageHasFlysAnchoredBelowThis = true; + break; + } + // on same page: check if it's in next-chain in the document body + // (in case both are in the same fly the flag must not be + // set because the whole fly moves at once) + SwContentFrame const*const pInBodyFrameObj(FindFrameInBody(*pObj)); + SwContentFrame const*const pInBodyFrameAnchoredObj(FindFrameInBody(_rAnchoredObj)); + if (pInBodyFrameObj && pInBodyFrameAnchoredObj) + { + bool isBreakMore(false); + // currently this ignores index of at-char flys + for (SwContentFrame const* pContentFrame = pInBodyFrameAnchoredObj->FindNextCnt(); + pContentFrame; + pContentFrame = pContentFrame->FindNextCnt()) + { + if (pInBodyFrameObj == pContentFrame) + { + // subsequent cells in a row are not automatically + // "below" and the row could potentially be split + // TODO refine check if needed + if (!pInBodyFrameAnchoredObj->IsInTab() + || FindTopLevelRowFrame(pInBodyFrameAnchoredObj) + != FindTopLevelRowFrame(pInBodyFrameAnchoredObj)) + { // anchored in next chain on same page + SAL_INFO("sw.layout", "SwObjectFormatterTextFrame::CheckMovedFwdCondition(): o_rbPageHasFlysAnchoredBelowThis because next chain on same page"); + o_rbPageHasFlysAnchoredBelowThis = true; + isBreakMore = true; + } + break; + } + } + if (isBreakMore) + { + break; + } + } + } + } + } + } + + return bAnchorIsMovedForward; +} + +static void CleanupEmptyFootnoteFrame(SwFrame* pLowerFrame) +{ + // Calc on a SwTextFrame in a footnote can move it to the next page - + // deletion of the SwFootnoteFrame was disabled with SwFrameDeleteGuard + // but now we have to clean up empty footnote frames to prevent crashes. + // Note: check it at this level, not lower: both container and footnote + // can be deleted at the same time! + if (!pLowerFrame->IsFootnoteContFrame()) + return; + + for (SwFrame * pFootnote = pLowerFrame->GetLower(); pFootnote; ) + { + assert(pFootnote->IsFootnoteFrame()); + SwFrame *const pNextNote = pFootnote->GetNext(); + if (!pFootnote->IsDeleteForbidden() && !pFootnote->GetLower() && !pFootnote->IsColLocked() && + !static_cast<SwFootnoteFrame*>(pFootnote)->IsBackMoveLocked()) + { + pFootnote->Cut(); + SwFrame::DestroyFrame(pFootnote); + } + pFootnote = pNextNote; + } +} + +// #i40140# - helper method to format layout frames used by +// method <SwObjectFormatterTextFrame::FormatAnchorFrameForCheckMoveFwd()> +// #i44049# - format till a certain lower frame, if provided. +static void lcl_FormatContentOfLayoutFrame( SwLayoutFrame* pLayFrame, + SwFrame* pLastLowerFrame = nullptr ) +{ + SwFrame* pLowerFrame = pLayFrame->GetLower(); + while ( pLowerFrame ) + { + // #i44049# + if ( pLastLowerFrame && pLowerFrame == pLastLowerFrame ) + { + break; + } + if ( pLowerFrame->IsLayoutFrame() ) + { + SwFrameDeleteGuard aCrudeHack(pLowerFrame); // ??? any issue setting this for non-footnote frames? + lcl_FormatContentOfLayoutFrame( static_cast<SwLayoutFrame*>(pLowerFrame), + pLastLowerFrame ); + } + else + pLowerFrame->Calc(pLowerFrame->getRootFrame()->GetCurrShell()->GetOut()); + + // Calc on a SwTextFrame in a footnote can move it to the next page - + // deletion of the SwFootnoteFrame was disabled with SwFrameDeleteGuard + // but now we have to clean up empty footnote frames to prevent crashes. + // Note: check it at this level, not lower: both container and footnote + // can be deleted at the same time! + SwFrame *const pNext = pLowerFrame->GetNext(); + CleanupEmptyFootnoteFrame(pLowerFrame); + pLowerFrame = pNext; + } +} + +/** method to format given anchor text frame and its previous frames + + #i56300# + Usage: Needed to check, if the anchor text frame is moved forward + due to the positioning and wrapping of its anchored objects, and + to format the frames, which have become invalid due to the anchored + object formatting in the iterative object positioning algorithm +*/ +void SwObjectFormatterTextFrame::FormatAnchorFrameAndItsPrevs( SwTextFrame& _rAnchorTextFrame ) +{ + // #i47014# - no format of section and previous columns + // for follow text frames. + if ( !_rAnchorTextFrame.IsFollow() ) + { + // In case the anchor frame is in a column or section, format its + // previous frames first - but don't jump out of the current layout + // environment, e.g. from footnotes into the footnote boss. + SwFrame * pSectFrame(nullptr); + SwFrame * pColFrameOfAnchor(nullptr); + for (SwFrame* pUpper = _rAnchorTextFrame.GetUpper(); + pUpper != nullptr; pUpper = pUpper->GetUpper()) + { + if (pUpper->IsCellFrame()) + { + break; // apparently nothing to be done? + } + if (pUpper->IsFootnoteFrame()) + { + SAL_INFO_IF(pColFrameOfAnchor == nullptr && pUpper->FindColFrame(), + "sw.layout", "tdf#122894 skipping column for footnote in column"); + break; // stop: prevent crash in case footnotes are being moved + } + if (pUpper->IsSctFrame()) + { + pColFrameOfAnchor = nullptr; + pSectFrame = pUpper; + break; + } + if (pColFrameOfAnchor != nullptr) + { // parent of column not a section frame => column not in section + break; + } + if (pUpper->IsColumnFrame()) + { + pColFrameOfAnchor = pUpper; + } + } + + // if anchor frame is directly inside a section, format this section and + // its previous frames. + // Note: It's a very simple format without formatting objects. + if (pSectFrame) + { + assert(pSectFrame->IsSctFrame()); + { + SwFrameDeleteGuard aDeleteGuard(&_rAnchorTextFrame); + // #i44049# + _rAnchorTextFrame.LockJoin(); + SwFrame* pFrame = pSectFrame->GetUpper()->GetLower(); + // #i49605# - section frame could move forward + // by the format of its previous frame. + // Thus, check for valid <pFrame>. + while ( pFrame && pFrame != pSectFrame ) + { + SwFrameDeleteGuard aDeleteFrameGuard(pFrame); + + if ( pFrame->IsLayoutFrame() ) + lcl_FormatContentOfLayoutFrame( static_cast<SwLayoutFrame*>(pFrame) ); + else + pFrame->Calc(pFrame->getRootFrame()->GetCurrShell()->GetOut()); + + pFrame = pFrame->GetNext(); + } + lcl_FormatContentOfLayoutFrame( static_cast<SwLayoutFrame*>(pSectFrame), + &_rAnchorTextFrame ); + // #i44049# + _rAnchorTextFrame.UnlockJoin(); + } + } + + // #i40140# - if anchor frame is inside a column, + // format the content of the previous columns. + // Note: It's a very simple format without formatting objects. + if (pColFrameOfAnchor) + { + assert(pColFrameOfAnchor->IsColumnFrame()); + // #i44049# + _rAnchorTextFrame.LockJoin(); + SwFrameDeleteGuard aDeleteGuard(&_rAnchorTextFrame); + SwFrame* pColFrame = pColFrameOfAnchor->GetUpper()->GetLower(); + while ( pColFrame != pColFrameOfAnchor ) + { + SwFrame* pFrame = pColFrame->GetLower(); + while ( pFrame ) + { + if ( pFrame->IsLayoutFrame() ) + lcl_FormatContentOfLayoutFrame( static_cast<SwLayoutFrame*>(pFrame) ); + else + pFrame->Calc(pFrame->getRootFrame()->GetCurrShell()->GetOut()); + + pFrame = pFrame->GetNext(); + } + + pColFrame = pColFrame->GetNext(); + } + // #i44049# + _rAnchorTextFrame.UnlockJoin(); + } + } + + // format anchor frame - format of its follow not needed + // #i43255# - forbid follow format, only if anchor text + // frame is in table + if ( _rAnchorTextFrame.IsInTab() ) + { + SwForbidFollowFormat aForbidFollowFormat( _rAnchorTextFrame ); + _rAnchorTextFrame.Calc(_rAnchorTextFrame.getRootFrame()->GetCurrShell()->GetOut()); + } + else + { + _rAnchorTextFrame.Calc(_rAnchorTextFrame.getRootFrame()->GetCurrShell()->GetOut()); + } +} + +/** method to format the anchor frame for checking of the move forward condition + + #i40141# +*/ +void SwObjectFormatterTextFrame::FormatAnchorFrameForCheckMoveFwd() +{ + SwObjectFormatterTextFrame::FormatAnchorFrameAndItsPrevs( mrAnchorTextFrame ); +} + +/** method to determine if at least one anchored object has state + <temporarily consider wrapping style influence> set. +*/ +bool SwObjectFormatterTextFrame::AtLeastOneObjIsTmpConsiderWrapInfluence() +{ + bool bRet( false ); + + const SwSortedObjs* pObjs = GetAnchorFrame().GetDrawObjs(); + if ( pObjs && pObjs->size() > 1 ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos() ) + { + bRet = true; + break; + } + } + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/objectformattertxtfrm.hxx b/sw/source/core/layout/objectformattertxtfrm.hxx new file mode 100644 index 000000000..25a7a7e92 --- /dev/null +++ b/sw/source/core/layout/objectformattertxtfrm.hxx @@ -0,0 +1,190 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_LAYOUT_OBJECTFORMATTERTXTFRM_HXX +#define INCLUDED_SW_SOURCE_CORE_LAYOUT_OBJECTFORMATTERTXTFRM_HXX + +#include <objectformatter.hxx> +#include <sal/types.h> + +class SwTextFrame; + +// #i28701# +// Format floating screen objects, which are anchored at a given anchor text frame +// and registered at the given page frame. +class SwObjectFormatterTextFrame : public SwObjectFormatter +{ + private: + // anchor text frame + SwTextFrame& mrAnchorTextFrame; + + // 'master' anchor text frame + SwTextFrame* mpMasterAnchorTextFrame; + + SwObjectFormatterTextFrame( SwTextFrame& _rAnchorTextFrame, + const SwPageFrame& _rPageFrame, + SwTextFrame* _pMasterAnchorTextFrame, + SwLayAction* _pLayAction ); + + /** method to invalidate objects, anchored previous to given object at + the anchor text frame + + @param _rAnchoredObj + reference to anchored object - objects, anchored previous to + this one will be invalidated. + */ + void InvalidatePrevObjs( SwAnchoredObject& _rAnchoredObj ); + + /** method to invalidate objects, anchored after the given object at + the page frame + + @param _rAnchoredObj + reference to anchored object - objects, anchored after this one will + be invalidated. + */ + void InvalidateFollowObjs( SwAnchoredObject& _rAnchoredObj ); + + /** method to determine first anchored object, whose 'anchor is moved + forward'. + + 'anchor (of an object) is moved forward', if the anchor frame + respectively the anchor character of the object isn't on the + proposed page frame. Instead its on a following page + + #i26945# - For at-character anchored objects, + it has also to be checked, if the anchor character is in a follow + text frame, which would move to the next page. + + #i43913# - add output parameter <_boInFollow> + + @param _nWrapInfluenceOnPosition + input parameter - only object with this given wrapping style + influence are investigated. + + @param _nFromPageNum + input parameter - number of page frame, the 'anchor' should be + + @param _noToPageNum + output parameter - number of page frame, the 'anchor' of the returned + anchored object is. + + @param _boInFollow + output parameter - boolean, indicating that anchor text frame is + currently on the same page, but it's a follow of in a follow row, + which will move forward. value only relevant, if method returns + an anchored object + + @return SwAnchoredObject* + anchored object with a 'moved forward anchor'. If NULL, no such + anchored object is found. + */ + SwAnchoredObject* GetFirstObjWithMovedFwdAnchor( + const sal_Int16 _nWrapInfluenceOnPosition, + sal_uInt32& _noToPageNum, + bool& _boInFollow, + bool& o_rbPageHasFlysAnchoredBelowThis); + + /** method to format the anchor frame for checking of the move forward condition + + #i40141# + */ + void FormatAnchorFrameForCheckMoveFwd(); + + /** method to determine if at least one anchored object has state + <temporarily consider wrapping style influence> set. + */ + bool AtLeastOneObjIsTmpConsiderWrapInfluence(); + + protected: + + virtual SwFrame& GetAnchorFrame() override; + + public: + virtual ~SwObjectFormatterTextFrame() override; + + // #i40147# - add parameter <_bCheckForMovedFwd>. + virtual bool DoFormatObj( SwAnchoredObject& _rAnchoredObj, + const bool _bCheckForMovedFwd = false ) override; + virtual bool DoFormatObjs() override; + + /** method to create an instance of <SwObjectFormatterTextFrame> is + necessary. + */ + static std::unique_ptr<SwObjectFormatterTextFrame> CreateObjFormatter( + SwTextFrame& _rAnchorTextFrame, + const SwPageFrame& _rPageFrame, + SwLayAction* _pLayAction ); + + /** method to format given anchor text frame and its previous frames + + #i56300# + Usage: Needed to check, if the anchor text frame is moved forward + due to the positioning and wrapping of its anchored objects, and + to format the frames, which have become invalid due to the anchored + object formatting in the iterative object positioning algorithm + + @param _rAnchorTextFrame + input parameter - reference to anchor text frame, which has to be + formatted including its previous frames of the page. + */ + static void FormatAnchorFrameAndItsPrevs( SwTextFrame& _rAnchorTextFrame ); + + /** method to check the conditions, if 'anchor is moved forward' + + #i26945# + #i43913# - add output parameter <_boInFollow> + #i58182# - replace method by a corresponding static + method, because it's needed for the iterative positioning algorithm. + + @param _rAnchoredObj + input parameter - anchored object, for which the condition has to checked. + + @param _nFromPageNum + input parameter - number of the page, on which the check is performed + + @param _bAnchoredAtMasterBeforeFormatAnchor + input parameter - boolean indicating, that the given anchored object + was anchored at the master frame before the anchor frame has been + formatted. + + @param _noToPageNum + output parameter - number of page frame, the 'anchor' of the returned + anchored object is. + + @param _boInFollow + output parameter - boolean, indicating that anchor text frame is + currently on the same page, but it's a follow of in a follow row, + which will move forward. value only relevant, if method return <true>. + @param o_rbPageHasFlysAnchoredBelowThis + output parameter - indicates that the page has flys anchored + somewhere below the anchor of the passed _rAnchoredObj + + @return boolean + indicating, if 'anchor is moved forward' + */ + static bool CheckMovedFwdCondition( SwAnchoredObject& _rAnchoredObj, + SwPageFrame const& rFromPageFrame, + const bool _bAnchoredAtMasterBeforeFormatAnchor, + sal_uInt32& _noToPageNum, + bool& _boInFollow, + bool& o_rbPageHasFlysAnchoredBelowThis); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/objstmpconsiderwrapinfl.cxx b/sw/source/core/layout/objstmpconsiderwrapinfl.cxx new file mode 100644 index 000000000..e35336b9f --- /dev/null +++ b/sw/source/core/layout/objstmpconsiderwrapinfl.cxx @@ -0,0 +1,60 @@ +/* -*- 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 "objstmpconsiderwrapinfl.hxx" +#include <anchoredobject.hxx> + +SwObjsMarkedAsTmpConsiderWrapInfluence::SwObjsMarkedAsTmpConsiderWrapInfluence() +{ +} + +SwObjsMarkedAsTmpConsiderWrapInfluence::~SwObjsMarkedAsTmpConsiderWrapInfluence() +{ + Clear(); +} + +void SwObjsMarkedAsTmpConsiderWrapInfluence::Insert( SwAnchoredObject& _rAnchoredObj ) +{ + auto it = std::find(maObjsTmpConsiderWrapInfl.begin(), maObjsTmpConsiderWrapInfl.end(), &_rAnchoredObj); + if (it != maObjsTmpConsiderWrapInfl.end()) + return; + maObjsTmpConsiderWrapInfl.push_back( &_rAnchoredObj ); +} + +void SwObjsMarkedAsTmpConsiderWrapInfluence::Remove( SwAnchoredObject& _rAnchoredObj ) +{ + auto it = std::find(maObjsTmpConsiderWrapInfl.begin(), maObjsTmpConsiderWrapInfl.end(), &_rAnchoredObj); + if (it == maObjsTmpConsiderWrapInfl.end()) + return; + maObjsTmpConsiderWrapInfl.erase(it); +} + +void SwObjsMarkedAsTmpConsiderWrapInfluence::Clear() +{ + while ( !maObjsTmpConsiderWrapInfl.empty() ) + { + SwAnchoredObject* pAnchoredObj = maObjsTmpConsiderWrapInfl.back(); + pAnchoredObj->SetTmpConsiderWrapInfluence( false ); + pAnchoredObj->SetClearedEnvironment( false ); + + maObjsTmpConsiderWrapInfl.pop_back(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/objstmpconsiderwrapinfl.hxx b/sw/source/core/layout/objstmpconsiderwrapinfl.hxx new file mode 100644 index 000000000..28b6acf9d --- /dev/null +++ b/sw/source/core/layout/objstmpconsiderwrapinfl.hxx @@ -0,0 +1,42 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_LAYOUT_OBJSTMPCONSIDERWRAPINFL_HXX +#define INCLUDED_SW_SOURCE_CORE_LAYOUT_OBJSTMPCONSIDERWRAPINFL_HXX + +#include <vector> + +class SwAnchoredObject; + +class SwObjsMarkedAsTmpConsiderWrapInfluence +{ + private: + std::vector< SwAnchoredObject* > maObjsTmpConsiderWrapInfl; + + public: + SwObjsMarkedAsTmpConsiderWrapInfluence(); + ~SwObjsMarkedAsTmpConsiderWrapInfluence(); + + void Insert( SwAnchoredObject& _rAnchoredObj ); + void Remove( SwAnchoredObject& _rAnchoredObj ); + void Clear(); +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/pagechg.cxx b/sw/source/core/layout/pagechg.cxx new file mode 100644 index 000000000..42720ee19 --- /dev/null +++ b/sw/source/core/layout/pagechg.cxx @@ -0,0 +1,2614 @@ +/* -*- 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 <config_wasm_strip.h> + +#include <comphelper/lok.hxx> +#include <ndole.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <svl/itemiter.hxx> +#include <fmtfsize.hxx> +#include <fmthdft.hxx> +#include <fmtclds.hxx> +#include <fmtpdsc.hxx> +#include <fmtornt.hxx> +#include <fmtsrnd.hxx> +#include <ftninfo.hxx> +#include <frmtool.hxx> +#include <tgrditem.hxx> +#include <viewopt.hxx> +#include <docsh.hxx> +#include <wrtsh.hxx> +#include <view.hxx> +#include <edtwin.hxx> +#include <frameformats.hxx> + +#include <viewimp.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <dcontact.hxx> +#include <hints.hxx> +#include <FrameControlsManager.hxx> + +#include <ftnidx.hxx> +#include <bodyfrm.hxx> +#include <ftnfrm.hxx> +#include <tabfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <layact.hxx> +#include <flyfrms.hxx> +#include <htmltbl.hxx> +#include <pagedesc.hxx> +#include <editeng/frmdiritem.hxx> +#include <sortedobjs.hxx> +#include <calbck.hxx> +#include <txtfly.hxx> + +using namespace ::com::sun::star; + +SwBodyFrame::SwBodyFrame( SwFrameFormat *pFormat, SwFrame* pSib ): + SwLayoutFrame( pFormat, pSib ) +{ + mnFrameType = SwFrameType::Body; +} + +void SwBodyFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs * ) +{ + // Formatting of the body is too simple, thus, it gets its own format method. + // Borders etc. are not taken into account here. + // Width is taken from the PrtArea of the Upper. Height is the height of the + // PrtArea of the Upper minus any neighbors (for robustness). + // The PrtArea has always the size of the frame. + + if ( !isFrameAreaSizeValid() ) + { + SwTwips nHeight = GetUpper()->getFramePrintArea().Height(); + SwTwips nWidth = GetUpper()->getFramePrintArea().Width(); + const SwFrame *pFrame = GetUpper()->Lower(); + do + { + if ( pFrame != this ) + { + if( pFrame->IsVertical() ) + nWidth -= pFrame->getFrameArea().Width(); + else + nHeight -= pFrame->getFrameArea().Height(); + } + pFrame = pFrame->GetNext(); + } while ( pFrame ); + + if ( nHeight < 0 ) + { + nHeight = 0; + } + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( nHeight ); + + if( IsVertical() && !IsVertLR() && nWidth != aFrm.Width() ) + { + aFrm.Pos().setX(aFrm.Pos().getX() + aFrm.Width() - nWidth); + } + + aFrm.Width( nWidth ); + } + + bool bNoGrid = true; + if( GetUpper()->IsPageFrame() && static_cast<SwPageFrame*>(GetUpper())->HasGrid() ) + { + SwTextGridItem const*const pGrid( + GetGridItem(static_cast<SwPageFrame*>(GetUpper()))); + if( pGrid ) + { + bNoGrid = false; + tools::Long nSum = pGrid->GetBaseHeight() + pGrid->GetRubyHeight(); + SwRectFnSet aRectFnSet(this); + tools::Long nSize = aRectFnSet.GetWidth(getFrameArea()); + tools::Long nBorder = 0; + if( GRID_LINES_CHARS == pGrid->GetGridType() ) + { + //for textgrid refactor + SwDoc *pDoc = GetFormat()->GetDoc(); + nBorder = nSize % (GetGridWidth(*pGrid, *pDoc)); + nSize -= nBorder; + nBorder /= 2; + } + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetPosX( aPrt, nBorder ); + aRectFnSet.SetWidth( aPrt, nSize ); + + // Height of body frame: + nBorder = aRectFnSet.GetHeight(getFrameArea()); + + // Number of possible lines in area of body frame: + tools::Long nNumberOfLines = nBorder / nSum; + if( nNumberOfLines > pGrid->GetLines() ) + nNumberOfLines = pGrid->GetLines(); + + // Space required for nNumberOfLines lines: + nSize = nNumberOfLines * nSum; + nBorder -= nSize; + nBorder /= 2; + + // #i21774# Footnotes and centering the grid does not work together: + const bool bAdjust = static_cast<SwPageFrame*>(GetUpper())->GetFormat()->GetDoc()-> + GetFootnoteIdxs().empty(); + + aRectFnSet.SetPosY( aPrt, bAdjust ? nBorder : 0 ); + aRectFnSet.SetHeight( aPrt, nSize ); + } + } + + if( bNoGrid ) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Pos().setX(0); + aPrt.Pos().setY(0); + aPrt.Height( getFrameArea().Height() ); + aPrt.Width( getFrameArea().Width() ); + } + + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); +} + +SwPageFrame::SwPageFrame( SwFrameFormat *pFormat, SwFrame* pSib, SwPageDesc *pPgDsc ) : + SwFootnoteBossFrame( pFormat, pSib ), + m_pDesc( pPgDsc ), + m_nPhyPageNum( 0 ) +{ + SetDerivedVert( false ); + SetDerivedR2L( false ); + if( m_pDesc ) + { + m_bHasGrid = true; + SwTextGridItem const*const pGrid(GetGridItem(this)); + if( !pGrid ) + m_bHasGrid = false; + } + else + m_bHasGrid = false; + SetMaxFootnoteHeight( pPgDsc->GetFootnoteInfo().GetHeight() ? + pPgDsc->GetFootnoteInfo().GetHeight() : LONG_MAX ); + mnFrameType = SwFrameType::Page; + m_bInvalidLayout = m_bInvalidContent = m_bInvalidSpelling = m_bInvalidSmartTags = m_bInvalidAutoCmplWrds = m_bInvalidWordCount = true; + m_bInvalidFlyLayout = m_bInvalidFlyContent = m_bInvalidFlyInCnt = m_bFootnotePage = m_bEndNotePage = false; + + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + vcl::RenderContext* pRenderContext = pSh ? pSh->GetOut() : nullptr; + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + if ( bBrowseMode ) + { + aFrm.Height( 0 ); + tools::Long nWidth = pSh->VisArea().Width(); + + if ( !nWidth ) + { + nWidth = 5000; // changes anyway + } + + aFrm.Width ( nWidth ); + } + else + { + aFrm.SSize( pFormat->GetFrameSize().GetSize() ); + } + } + + // create and insert body area if it is not a blank page + SwDoc* pDoc(pFormat->GetDoc()); + m_bEmptyPage = (pFormat == pDoc->GetEmptyPageFormat()); + + if(m_bEmptyPage) + { + return; + } + + Calc(pRenderContext); // so that the PrtArea is correct + SwBodyFrame *pBodyFrame = new SwBodyFrame( pDoc->GetDfltFrameFormat(), this ); + pBodyFrame->ChgSize( getFramePrintArea().SSize() ); + pBodyFrame->Paste( this ); + pBodyFrame->Calc(pRenderContext); // so that the columns can be inserted correctly + pBodyFrame->InvalidatePos(); + + if ( bBrowseMode ) + InvalidateSize_(); + + // insert header/footer,, but only if active. + if ( pFormat->GetHeader().IsActive() ) + PrepareHeader(); + if ( pFormat->GetFooter().IsActive() ) + PrepareFooter(); + + const SwFormatCol &rCol = pFormat->GetCol(); + if ( rCol.GetNumCols() > 1 ) + { + const SwFormatCol aOld; //ChgColumns() needs an old value + pBodyFrame->ChgColumns( aOld, rCol ); + } + +} + +void SwPageFrame::DestroyImpl() +{ + // Cleanup the header-footer controls in all SwEditWins + SwViewShell* pSh = getRootFrame()->GetCurrShell(); + if (pSh) + { + for (SwViewShell& rSh : pSh->GetRingContainer()) + { + SwWrtShell* pWrtSh = dynamic_cast< SwWrtShell* >( &rSh ); + if ( pWrtSh ) + { + SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin(); + rEditWin.GetFrameControlsManager( ).RemoveControls( this ); + } + } + } + + // empty FlyContainer, deletion of the Flys is done by the anchor (in base class SwFrame) + if (m_pSortedObjs) + { + // Objects can be anchored at pages that are before their anchors (why ever...). + // In such cases, we would access already freed memory. + for (SwAnchoredObject* pAnchoredObj : *m_pSortedObjs) + { + pAnchoredObj->SetPageFrame( nullptr ); + } + m_pSortedObjs.reset(); // reset to zero to prevent problems when detaching the Flys + } + + // prevent access to destroyed pages + SwDoc *pDoc = GetFormat() ? GetFormat()->GetDoc() : nullptr; + if( pDoc && !pDoc->IsInDtor() ) + { + if ( pSh ) + { + SwViewShellImp *pImp = pSh->Imp(); + pImp->SetFirstVisPageInvalid(); + if ( pImp->IsAction() ) + pImp->GetLayAction().SetAgain(true); + // #i9719# - retouche area of page + // including border and shadow area. + const bool bRightSidebar = (SidebarPosition() == sw::sidebarwindows::SidebarPosition::RIGHT); + SwRect aRetoucheRect; + SwPageFrame::GetBorderAndShadowBoundRect( getFrameArea(), pSh, pSh->GetOut(), aRetoucheRect, IsLeftShadowNeeded(), IsRightShadowNeeded(), bRightSidebar ); + pSh->AddPaintRect( aRetoucheRect ); + } + } + + SwFootnoteBossFrame::DestroyImpl(); +} + +SwPageFrame::~SwPageFrame() +{ +} + +void SwPageFrame::CheckGrid( bool bInvalidate ) +{ + bool bOld = m_bHasGrid; + m_bHasGrid = true; + SwTextGridItem const*const pGrid(GetGridItem(this)); + m_bHasGrid = nullptr != pGrid; + if( !(bInvalidate || bOld != m_bHasGrid) ) + return; + + SwLayoutFrame* pBody = FindBodyCont(); + if( pBody ) + { + pBody->InvalidatePrt(); + SwContentFrame* pFrame = pBody->ContainsContent(); + while( pBody->IsAnLower( pFrame ) ) + { + static_cast<SwTextFrame*>(pFrame)->Prepare(); + pFrame = pFrame->GetNextContentFrame(); + } + } + SetCompletePaint(); +} + +void SwPageFrame::CheckDirection( bool bVert ) +{ + SvxFrameDirection nDir = GetFormat()->GetFormatAttr( RES_FRAMEDIR ).GetValue(); + if( bVert ) + { + if( SvxFrameDirection::Horizontal_LR_TB == nDir || SvxFrameDirection::Horizontal_RL_TB == nDir ) + { + mbVertLR = false; + mbVertical = false; + } + else + { + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + { + mbVertLR = false; + mbVertical = false; + } + else + { + mbVertical = true; + + if(SvxFrameDirection::Vertical_RL_TB == nDir) + mbVertLR = false; + else if(SvxFrameDirection::Vertical_LR_TB==nDir) + mbVertLR = true; + } + } + + mbInvalidVert = false; + } + else + { + if( SvxFrameDirection::Horizontal_RL_TB == nDir ) + mbRightToLeft = true; + else + mbRightToLeft = false; + mbInvalidR2L = false; + } +} + +/// create specific Flys for this page and format generic content +static void lcl_FormatLay( SwLayoutFrame *pLay ) +{ + vcl::RenderContext* pRenderContext = pLay->getRootFrame()->GetCurrShell()->GetOut(); + // format all LayoutFrames - no tables, Flys etc. + + SwFrame *pTmp = pLay->Lower(); + // first the low-level ones + while ( pTmp ) + { + const SwFrameType nTypes = SwFrameType::Root | SwFrameType::Page | SwFrameType::Column + | SwFrameType::Header | SwFrameType::Footer | SwFrameType::FtnCont + | SwFrameType::Ftn | SwFrameType::Body; + if ( pTmp->GetType() & nTypes ) + ::lcl_FormatLay( static_cast<SwLayoutFrame*>(pTmp) ); + pTmp = pTmp->GetNext(); + } + pLay->Calc(pRenderContext); +} + +/// Create Flys or register draw objects +static void lcl_MakeObjs( const SwFrameFormats &rTable, SwPageFrame *pPage ) +{ + // formats are in the special table of the document + + for ( size_t i = 0; i < rTable.size(); ++i ) + { + SwFrameFormat *pFormat = rTable[i]; + const SwFormatAnchor &rAnch = pFormat->GetAnchor(); + if ( rAnch.GetPageNum() == pPage->GetPhyPageNum() ) + { + if( rAnch.GetContentAnchor() ) + { + if (RndStdIds::FLY_AT_PAGE == rAnch.GetAnchorId()) + { + SwFormatAnchor aAnch( rAnch ); + aAnch.SetAnchor( nullptr ); + pFormat->SetFormatAttr( aAnch ); + } + else + continue; + } + + // is it a border or a SdrObject? + bool bSdrObj = RES_DRAWFRMFMT == pFormat->Which(); + SdrObject *pSdrObj = nullptr; + if ( bSdrObj && nullptr == (pSdrObj = pFormat->FindSdrObject()) ) + { + OSL_FAIL( "DrawObject not found." ); + pFormat->GetDoc()->DelFrameFormat( pFormat ); + --i; + continue; + } + // The object might be anchored to another page, e.g. when inserting + // a new page due to a page descriptor change. In such cases, the + // object needs to be moved. + // In some cases the object is already anchored to the correct page. + // This will be handled here and does not need to be coded extra. + SwPageFrame *pPg = pPage->IsEmptyPage() ? static_cast<SwPageFrame*>(pPage->GetNext()) : pPage; + if ( bSdrObj ) + { + // OD 23.06.2003 #108784# - consider 'virtual' drawing objects + SwDrawContact *pContact = + static_cast<SwDrawContact*>(::GetUserCall(pSdrObj)); + if ( auto pDrawVirtObj = dynamic_cast<SwDrawVirtObj *>( pSdrObj ) ) + { + if ( pContact ) + { + pDrawVirtObj->RemoveFromWriterLayout(); + pDrawVirtObj->RemoveFromDrawingPage(); + pPg->AppendDrawObj( *(pContact->GetAnchoredObj( pDrawVirtObj )) ); + } + } + else + { + if ( pContact->GetAnchorFrame() ) + pContact->DisconnectFromLayout( false ); + pPg->AppendDrawObj( *(pContact->GetAnchoredObj( pSdrObj )) ); + } + } + else + { + SwIterator<SwFlyFrame,SwFormat> aIter( *pFormat ); + SwFlyFrame *pFly = aIter.First(); + if ( pFly) + { + if( pFly->GetAnchorFrame() ) + pFly->AnchorFrame()->RemoveFly( pFly ); + } + else + pFly = new SwFlyLayFrame( static_cast<SwFlyFrameFormat*>(pFormat), pPg, pPg ); + pPg->AppendFly( pFly ); + ::RegistFlys( pPg, pFly ); + } + } + } +} + +void SwPageFrame::PreparePage( bool bFootnote ) +{ + SetFootnotePage( bFootnote ); + + // #i82258# + // Due to made change on OOo 2.0 code line, method <::lcl_FormatLay(..)> has + // the side effect, that the content of page header and footer are formatted. + // For this formatting it is needed that the anchored objects are registered + // at the <SwPageFrame> instance. + // Thus, first calling <::RegistFlys(..)>, then call <::lcl_FormatLay(..)> + ::RegistFlys( this, this ); + + if ( Lower() ) + { + ::lcl_FormatLay( this ); + } + + // Flys and draw objects that are still attached to the document. + // Footnote pages do not have page-bound Flys! + // There might be Flys or draw objects that want to be placed on + // empty pages, however, the empty pages ignore that and the following + // pages take care of them. + if ( !bFootnote && !IsEmptyPage() ) + { + SwDoc *pDoc = GetFormat()->GetDoc(); + + if ( GetPrev() && static_cast<SwPageFrame*>(GetPrev())->IsEmptyPage() ) + lcl_MakeObjs( *pDoc->GetSpzFrameFormats(), static_cast<SwPageFrame*>(GetPrev()) ); + lcl_MakeObjs( *pDoc->GetSpzFrameFormats(), this ); + } +} + +void SwPageFrame::SwClientNotify(const SwModify& rModify, const SfxHint& rHint) +{ + if(typeid(sw::PageFootnoteHint) == typeid(rHint)) + { + // currently the savest way: + static_cast<SwRootFrame*>(GetUpper())->SetSuperfluous(); + SetMaxFootnoteHeight(m_pDesc->GetFootnoteInfo().GetHeight()); + if(!GetMaxFootnoteHeight()) + SetMaxFootnoteHeight(LONG_MAX); + SetColMaxFootnoteHeight(); + // here, the page might be destroyed: + static_cast<SwRootFrame*>(GetUpper())->RemoveFootnotes(nullptr, false, true); + } + else if (rHint.GetId() == SfxHintId::SwLegacyModify) + { + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + if(auto pSh = getRootFrame()->GetCurrShell()) + pSh->SetFirstVisPageInvalid(); + + SwPageFrameInvFlags eInvFlags = SwPageFrameInvFlags::NONE; + if(pLegacy->m_pNew && RES_ATTRSET_CHG == pLegacy->m_pNew->Which()) + { + auto& rOldSetChg = *static_cast<const SwAttrSetChg*>(pLegacy->m_pOld); + auto& rNewSetChg = *static_cast<const SwAttrSetChg*>(pLegacy->m_pNew); + SfxItemIter aOIter(*rOldSetChg.GetChgSet()); + SfxItemIter aNIter(*rNewSetChg.GetChgSet()); + const SfxPoolItem* pOItem = aOIter.GetCurItem(); + const SfxPoolItem* pNItem = aNIter.GetCurItem(); + SwAttrSetChg aOldSet(rOldSetChg); + SwAttrSetChg aNewSet(rNewSetChg); + do + { + UpdateAttr_(pOItem, pNItem, eInvFlags, &aOldSet, &aNewSet); + pOItem = aOIter.NextItem(); + pNItem = aNIter.NextItem(); + } while(pNItem); + if(aOldSet.Count() || aNewSet.Count()) + SwLayoutFrame::SwClientNotify(rModify, sw::LegacyModifyHint(&aOldSet, &aNewSet)); + } + else + UpdateAttr_(pLegacy->m_pOld, pLegacy->m_pNew, eInvFlags); + + if (eInvFlags == SwPageFrameInvFlags::NONE) + return; + + InvalidatePage( this ); + if(eInvFlags & SwPageFrameInvFlags::InvalidatePrt) + InvalidatePrt_(); + if(eInvFlags & SwPageFrameInvFlags::SetCompletePaint) + SetCompletePaint(); + if(eInvFlags & SwPageFrameInvFlags::InvalidateNextPos && GetNext() ) + GetNext()->InvalidatePos(); + if(eInvFlags & SwPageFrameInvFlags::PrepareHeader) + PrepareHeader(); + if(eInvFlags & SwPageFrameInvFlags::PrepareFooter) + PrepareFooter(); + if(eInvFlags & SwPageFrameInvFlags::CheckGrid) + CheckGrid(bool(eInvFlags & SwPageFrameInvFlags::InvalidateGrid)); + } else + SwFrame::SwClientNotify(rModify, rHint); +} + +void SwPageFrame::UpdateAttr_( const SfxPoolItem *pOld, const SfxPoolItem *pNew, + SwPageFrameInvFlags &rInvFlags, + SwAttrSetChg *pOldSet, SwAttrSetChg *pNewSet ) +{ + bool bClear = true; + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + switch( nWhich ) + { + case RES_FMT_CHG: + { + // state of m_bEmptyPage needs to be determined newly + const bool bNewState(GetFormat() == GetFormat()->GetDoc()->GetEmptyPageFormat()); + + if(m_bEmptyPage != bNewState) + { + // copy new state + m_bEmptyPage = bNewState; + + if(nullptr == GetLower()) + { + // if we were an empty page before there is not yet a BodyArea in the + // form of a SwBodyFrame, see constructor + SwViewShell* pSh(getRootFrame()->GetCurrShell()); + vcl::RenderContext* pRenderContext(pSh ? pSh->GetOut() : nullptr); + Calc(pRenderContext); // so that the PrtArea is correct + SwBodyFrame* pBodyFrame = new SwBodyFrame(GetFormat(), this); + pBodyFrame->ChgSize(getFramePrintArea().SSize()); + pBodyFrame->Paste(this); + pBodyFrame->InvalidatePos(); + } + } + + // If the frame format is changed, several things might also change: + // 1. columns: + assert(pOld && pNew); //FMT_CHG Missing Format + const SwFormat *const pOldFormat = static_cast<const SwFormatChg*>(pOld)->pChangedFormat; + const SwFormat *const pNewFormat = static_cast<const SwFormatChg*>(pNew)->pChangedFormat; + assert(pOldFormat && pNewFormat); //FMT_CHG Missing Format + const SwFormatCol &rOldCol = pOldFormat->GetCol(); + const SwFormatCol &rNewCol = pNewFormat->GetCol(); + if( rOldCol != rNewCol ) + { + SwLayoutFrame *pB = FindBodyCont(); + assert(pB && "Page without Body."); + pB->ChgColumns( rOldCol, rNewCol ); + rInvFlags |= SwPageFrameInvFlags::CheckGrid; + } + + // 2. header and footer: + const SwFormatHeader &rOldH = pOldFormat->GetHeader(); + const SwFormatHeader &rNewH = pNewFormat->GetHeader(); + if( rOldH != rNewH ) + rInvFlags |= SwPageFrameInvFlags::PrepareHeader; + + const SwFormatFooter &rOldF = pOldFormat->GetFooter(); + const SwFormatFooter &rNewF = pNewFormat->GetFooter(); + if( rOldF != rNewF ) + rInvFlags |= SwPageFrameInvFlags::PrepareFooter; + CheckDirChange(); + + [[fallthrough]]; + } + case RES_FRM_SIZE: + { + const SwRect aOldPageFrameRect( getFrameArea() ); + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + { + setFrameAreaSizeValid(false); + // OD 28.10.2002 #97265# - Don't call <SwPageFrame::MakeAll()> + // Calculation of the page is not necessary, because its size is + // invalidated here and further invalidation is done in the + // calling method <SwPageFrame::Modify(..)> and probably by calling + // <SwLayoutFrame::SwClientNotify(..)> at the end. + // It can also causes inconsistences, because the lowers are + // adjusted, but not calculated, and a <SwPageFrame::MakeAll()> of + // a next page is called. This is performed on the switch to the + // online layout. + //MakeAll(); + } + else if (pNew) + { + const SwFormatFrameSize &rSz = nWhich == RES_FMT_CHG ? + static_cast<const SwFormatChg*>(pNew)->pChangedFormat->GetFrameSize() : + static_cast<const SwFormatFrameSize&>(*pNew); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( std::max( rSz.GetHeight(), tools::Long(MINLAY) ) ); + aFrm.Width ( std::max( rSz.GetWidth(), tools::Long(MINLAY) ) ); + } + + if ( GetUpper() ) + { + static_cast<SwRootFrame*>(GetUpper())->CheckViewLayout( nullptr, nullptr ); + } + } + // cleanup Window + if( pSh && pSh->GetWin() && aOldPageFrameRect.HasArea() ) + { + // #i9719# - consider border and shadow of + // page frame for determine 'old' rectangle - it's used for invalidating. + const bool bRightSidebar = (SidebarPosition() == sw::sidebarwindows::SidebarPosition::RIGHT); + SwRect aOldRectWithBorderAndShadow; + SwPageFrame::GetBorderAndShadowBoundRect( aOldPageFrameRect, pSh, pSh->GetOut(), aOldRectWithBorderAndShadow, + IsLeftShadowNeeded(), IsRightShadowNeeded(), bRightSidebar ); + pSh->InvalidateWindows( aOldRectWithBorderAndShadow ); + } + rInvFlags |= SwPageFrameInvFlags::InvalidatePrt | SwPageFrameInvFlags::SetCompletePaint; + if ( aOldPageFrameRect.Height() != getFrameArea().Height() ) + rInvFlags |= SwPageFrameInvFlags::InvalidateNextPos; + } + break; + + case RES_COL: + assert(pOld && pNew); //COL Missing Format + if (pOld && pNew) + { + SwLayoutFrame *pB = FindBodyCont(); + assert(pB); //page without body + pB->ChgColumns( *static_cast<const SwFormatCol*>(pOld), *static_cast<const SwFormatCol*>(pNew) ); + rInvFlags |= SwPageFrameInvFlags::SetCompletePaint | SwPageFrameInvFlags::CheckGrid; + } + break; + + case RES_HEADER: + rInvFlags |= SwPageFrameInvFlags::PrepareHeader; + break; + + case RES_FOOTER: + rInvFlags |= SwPageFrameInvFlags::PrepareFooter; + break; + case RES_TEXTGRID: + rInvFlags |= SwPageFrameInvFlags::CheckGrid | SwPageFrameInvFlags::InvalidateGrid; + break; + case RES_FRAMEDIR : + CheckDirChange(); + break; + + default: + bClear = false; + } + if ( !bClear ) + return; + + if ( pOldSet || pNewSet ) + { + if ( pOldSet ) + pOldSet->ClearItem( nWhich ); + if ( pNewSet ) + pNewSet->ClearItem( nWhich ); + } + else + { + SwModify aMod; + SwLayoutFrame::SwClientNotify(aMod, sw::LegacyModifyHint(pOld, pNew)); + } +} + +/// get information from Modify +bool SwPageFrame::GetInfo( SfxPoolItem & rInfo ) const +{ + if( RES_AUTOFMT_DOCNODE == rInfo.Which() ) + { + // a page frame exists, so use this one + return false; + } + return true; // continue searching +} + +void SwPageFrame::SetPageDesc( SwPageDesc *pNew, SwFrameFormat *pFormat ) +{ + m_pDesc = pNew; + if ( pFormat ) + SetFrameFormat( pFormat ); +} + +/* determine the right PageDesc: + * 0. from the document for footnote and endnote pages + * 1. from the first BodyContent below a page + * 2. from PageDesc of the predecessor page + * 3. from PageDesc of the previous page if blank page + * 3.1 from PageDesc of the next page if no predecessor exists + * 4. default PageDesc + * 5. In BrowseMode use the first paragraph or default PageDesc. + */ +SwPageDesc *SwPageFrame::FindPageDesc() +{ + // 0. + if ( IsFootnotePage() ) + { + SwDoc *pDoc = GetFormat()->GetDoc(); + if ( IsEndNotePage() ) + return pDoc->GetEndNoteInfo().GetPageDesc( *pDoc ); + else + return pDoc->GetFootnoteInfo().GetPageDesc( *pDoc ); + } + + SwPageDesc *pRet = nullptr; + + //5. + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + { + SwContentFrame *pFrame = GetUpper()->ContainsContent(); + while (pFrame && !pFrame->IsInDocBody()) + pFrame = pFrame->GetNextContentFrame(); + if (pFrame) + { + SwFrame *pFlow = pFrame; + if ( pFlow->IsInTab() ) + pFlow = pFlow->FindTabFrame(); + pRet = const_cast<SwPageDesc*>(pFlow->GetPageDescItem().GetPageDesc()); + } + if ( !pRet ) + pRet = &GetFormat()->GetDoc()->GetPageDesc( 0 ); + return pRet; + } + + SwFrame *pFlow = FindFirstBodyContent(); + if ( pFlow && pFlow->IsInTab() ) + pFlow = pFlow->FindTabFrame(); + + //1. + if ( pFlow ) + { + SwFlowFrame *pTmp = SwFlowFrame::CastFlowFrame( pFlow ); + if ( !pTmp->IsFollow() ) + pRet = const_cast<SwPageDesc*>(pFlow->GetPageDescItem().GetPageDesc()); + } + + //3. and 3.1 + if ( !pRet && IsEmptyPage() ) + // FME 2008-03-03 #i81544# lijian/fme: an empty page should have + // the same page description as its prev, just like after construction + // of the empty page. + pRet = GetPrev() ? static_cast<SwPageFrame*>(GetPrev())->GetPageDesc() : + GetNext() ? static_cast<SwPageFrame*>(GetNext())->GetPageDesc() : nullptr; + + //2. + if ( !pRet ) + pRet = GetPrev() ? + static_cast<SwPageFrame*>(GetPrev())->GetPageDesc()->GetFollow() : nullptr; + + //4. + if ( !pRet ) + pRet = &GetFormat()->GetDoc()->GetPageDesc( 0 ); + + OSL_ENSURE( pRet, "could not find page descriptor." ); + return pRet; +} + +// Notify if the RootFrame changes its size +void AdjustSizeChgNotify( SwRootFrame *pRoot ) +{ + const bool bOld = pRoot->IsSuperfluous(); + pRoot->mbCheckSuperfluous = false; + if ( pRoot->GetCurrShell() ) + { + for(SwViewShell& rSh : pRoot->GetCurrShell()->GetRingContainer()) + { + if( pRoot == rSh.GetLayout() ) + { + rSh.SizeChgNotify(); + if ( rSh.Imp() ) + rSh.Imp()->NotifySizeChg( pRoot->getFrameArea().SSize() ); + } + } + } + pRoot->mbCheckSuperfluous = bOld; +} + +inline void SetLastPage( SwPageFrame *pPage ) +{ + static_cast<SwRootFrame*>(pPage->GetUpper())->mpLastPage = pPage; +} + +void SwPageFrame::Cut() +{ + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( !IsEmptyPage() ) + { + if ( GetNext() ) + GetNext()->InvalidatePos(); + + // move Flys whose anchor is on a different page (draw objects are not relevant here) + if ( GetSortedObjs() ) + { + size_t i = 0; + while ( GetSortedObjs() && i < GetSortedObjs()->size() ) + { + // #i28701# + SwAnchoredObject* pAnchoredObj = (*GetSortedObjs())[i]; + + if ( auto pFly = dynamic_cast<SwFlyAtContentFrame *>( pAnchoredObj ) ) + { + SwPageFrame *pAnchPage = pFly->GetAnchorFrame() ? + pFly->AnchorFrame()->FindPageFrame() : nullptr; + if ( pAnchPage && (pAnchPage != this) ) + { + MoveFly( pFly, pAnchPage ); + pFly->InvalidateSize(); + pFly->InvalidatePos_(); + // Do not increment index, in this case + continue; + } + } + ++i; + } + } + // cleanup Window + if ( pSh && pSh->GetWin() ) + pSh->InvalidateWindows( getFrameArea() ); + } + + // decrease the root's page number + static_cast<SwRootFrame*>(GetUpper())->DecrPhyPageNums(); + SwPageFrame *pPg = static_cast<SwPageFrame*>(GetNext()); + if ( pPg ) + { + while ( pPg ) + { + --pPg->m_nPhyPageNum; + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + } + } + else + ::SetLastPage( static_cast<SwPageFrame*>(GetPrev()) ); + + SwFrame* pRootFrame = GetUpper(); + + // cut all connections + RemoveFromLayout(); + + if ( pRootFrame ) + static_cast<SwRootFrame*>(pRootFrame)->CheckViewLayout( nullptr, nullptr ); +} + +void SwPageFrame::Paste( SwFrame* pParent, SwFrame* pSibling ) +{ + OSL_ENSURE( pParent->IsRootFrame(), "Parent is no Root." ); + OSL_ENSURE( pParent, "No parent for Paste()." ); + OSL_ENSURE( pParent != this, "I'm my own parent." ); + OSL_ENSURE( pSibling != this, "I'm my own neighbour." ); + OSL_ENSURE( !GetPrev() && !GetNext() && !GetUpper(), + "I am still registered somewhere." ); + + // insert into tree structure + InsertBefore( static_cast<SwLayoutFrame*>(pParent), pSibling ); + + // increase the root's page number + static_cast<SwRootFrame*>(GetUpper())->IncrPhyPageNums(); + if( GetPrev() ) + SetPhyPageNum( static_cast<SwPageFrame*>(GetPrev())->GetPhyPageNum() + 1 ); + else + SetPhyPageNum( 1 ); + SwPageFrame *pPg = static_cast<SwPageFrame*>(GetNext()); + if ( pPg ) + { + while ( pPg ) + { + ++pPg->m_nPhyPageNum; + pPg->InvalidatePos_(); + pPg->InvalidateLayout(); + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + } + } + else + ::SetLastPage( this ); + + if( getFrameArea().Width() != pParent->getFramePrintArea().Width() ) + InvalidateSize_(); + + InvalidatePos(); + + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( pSh ) + pSh->SetFirstVisPageInvalid(); + + getRootFrame()->CheckViewLayout( nullptr, nullptr ); +} + +static void lcl_PrepFlyInCntRegister( SwContentFrame *pFrame ) +{ + pFrame->Prepare( PrepareHint::Register ); + if( !pFrame->GetDrawObjs() ) + return; + + for(SwAnchoredObject* pAnchoredObj : *pFrame->GetDrawObjs()) + { + // #i28701# + if ( auto pFly = dynamic_cast<SwFlyInContentFrame *>( pAnchoredObj ) ) + { + SwContentFrame *pCnt = pFly->ContainsContent(); + while ( pCnt ) + { + lcl_PrepFlyInCntRegister( pCnt ); + pCnt = pCnt->GetNextContentFrame(); + } + } + } +} + +void SwPageFrame::PrepareRegisterChg() +{ + SwContentFrame *pFrame = FindFirstBodyContent(); + while( pFrame ) + { + lcl_PrepFlyInCntRegister( pFrame ); + pFrame = pFrame->GetNextContentFrame(); + if( !IsAnLower( pFrame ) ) + break; + } + if( !GetSortedObjs() ) + return; + + for(SwAnchoredObject* pAnchoredObj : *GetSortedObjs()) + { + // #i28701# + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + pFrame = pFly->ContainsContent(); + while ( pFrame ) + { + ::lcl_PrepFlyInCntRegister( pFrame ); + pFrame = pFrame->GetNextContentFrame(); + } + } + } +} + +namespace sw { + +/// check if there's content on the page that requires it to exist +bool IsPageFrameEmpty(SwPageFrame const& rPage) +{ + bool bExistEssentialObjs = (nullptr != rPage.GetSortedObjs()); + if (bExistEssentialObjs) + { + // Only because the page has Flys does not mean that it is needed. If all Flys are + // attached to generic content it is also superfluous (checking DocBody should be enough) + // OD 19.06.2003 - consider that drawing objects in + // header/footer are supported now. + bool bOnlySuperfluousObjs = true; + SwSortedObjs const& rObjs = *rPage.GetSortedObjs(); + for (size_t i = 0; bOnlySuperfluousObjs && i < rObjs.size(); ++i) + { + // #i28701# + SwAnchoredObject* pAnchoredObj = rObjs[i]; + // do not consider hidden objects + if ( rPage.GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( + pAnchoredObj->GetDrawObj()->GetLayer() ) && + !pAnchoredObj->GetAnchorFrame()->FindFooterOrHeader() ) + { + bOnlySuperfluousObjs = false; + } + } + bExistEssentialObjs = !bOnlySuperfluousObjs; + } + + // optimization: check first if essential objects exist. + const SwLayoutFrame* pBody = nullptr; + if ( bExistEssentialObjs || + rPage.FindFootnoteCont() || + (nullptr != (pBody = rPage.FindBodyCont()) && + ( pBody->ContainsContent() || + // check for section frames that are being formatted on the stack + rPage.ContainsDeleteForbiddenLayFrame() || + // #i47580# + // Do not delete page if there's an empty tabframe + // left. I think it might be correct to use ContainsAny() + // instead of ContainsContent() to cover the empty-table-case, + // but I'm not fully sure, since ContainsAny() also returns + // SectionFrames. Therefore I prefer to do it the safe way: + ( pBody->Lower() && pBody->Lower()->IsTabFrame() ) ) ) ) + { + return false; + } + else + { + return true; + } +} + +} // namespace sw + +//FIXME: provide missing documentation +/** Check all pages (starting from the given one) if they use the appropriate frame format. + * + * If "wrong" pages are found, try to fix this as simple as possible. + * + * Also delete pages that don't have content on them. + * + * @param pStart the page from where to start searching + * @param bNotifyFields + * @param ppPrev + */ +void SwFrame::CheckPageDescs( SwPageFrame *pStart, bool bNotifyFields, SwPageFrame** ppPrev ) +{ + SAL_INFO( "sw.pageframe", "(CheckPageDescs in phy: " << pStart->GetPhyPageNum() ); + assert(pStart && "no starting page."); + + SwViewShell *pSh = pStart->getRootFrame()->GetCurrShell(); + SwViewShellImp *pImp = pSh ? pSh->Imp() : nullptr; + + if ( pImp && pImp->IsAction() && !pImp->GetLayAction().IsCheckPages() ) + { + pImp->GetLayAction().SetCheckPageNum( pStart->GetPhyPageNum() ); + SAL_INFO( "sw.pageframe", "CheckPageDescs out fast - via SetCheckPageNum: " + << pStart->GetPhyPageNum() << ")" ); + return; + } + + // For the update of page numbering fields, nDocPos provides + // the page position from where invalidation should start. + SwTwips nDocPos = LONG_MAX; + + SwRootFrame *pRoot = static_cast<SwRootFrame*>(pStart->GetUpper()); + SwDoc* pDoc = pStart->GetFormat()->GetDoc(); + const bool bFootnotes = !pDoc->GetFootnoteIdxs().empty(); + + SwPageFrame *pPage = pStart; + if( pPage->GetPrev() && static_cast<SwPageFrame*>(pPage->GetPrev())->IsEmptyPage() ) + pPage = static_cast<SwPageFrame*>(pPage->GetPrev()); + while ( pPage ) + { + SwPageFrame *pPrevPage = static_cast<SwPageFrame*>(pPage->GetPrev()); + SwPageFrame *pNextPage = static_cast<SwPageFrame*>(pPage->GetNext()); + + SwPageDesc *pDesc = pPage->FindPageDesc(); + /// page is intentionally empty page + bool bIsEmpty = pPage->IsEmptyPage(); + // false for intentionally empty pages, they need additional check + bool isPageFrameEmpty(!bIsEmpty && sw::IsPageFrameEmpty(*pPage)); + bool bIsOdd = pPage->OnRightPage(); + bool bWantOdd = pPage->WannaRightPage(); + bool bFirst = pPage->OnFirstPage(); + SwFrameFormat *pFormatWish = bWantOdd + ? pDesc->GetRightFormat(bFirst) : pDesc->GetLeftFormat(bFirst); + + if ( bIsOdd != bWantOdd || + pDesc != pPage->GetPageDesc() || // wrong Desc + ( pFormatWish != pPage->GetFormat() && // wrong format and + ( !bIsEmpty || pFormatWish ) // not blank /empty + ) + ) + { + // Updating a page might take a while, so check the WaitCursor + if( pImp ) + pImp->CheckWaitCursor(); + + // invalidate the field, starting from here + if ( nDocPos == LONG_MAX ) + nDocPos = pPrevPage ? pPrevPage->getFrameArea().Top() : pPage->getFrameArea().Top(); + + // Cases: + // 1. Empty page should be "normal" page -> remove empty page and take next one + // 2. Empty page should have different descriptor -> change + // 3. Normal page should be empty -> insert empty page if previous page + // is not empty, otherwise see (6). + // 4. Normal page should have different descriptor -> change + // 5. Normal page should have different format -> change + // 6. No "wish" format provided -> take the "other" format (left/right) of the PageDesc + + if ( bIsEmpty && ( pFormatWish || //1. + ( !bWantOdd && !pPrevPage ) ) ) + { + // Check all cases for the next page, so we don't oscillate empty pages + // Skip case 1 and 2, as we require a non-empty next page to save the empty page + // Case 3 is the one we actually want to predict and skip + // We can skip the empty check of case 3, as we just work on an existing next page + bool bNextWantOdd; + SwPageDesc *pNextDesc; + if ( pNextPage && !pNextPage->IsEmptyPage() && //3. + pNextPage->OnRightPage() == (bNextWantOdd = pNextPage->WannaRightPage()) && + pNextPage->GetPageDesc() == (pNextDesc = pNextPage->FindPageDesc()) ) //4. + { + bool bNextFirst = pNextPage->OnFirstPage(); + SwFrameFormat *pNextFormatWish = bNextWantOdd ? //5. + pNextDesc->GetRightFormat(bNextFirst) : pNextDesc->GetLeftFormat(bNextFirst); + if ( !pNextFormatWish ) // 6. + pNextFormatWish = bNextWantOdd ? pNextDesc->GetLeftFormat() : pNextDesc->GetRightFormat(); + if ( pNextFormatWish && pNextPage->GetFormat() == pNextFormatWish ) + { + SAL_INFO( "sw.pageframe", "CheckPageDescs phys: " << pPage->GetPhyPageNum() + << " c: 1+3 - skip next page of p: " << pPage ); + if (pPrevPage && pPage->GetPageDesc() != pPrevPage->GetPageDesc()) + pPage->SetPageDesc( pPrevPage->GetPageDesc(), nullptr ); + // We can skip the next page, as all checks were already done! + pPage = static_cast<SwPageFrame*>(pNextPage->GetNext()); + continue; + } + } + + pPage->Cut(); + bool bUpdatePrev = false; + if (ppPrev && *ppPrev == pPage) + bUpdatePrev = true; + SAL_INFO( "sw.pageframe", "CheckPageDescs phys: " << pPage->GetPhyPageNum() + << " c: 1 - destroy p: " << pPage ); + SwFrame::DestroyFrame(pPage); + if ( pStart == pPage ) + pStart = pNextPage; + pPage = pNextPage; + if (bUpdatePrev) + *ppPrev = pNextPage; + continue; + } + else if ( bIsEmpty && !pFormatWish && //2. + pDesc != pPage->GetPageDesc() ) + { + SAL_INFO( "sw.pageframe", "CheckPageDescs phys: " << pPage->GetPhyPageNum() + << " c: 2 - set desc p: " << pPage << " d: " << pDesc ); + pPage->SetPageDesc( pDesc, nullptr ); + } + else if ( !bIsEmpty && //3. + bIsOdd != bWantOdd && + ( ( !pPrevPage && !bWantOdd ) || + ( pPrevPage && !pPrevPage->IsEmptyPage() ) + ) + ) + { + if ( pPrevPage ) + pDesc = pPrevPage->GetPageDesc(); + SwPageFrame *pTmp = new SwPageFrame( pDoc->GetEmptyPageFormat(), pRoot, pDesc ); + SAL_INFO( "sw.pageframe", "CheckPageDescs phys: " << pPage->GetPhyPageNum() + << " c: 3 - insert empty p: " << pTmp << " d: " << pDesc ); + pTmp->Paste( pRoot, pPage ); + pTmp->PreparePage( false ); + pPage = pTmp; + isPageFrameEmpty = false; // don't delete it right away! + } + else if ( pPage->GetPageDesc() != pDesc ) //4. + { + SwPageDesc *pOld = pPage->GetPageDesc(); + pPage->SetPageDesc( pDesc, pFormatWish ); + SAL_INFO( "sw.pageframe", "CheckPageDescs phys: " << pPage->GetPhyPageNum() + << " c: 4 - set desc + format p: " << pPage + << " d: " << pDesc << " f: " << pFormatWish ); + if ( bFootnotes ) + { + // If specific values of the FootnoteInfo are changed, something has to happen. + // We try to limit the damage... + // If the page has no FootnoteCont it might be problematic. + // Let's hope that invalidation is enough. + SwFootnoteContFrame *pCont = pPage->FindFootnoteCont(); + if ( pCont && !(pOld->GetFootnoteInfo() == pDesc->GetFootnoteInfo()) ) + pCont->InvalidateAll_(); + } + } + else if ( pFormatWish && pPage->GetFormat() != pFormatWish ) //5. + { + pPage->SetFrameFormat( pFormatWish ); + SAL_INFO( "sw.pageframe", "CheckPageDescs phys: " << pPage->GetPhyPageNum() + << " c: 5 - set format p: " << pPage << " f: " << pFormatWish ); + } + else if ( !pFormatWish ) //6. + { + // get format with inverted logic + pFormatWish = bWantOdd ? pDesc->GetLeftFormat() : pDesc->GetRightFormat(); + if ( pFormatWish && pPage->GetFormat() != pFormatWish ) + { + pPage->SetFrameFormat( pFormatWish ); + SAL_INFO( "sw.pageframe", "CheckPageDescs phys: " << pPage->GetPhyPageNum() + << " c: 6 - set format p: " << pPage << " f: " << pFormatWish ); + } + } +#if OSL_DEBUG_LEVEL > 0 + else + { + OSL_FAIL( "CheckPageDescs, missing solution" ); + } +#endif + } + assert(!bIsEmpty || !isPageFrameEmpty); + const bool bWantRemovePage = bIsEmpty || isPageFrameEmpty; + if (bWantRemovePage && !pPage->IsDeleteForbidden()) + { + // It also might be that an empty page is not needed at all. + // However, the algorithm above cannot determine that. It is not needed if the following + // page can live without it. Do obtain that information, we need to dig deeper... + SwPageFrame *pPg = static_cast<SwPageFrame*>(pPage->GetNext()); + if (isPageFrameEmpty || !pPg || pPage->OnRightPage() == pPg->WannaRightPage()) + { + // The following page can find a FrameFormat or has no successor -> empty page not needed + SwPageFrame *pTmp = static_cast<SwPageFrame*>(pPage->GetNext()); + if (isPageFrameEmpty && pPage->GetPrev()) + { // check previous *again* vs. its new next! see "ooo321_stylepagenumber.odt" + pTmp = static_cast<SwPageFrame*>(pPage->GetPrev()); + } + pPage->Cut(); + bool bUpdatePrev = false; + if (ppPrev && *ppPrev == pPage) + bUpdatePrev = true; + SwFrame::DestroyFrame(pPage); + SAL_INFO( "sw.pageframe", "CheckPageDescs - handle bIsEmpty - destroy p: " << pPage ); + if ( pStart == pPage ) + pStart = pTmp; + pPage = pTmp; + if (bUpdatePrev) + *ppPrev = pTmp; + continue; + } + } + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + + pRoot->SetAssertFlyPages(); + SwRootFrame::AssertPageFlys( pStart ); + + if ( bNotifyFields && (!pImp || !pImp->IsUpdateExpFields()) ) + { + SwDocPosUpdate aMsgHint( nDocPos ); + pDoc->getIDocumentFieldsAccess().UpdatePageFields( &aMsgHint ); + } + +#if OSL_DEBUG_LEVEL > 0 + //1. check if two empty pages are behind one another + bool bEmpty = false; + SwPageFrame *pPg = pStart; + while ( pPg ) + { + if ( pPg->IsEmptyPage() ) + { + if ( bEmpty ) + { + OSL_FAIL( "double empty pages." ); + break; // once is enough + } + bEmpty = true; + } + else + bEmpty = false; + + pPg = static_cast<SwPageFrame*>(pPg->GetNext()); + } +#endif + SAL_INFO( "sw.pageframe", "CheckPageDescs out)" ); +} + +namespace +{ + bool isDeleteForbidden(const SwPageFrame *pDel) + { + if (pDel->IsDeleteForbidden()) + return true; + const SwLayoutFrame* pBody = pDel->FindBodyCont(); + const SwFrame* pBodyContent = pBody ? pBody->Lower() : nullptr; + return pBodyContent && pBodyContent->IsDeleteForbidden(); + } + + bool doInsertPage( SwRootFrame *pRoot, SwPageFrame **pRefSibling, + SwFrameFormat *pFormat, SwPageDesc *pDesc, + bool bFootnote, SwPageFrame **pRefPage ) + { + SwPageFrame *pPage = new SwPageFrame(pFormat, pRoot, pDesc); + SwPageFrame *pSibling = *pRefSibling; + if ( pRefPage ) + { + *pRefPage = pPage; + SAL_INFO( "sw.pageframe", "doInsertPage p: " << pPage + << " d: " << pDesc << " f: " << pFormat ); + } + else + SAL_INFO( "sw.pageframe", "doInsertPage - insert empty p: " + << pPage << " d: " << pDesc ); + pPage->Paste( pRoot, pSibling ); + + SwViewShell* pViewShell = pRoot->GetCurrShell(); + if (pViewShell && pViewShell->GetViewOptions()->IsHideWhitespaceMode()) + { + // Hide-whitespace mode does not shrink the last page, so resize the page that used to + // be the last one. + if (SwFrame* pPrevPage = pPage->GetPrev()) + { + pPrevPage->InvalidateSize(); + } + } + + pPage->PreparePage( bFootnote ); + // If the sibling has no body text, destroy it as long as it is no footnote page. + if (!pSibling) + return true; + if (pSibling->IsFootnotePage()) + return true; + if (pSibling->FindFirstBodyContent()) + return true; + + if (!pRefPage || !isDeleteForbidden(pSibling)) + { + pRoot->RemovePage( pRefSibling, SwRemoveResult::Next ) ; + return false; + } + + return true; + } +} + +SwPageFrame *SwFrame::InsertPage( SwPageFrame *pPrevPage, bool bFootnote ) +{ + SwRootFrame *pRoot = static_cast<SwRootFrame*>(pPrevPage->GetUpper()); + SwPageFrame *pSibling = static_cast<SwPageFrame*>(pPrevPage->GetNext()); + SwPageDesc *pDesc = nullptr; + + // insert right (odd) or left (even) page? + bool bNextRightPage = !pPrevPage->OnRightPage(); + bool bWishedRightPage = bNextRightPage; + + // Which PageDesc is relevant? + // For ContentFrame take the one from format if provided, + // otherwise from the Follow of the PrevPage + if ( IsFlowFrame() && !SwFlowFrame::CastFlowFrame( this )->IsFollow() ) + { + SwFormatPageDesc &rDesc = const_cast<SwFormatPageDesc&>(GetPageDescItem()); + pDesc = rDesc.GetPageDesc(); + if ( rDesc.GetNumOffset() ) + { + ::std::optional<sal_uInt16> oNumOffset = rDesc.GetNumOffset(); + bWishedRightPage = sw::IsRightPageByNumber(*pRoot, *oNumOffset); + // use the opportunity to set the flag at root + pRoot->SetVirtPageNum( true ); + } + } + if ( !pDesc ) + pDesc = pPrevPage->GetPageDesc()->GetFollow(); + + assert(pDesc && "Missing PageDesc"); + if( !(bWishedRightPage ? pDesc->GetRightFormat() : pDesc->GetLeftFormat()) ) + bWishedRightPage = !bWishedRightPage; + bool const bWishedFirst = pDesc != pPrevPage->GetPageDesc(); + + SwDoc *pDoc = pPrevPage->GetFormat()->GetDoc(); + bool bCheckPages = false; + // If there is no FrameFormat for this page, create an empty page. + if (bWishedRightPage != bNextRightPage) + { + if( doInsertPage( pRoot, &pSibling, pDoc->GetEmptyPageFormat(), + pPrevPage->GetPageDesc(), bFootnote, nullptr ) ) + bCheckPages = true; + } + SwFrameFormat *const pFormat( bWishedRightPage + ? pDesc->GetRightFormat(bWishedFirst) + : pDesc->GetLeftFormat(bWishedFirst) ); + assert(pFormat); + SwPageFrame *pPage = nullptr; + if( doInsertPage( pRoot, &pSibling, pFormat, pDesc, bFootnote, &pPage ) ) + bCheckPages = true; + + if ( pSibling ) + { + if ( bCheckPages ) + { + CheckPageDescs( pSibling, false ); + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + SwViewShellImp *pImp = pSh ? pSh->Imp() : nullptr; + if ( pImp && pImp->IsAction() && !pImp->GetLayAction().IsCheckPages() ) + { + const sal_uInt16 nNum = pImp->GetLayAction().GetCheckPageNum(); + if ( nNum == pPrevPage->GetPhyPageNum() + 1 ) + { + pImp->GetLayAction().SetCheckPageNumDirect( + pSibling->GetPhyPageNum() ); + SAL_INFO( "sw.pageframe", "InsertPage - SetCheckPageNumDirect: " + << pSibling->GetPhyPageNum() ); + } + return pPage; + } + } + else + SwRootFrame::AssertPageFlys( pSibling ); + } + + // For the update of page numbering fields, nDocPos provides + // the page position from where invalidation should start. + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( !pSh || !pSh->Imp()->IsUpdateExpFields() ) + { + SwDocPosUpdate aMsgHint( pPrevPage->getFrameArea().Top() ); + pDoc->getIDocumentFieldsAccess().UpdatePageFields( &aMsgHint ); + } + return pPage; +} + +sw::sidebarwindows::SidebarPosition SwPageFrame::SidebarPosition() const +{ + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( !pSh || pSh->GetViewOptions()->getBrowseMode() ) + { + return sw::sidebarwindows::SidebarPosition::RIGHT; + } + else + { + const bool bLTR = getRootFrame()->IsLeftToRightViewLayout(); + const bool bBookMode = pSh->GetViewOptions()->IsViewLayoutBookMode(); + const bool bRightSidebar = bLTR ? (!bBookMode || OnRightPage()) : (bBookMode && !OnRightPage()); + + return bRightSidebar + ? sw::sidebarwindows::SidebarPosition::RIGHT + : sw::sidebarwindows::SidebarPosition::LEFT; + } +} + +SwTwips SwRootFrame::GrowFrame( SwTwips nDist, bool bTst, bool ) +{ + if ( !bTst ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.AddHeight(nDist ); + } + + return nDist; +} + +SwTwips SwRootFrame::ShrinkFrame( SwTwips nDist, bool bTst, bool ) +{ + OSL_ENSURE( nDist >= 0, "nDist < 0." ); + OSL_ENSURE( nDist <= getFrameArea().Height(), "nDist greater than current size." ); + + if ( !bTst ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.AddHeight( -nDist ); + } + + return nDist; +} + +void SwRootFrame::RemovePage( SwPageFrame **pDelRef, SwRemoveResult eResult ) +{ + SwPageFrame *pDel = *pDelRef; + (*pDelRef) = static_cast<SwPageFrame*>( + eResult == SwRemoveResult::Next ? pDel->GetNext() : pDel->GetPrev() ); + if ( !GetFormat()->GetDoc()->GetFootnoteIdxs().empty() ) + RemoveFootnotes( pDel, true ); + pDel->Cut(); + SwFrame::DestroyFrame( pDel ); +} + +/// remove pages that are not needed at all +void SwRootFrame::RemoveSuperfluous() +{ + // A page is empty if the body text area has no ContentFrame, but not if there + // is at least one Fly or one footnote attached to the page. Two runs are + // needed: one for endnote pages and one for the pages of the body text. + + if ( !IsSuperfluous() ) + return; + mbCheckSuperfluous = false; + + SwPageFrame *pPage = GetLastPage(); + tools::Long nDocPos = LONG_MAX; + + // Check the corresponding last page if it is empty and stop loop at the last non-empty page. + do + { + if (!sw::IsPageFrameEmpty(*pPage)) + { + if ( pPage->IsFootnotePage() ) + { + while ( pPage->IsFootnotePage() ) + { + pPage = static_cast<SwPageFrame*>(pPage->GetPrev()); + OSL_ENSURE( pPage, "only endnote pages remain." ); + } + continue; + } + else + pPage = nullptr; + } + + if ( pPage ) + { + SAL_INFO( "sw.pageframe", "RemoveSuperfluous - DestroyFrm p: " << pPage ); + RemovePage( &pPage, SwRemoveResult::Prev ); + nDocPos = pPage ? pPage->getFrameArea().Top() : 0; + } + } while ( pPage ); + + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( nDocPos != LONG_MAX && + (!pSh || !pSh->Imp()->IsUpdateExpFields()) ) + { + SwDocPosUpdate aMsgHint( nDocPos ); + GetFormat()->GetDoc()->getIDocumentFieldsAccess().UpdatePageFields( &aMsgHint ); + } +} + +/// Ensures that enough pages exist, so that all page bound frames and draw objects can be placed +void SwRootFrame::AssertFlyPages() +{ + if ( !IsAssertFlyPages() ) + return; + mbAssertFlyPages = false; + + SwDoc *pDoc = GetFormat()->GetDoc(); + const SwFrameFormats *pTable = pDoc->GetSpzFrameFormats(); + + // what page targets the "last" Fly? + // note the needed pages in a set + sal_uInt16 nMaxPg(0); + o3tl::sorted_vector< sal_uInt16 > neededPages; + neededPages.reserve(pTable->size()); + + for ( size_t i = 0; i < pTable->size(); ++i ) + { + const SwFormatAnchor &rAnch = (*pTable)[i]->GetAnchor(); + if(!rAnch.GetContentAnchor()) + { + const sal_uInt16 nPageNum(rAnch.GetPageNum()); + + // calc MaxPage (as before) + nMaxPg = std::max(nMaxPg, nPageNum); + + // note as needed page + neededPages.insert(nPageNum); + } + } + + // How many pages exist at the moment? + // And are there EmptyPages that are needed? + SwPageFrame* pPage(static_cast<SwPageFrame*>(Lower())); + SwPageFrame* pPrevPage(nullptr); + SwPageFrame* pFirstRevivedEmptyPage(nullptr); + + while(pPage) // moved two while-conditions to break-statements (see below) + { + const sal_uInt16 nPageNum(pPage->GetPhyPageNum()); + + if(pPage->IsEmptyPage() && + nullptr != pPrevPage && + neededPages.find(nPageNum) != neededPages.end()) + { + // This is an empty page, but it *is* needed since a SwFrame + // is anchored at it directly. Initially these SwFrames are + // not fully initialized. Need to change the format of this SwFrame + // and let the ::Notify mechanism newly evaluate + // m_bEmptyPage (see SwPageFrame::UpdateAttr_). Code is taken and + // adapted from ::InsertPage (used below), this needs previous page + bool bWishedRightPage(!pPrevPage->OnRightPage()); + SwPageDesc* pDesc(pPrevPage->GetPageDesc()->GetFollow()); + assert(pDesc && "Missing PageDesc"); + + if (!(bWishedRightPage ? pDesc->GetRightFormat() : pDesc->GetLeftFormat())) + { + bWishedRightPage = !bWishedRightPage; + } + + bool const bWishedFirst(pDesc != pPrevPage->GetPageDesc()); + SwFrameFormat* pFormat(bWishedRightPage ? pDesc->GetRightFormat(bWishedFirst) : pDesc->GetLeftFormat(bWishedFirst)); + + // set SwFrameFormat, this will trigger SwPageFrame::UpdateAttr_ and re-evaluate + // m_bEmptyPage, too + pPage->SetFrameFormat(pFormat); + + if(nullptr == pFirstRevivedEmptyPage) + { + // remember first (lowest) SwPageFrame which needed correction + pFirstRevivedEmptyPage = pPage; + } + } + + // original while-condition II + if(nullptr == pPage->GetNext()) + { + break; + } + + // original while-condition III + if(static_cast< SwPageFrame* >(pPage->GetNext())->IsFootnotePage()) + { + break; + } + + pPrevPage = pPage; + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + + if ( nMaxPg > pPage->GetPhyPageNum() ) + { + for ( sal_uInt16 i = pPage->GetPhyPageNum(); i < nMaxPg; ++i ) + pPage = InsertPage( pPage, false ); + + // If the endnote pages are now corrupt, destroy them. + if ( !pDoc->GetFootnoteIdxs().empty() ) + { + pPage = static_cast<SwPageFrame*>(Lower()); + while ( pPage && !pPage->IsFootnotePage() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + + if ( pPage ) + { + SwPageDesc *pTmpDesc = pPage->FindPageDesc(); + bool isRightPage = pPage->OnRightPage(); + if ( pPage->GetFormat() != + (isRightPage ? pTmpDesc->GetRightFormat() : pTmpDesc->GetLeftFormat()) ) + RemoveFootnotes( pPage, false, true ); + } + } + } + + // if we corrected SwFrameFormat and changed one (or more) m_bEmptyPage + // flags, we need to correct evtl. currently wrong positioned SwFrame(s) + // which did think until now that these Page(s) are empty. + // After trying to correct myself I found SwRootFrame::AssertPageFlys + // directly below that already does that, so use it. + if(nullptr != pFirstRevivedEmptyPage) + { + AssertPageFlys(pFirstRevivedEmptyPage); + } + + //Remove masters that haven't been replaced yet from the list. + RemoveMasterObjs( mpDrawPage ); + +#if OSL_DEBUG_LEVEL > 0 + pPage = static_cast<SwPageFrame*>(Lower()); + while ( pPage && pPage->GetNext() && + !static_cast<SwPageFrame*>(pPage->GetNext())->IsFootnotePage() ) + { + SAL_INFO( "sw.pageframe", "AssertFlyPages p: " << pPage << " d: " << pPage->GetPageDesc() + << " f: " << pPage->GetFormat() << " virt: " << pPage->GetVirtPageNum() + << " phys: " << pPage->GetPhyPageNum() << " empty: " << pPage->IsEmptyPage() ); + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + SAL_INFO( "sw.pageframe", "AssertFlyPages p: " << pPage << " d: " << pPage->GetPageDesc() + << " f: " << pPage->GetFormat() << " virt: " << pPage->GetVirtPageNum() + << " phys: " << pPage->GetPhyPageNum() << " empty: " << pPage->IsEmptyPage() ); +#endif +} + +/// Ensure that after the given page all page-bound objects are located on the correct page +void SwRootFrame::AssertPageFlys( SwPageFrame *pPage ) +{ + SAL_INFO( "sw.pageframe", "(AssertPageFlys in" ); + while ( pPage ) + { + if (pPage->GetSortedObjs()) + { + size_t i = 0; + while ( pPage->GetSortedObjs() && i< pPage->GetSortedObjs()->size() ) + { + // #i28701# + SwFrameFormat& rFormat = (*pPage->GetSortedObjs())[i]->GetFrameFormat(); + const SwFormatAnchor &rAnch = rFormat.GetAnchor(); + const sal_uInt16 nPg = rAnch.GetPageNum(); + if ((rAnch.GetAnchorId() == RndStdIds::FLY_AT_PAGE) && + nPg != pPage->GetPhyPageNum() ) + { + SAL_INFO( "sw.pageframe", nPg << " " << pPage->GetPhyPageNum() ); + // If on the wrong page, check if previous page is empty + if( nPg && !(pPage->GetPhyPageNum()-1 == nPg && + static_cast<SwPageFrame*>(pPage->GetPrev())->IsEmptyPage()) ) + { + // It can move by itself. Just send a modify to its anchor attribute. +#if OSL_DEBUG_LEVEL > 1 + const size_t nCnt = pPage->GetSortedObjs()->size(); + rFormat.CallSwClientNotify(sw::LegacyModifyHint(nullptr, &rAnch)); + OSL_ENSURE( !pPage->GetSortedObjs() || + nCnt != pPage->GetSortedObjs()->size(), + "Object couldn't be reattached!" ); +#else + rFormat.CallSwClientNotify(sw::LegacyModifyHint(nullptr, &rAnch)); +#endif + // Do not increment index, in this case + continue; + } + } + ++i; + } + } + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + SAL_INFO( "sw.pageframe", "AssertPageFlys out)" ); +} + +Size SwRootFrame::ChgSize( const Size& aNewSize ) +{ + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.SSize(aNewSize); + } + + InvalidatePrt_(); + mbFixSize = false; + return getFrameArea().SSize(); +} + +void SwRootFrame::MakeAll(vcl::RenderContext* /*pRenderContext*/) +{ + if ( !isFrameAreaPositionValid() ) + { + setFrameAreaPositionValid(true); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Pos().setX(DOCUMENTBORDER); + aFrm.Pos().setY(DOCUMENTBORDER); + } + + if ( !isFramePrintAreaValid() ) + { + setFramePrintAreaValid(true); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Pos().setX(0); + aPrt.Pos().setY(0); + aPrt.SSize( getFrameArea().SSize() ); + } + + if ( !isFrameAreaSizeValid() ) + { + // SSize is set by the pages (Cut/Paste). + setFrameAreaSizeValid(true); + } +} + +void SwRootFrame::ImplInvalidateBrowseWidth() +{ + mbBrowseWidthValid = false; + SwFrame *pPg = Lower(); + while ( pPg ) + { + pPg->InvalidateSize(); + pPg = pPg->GetNext(); + } +} + +void SwRootFrame::ImplCalcBrowseWidth() +{ + OSL_ENSURE( GetCurrShell() && GetCurrShell()->GetViewOptions()->getBrowseMode(), + "CalcBrowseWidth and not in BrowseView" ); + + // The (minimal) with is determined from borders, tables and paint objects. + // It is calculated based on the attributes. Thus, it is not relevant how wide they are + // currently but only how wide they want to be. + // Frames and paint objects inside other objects (frames, tables) do not count. + // Borders and columns are not taken into account. + + SwFrame *pFrame = ContainsContent(); + while ( pFrame && !pFrame->IsInDocBody() ) + pFrame = static_cast<SwContentFrame*>(pFrame)->GetNextContentFrame(); + if ( !pFrame ) + return; + + mbBrowseWidthValid = true; + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + mnBrowseWidth = (!comphelper::LibreOfficeKit::isActive() && pSh)? MINLAY + 2 * pSh->GetOut()-> PixelToLogic( pSh->GetBrowseBorder() ).Width(): MIN_BROWSE_WIDTH; + + do + { + if ( pFrame->IsInTab() ) + pFrame = pFrame->FindTabFrame(); + + if ( pFrame->IsTabFrame() && + !static_cast<SwLayoutFrame*>(pFrame)->GetFormat()->GetFrameSize().GetWidthPercent() ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFrame ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + const SwFormatHoriOrient &rHori = rAttrs.GetAttrSet().GetHoriOrient(); + tools::Long nWidth = rAttrs.GetSize().Width(); + if ( nWidth < int(USHRT_MAX)-2000 && //-2k, because USHRT_MAX gets missing while trying to resize! (and cast to int to avoid -Wsign-compare due to broken USHRT_MAX on Android) + text::HoriOrientation::FULL != rHori.GetHoriOrient() ) + { + const SwHTMLTableLayout *pLayoutInfo = + static_cast<const SwTabFrame *>(pFrame)->GetTable() + ->GetHTMLTableLayout(); + if ( pLayoutInfo ) + nWidth = std::min( nWidth, pLayoutInfo->GetBrowseWidthMin() ); + + switch ( rHori.GetHoriOrient() ) + { + case text::HoriOrientation::NONE: + // OD 23.01.2003 #106895# - add 1st param to <SwBorderAttrs::CalcRight(..)> + nWidth += rAttrs.CalcLeft( pFrame ) + rAttrs.CalcRight( pFrame ); + break; + case text::HoriOrientation::LEFT_AND_WIDTH: + nWidth += rAttrs.CalcLeft( pFrame ); + break; + default: + break; + } + mnBrowseWidth = std::max( mnBrowseWidth, nWidth ); + } + } + else if ( pFrame->GetDrawObjs() ) + { + for ( size_t i = 0; i < pFrame->GetDrawObjs()->size(); ++i ) + { + // #i28701# + SwAnchoredObject* pAnchoredObj = (*pFrame->GetDrawObjs())[i]; + const SwFrameFormat& rFormat = pAnchoredObj->GetFrameFormat(); + const bool bFly = pAnchoredObj->DynCastFlyFrame() != nullptr; + if ((bFly && (FAR_AWAY == pAnchoredObj->GetObjRect().Width())) + || rFormat.GetFrameSize().GetWidthPercent()) + { + continue; + } + + tools::Long nWidth = 0; + switch ( rFormat.GetAnchor().GetAnchorId() ) + { + case RndStdIds::FLY_AS_CHAR: + nWidth = bFly ? rFormat.GetFrameSize().GetWidth() : + pAnchoredObj->GetObjRect().Width(); + break; + case RndStdIds::FLY_AT_PARA: + { + // #i33170# + // Reactivated old code because + // nWidth = pAnchoredObj->GetObjRect().Right() + // gives wrong results for objects that are still + // at position FAR_AWAY. + if ( bFly ) + { + nWidth = rFormat.GetFrameSize().GetWidth(); + const SwFormatHoriOrient &rHori = rFormat.GetHoriOrient(); + switch ( rHori.GetHoriOrient() ) + { + case text::HoriOrientation::NONE: + nWidth += rHori.GetPos(); + break; + case text::HoriOrientation::INSIDE: + case text::HoriOrientation::LEFT: + if ( text::RelOrientation::PRINT_AREA == rHori.GetRelationOrient() ) + nWidth += pFrame->getFramePrintArea().Left(); + break; + default: + break; + } + } + else + // Paint objects to not have attributes and + // are defined by their current size + nWidth = pAnchoredObj->GetObjRect().Right() - + pAnchoredObj->GetDrawObj()->GetAnchorPos().X(); + } + break; + default: /* do nothing */; + } + mnBrowseWidth = std::max( mnBrowseWidth, nWidth ); + } + } + pFrame = pFrame->FindNextCnt(); + } while ( pFrame ); +} + +void SwRootFrame::StartAllAction() +{ + if ( GetCurrShell() ) + for(SwViewShell& rSh : GetCurrShell()->GetRingContainer()) + { + if ( auto pCursorShell = dynamic_cast<SwCursorShell*>( &rSh) ) + pCursorShell->StartAction(); + else + rSh.StartAction(); + } +} + +void SwRootFrame::EndAllAction( bool bVirDev ) +{ + if ( !GetCurrShell() ) + return; + + for(SwViewShell& rSh : GetCurrShell()->GetRingContainer()) + { + const bool bOldEndActionByVirDev = rSh.IsEndActionByVirDev(); + rSh.SetEndActionByVirDev( bVirDev ); + if ( auto pCursorShell = dynamic_cast<SwCursorShell*>( &rSh) ) + { + pCursorShell->EndAction(); + pCursorShell->CallChgLnk(); + if ( auto pFEShell = dynamic_cast<SwFEShell*>( &rSh) ) + pFEShell->SetChainMarker(); + } + else + rSh.EndAction(); + rSh.SetEndActionByVirDev( bOldEndActionByVirDev ); + } +} + +void SwRootFrame::UnoRemoveAllActions() +{ + if ( !GetCurrShell() ) + return; + + for(SwViewShell& rSh : GetCurrShell()->GetRingContainer()) + { + // #i84729# + // No end action, if <SwViewShell> instance is currently in its end action. + // Recursive calls to <::EndAction()> are not allowed. + if ( !rSh.IsInEndAction() ) + { + OSL_ENSURE(!rSh.GetRestoreActions(), "Restore action count is already set!"); + bool bCursor = dynamic_cast<const SwCursorShell*>( &rSh) != nullptr; + bool bFE = dynamic_cast<const SwFEShell*>( &rSh) != nullptr; + sal_uInt16 nRestore = 0; + while( rSh.ActionCount() ) + { + if( bCursor ) + { + static_cast<SwCursorShell*>(&rSh)->EndAction(); + static_cast<SwCursorShell*>(&rSh)->CallChgLnk(); + if ( bFE ) + static_cast<SwFEShell*>(&rSh)->SetChainMarker(); + } + else + rSh.EndAction(); + nRestore++; + } + rSh.SetRestoreActions(nRestore); + } + rSh.LockView(true); + } +} + +void SwRootFrame::UnoRestoreAllActions() +{ + if ( !GetCurrShell() ) + return; + + for(SwViewShell& rSh : GetCurrShell()->GetRingContainer()) + { + sal_uInt16 nActions = rSh.GetRestoreActions(); + while( nActions-- ) + { + if ( auto pCursorShell = dynamic_cast<SwCursorShell*>( &rSh) ) + pCursorShell->StartAction(); + else + rSh.StartAction(); + } + rSh.SetRestoreActions(0); + rSh.LockView(false); + } +} + +// Helper functions for SwRootFrame::CheckViewLayout +static void lcl_MoveAllLowers( SwFrame* pFrame, const Point& rOffset ); + +static void lcl_MoveAllLowerObjs( SwFrame* pFrame, const Point& rOffset ) +{ + const bool bPage = pFrame->IsPageFrame(); + const SwSortedObjs* pSortedObj = bPage + ? static_cast<SwPageFrame*>(pFrame)->GetSortedObjs() + : pFrame->GetDrawObjs(); + if (pSortedObj == nullptr) + return; + + // note: pSortedObj elements may be removed and inserted from + // MoveObjectIfActive(), invalidating iterators + // DO NOT CONVERT THIS TO A C++11 FOR LOOP, IT DID NOT WORK THE LAST 2 TIMES + for (size_t i = 0; i < pSortedObj->size(); ++i) + { + SwAnchoredObject *const pAnchoredObj = (*pSortedObj)[i]; + const SwFrameFormat& rObjFormat = pAnchoredObj->GetFrameFormat(); + const SwFormatAnchor& rAnchor = rObjFormat.GetAnchor(); + + // all except from the as character anchored objects are moved + // when processing the page frame: + if ( !bPage && (rAnchor.GetAnchorId() != RndStdIds::FLY_AS_CHAR) ) + continue; + + SwObjPositioningInProgress aPosInProgress( *pAnchoredObj ); + + if ( auto pFlyFrame = pAnchoredObj->DynCastFlyFrame() ) + { + lcl_MoveAllLowers( pFlyFrame, rOffset ); + // tdf#138785 update position specific to as-char flys + if (pFlyFrame->IsFlyInContentFrame()) + { + static_cast<SwFlyInContentFrame*>(pFlyFrame)->AddRefOfst(rOffset); + } + pFlyFrame->NotifyDrawObj(); + // --> let the active embedded object be moved + SwFrame* pLower = pFlyFrame->Lower(); + if ( pLower && pLower->IsNoTextFrame() ) + { + SwRootFrame* pRoot = pLower->getRootFrame(); + SwViewShell *pSh = pRoot ? pRoot->GetCurrShell() : nullptr; + if ( pSh ) + { + SwNoTextFrame *const pContentFrame = static_cast<SwNoTextFrame*>(pLower); + SwOLENode* pNode = pContentFrame->GetNode()->GetOLENode(); + if ( pNode ) + { + svt::EmbeddedObjectRef& xObj = pNode->GetOLEObj().GetObject(); + if ( xObj.is() ) + { + for(SwViewShell& rSh : pSh->GetRingContainer()) + { + SwFEShell* pFEShell = dynamic_cast< SwFEShell* >( &rSh ); + if ( pFEShell ) + pFEShell->MoveObjectIfActive( xObj, rOffset ); + } + } + } + } + } + } + else if ( auto pAnchoredDrawObj = dynamic_cast<SwAnchoredDrawObject *>( pAnchoredObj ) ) + { + // don't touch objects that are not yet positioned: + if ( pAnchoredDrawObj->NotYetPositioned() ) + continue; + + const Point& aCurrAnchorPos = pAnchoredDrawObj->GetDrawObj()->GetAnchorPos(); + const Point aNewAnchorPos( aCurrAnchorPos + rOffset ); + pAnchoredDrawObj->DrawObj()->SetAnchorPos( aNewAnchorPos ); + pAnchoredDrawObj->SetLastObjRect( pAnchoredDrawObj->GetObjRect().SVRect() ); + + // clear contour cache + if ( pAnchoredDrawObj->GetFrameFormat().GetSurround().IsContour() ) + ClrContourCache( pAnchoredDrawObj->GetDrawObj() ); + } + // #i92511# + // cache for object rectangle inclusive spaces has to be invalidated. + pAnchoredObj->InvalidateObjRectWithSpaces(); + } +} + +static void lcl_MoveAllLowers( SwFrame* pFrame, const Point& rOffset ) +{ + // first move the current frame + // RotateFlyFrame3: moved to transform_translate instead of + // direct modification to allow the SwFrame evtl. needed own reactions + pFrame->transform_translate(rOffset); + + // Don't forget accessibility: +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if( pFrame->IsAccessibleFrame() ) + { + SwRootFrame *pRootFrame = pFrame->getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && + pRootFrame->GetCurrShell() ) + { + const SwRect aFrame( pFrame->getFrameArea() ); + + pRootFrame->GetCurrShell()->Imp()->MoveAccessibleFrame( pFrame, aFrame ); + } + } +#endif + + // the move any objects + lcl_MoveAllLowerObjs( pFrame, rOffset ); + + // finally, for layout frames we have to call this function recursively: + if (pFrame->IsLayoutFrame()) + { + SwFrame* pLowerFrame = pFrame->GetLower(); + while ( pLowerFrame ) + { + lcl_MoveAllLowers( pLowerFrame, rOffset ); + pLowerFrame = pLowerFrame->GetNext(); + } + } +} + +// Calculate how the pages have to be positioned +void SwRootFrame::CheckViewLayout( const SwViewOption* pViewOpt, const SwRect* pVisArea ) +{ + SwViewShell* pSh = GetCurrShell(); + vcl::RenderContext* pRenderContext = pSh ? pSh->GetOut() : nullptr; + // #i91432# + // No calculation of page positions, if only an empty page is present. + // This situation occurs when <SwRootFrame> instance is in construction + // and the document contains only left pages. + if ( Lower()->GetNext() == nullptr && + static_cast<SwPageFrame*>(Lower())->IsEmptyPage() ) + { + return; + } + + if ( !pVisArea ) + { + // no early return for bNewPage + if ( mnViewWidth < 0 ) + mnViewWidth = 0; + } + else + { + assert(pViewOpt && "CheckViewLayout required ViewOptions"); + + const sal_uInt16 nColumns = pViewOpt->GetViewLayoutColumns(); + const bool bBookMode = pViewOpt->IsViewLayoutBookMode(); + + if ( nColumns == mnColumns && bBookMode == mbBookMode && pVisArea->Width() == mnViewWidth && !mbSidebarChanged ) + return; + + mnColumns = nColumns; + mbBookMode = bBookMode; + mnViewWidth = pVisArea->Width(); + mbSidebarChanged = false; + } + + if( GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::BROWSE_MODE ) ) + { + mnColumns = 1; + mbBookMode = false; + } + + Calc(pRenderContext); + + const bool bOldCallbackActionEnabled = IsCallbackActionEnabled(); + SetCallbackActionEnabled( false ); + + maPageRects.clear(); + + const tools::Long nBorder = getFrameArea().Pos().getX(); + const tools::Long nVisWidth = mnViewWidth - 2 * nBorder; + const tools::Long nGapBetweenPages = pViewOpt ? pViewOpt->GetGapBetweenPages() + : (pSh ? pSh->GetViewOptions()->GetGapBetweenPages() + : SwViewOption::defGapBetweenPages); + + // check how many pages fit into the first page layout row: + SwPageFrame* pPageFrame = static_cast<SwPageFrame*>(Lower()); + + // will contain the number of pages per row. 0 means that + // the page does not fit. + tools::Long nWidthRemain = nVisWidth; + + // after one row has been processed, these variables contain + // the width of the row and the maximum of the page heights + tools::Long nCurrentRowHeight = 0; + tools::Long nCurrentRowWidth = 0; + + // these variables are used to finally set the size of the + // root frame + tools::Long nSumRowHeight = 0; + SwTwips nMinPageLeft = TWIPS_MAX; + SwTwips nMaxPageRight = 0; + SwPageFrame* pStartOfRow = pPageFrame; + sal_uInt16 nNumberOfPagesInRow = mbBookMode ? 1 : 0; // in book view, start with right page + bool bFirstRow = true; + + bool bPageChanged = false; + const bool bRTL = !IsLeftToRightViewLayout(); + const SwTwips nSidebarWidth = SwPageFrame::GetSidebarBorderWidth( pSh ); + + while ( pPageFrame ) + { + // we consider the current page to be "start of row" if + // 1. it is the first page in the current row or + // 2. it is the second page in the row and the first page is an empty page in non-book view: + const bool bStartOfRow = pPageFrame == pStartOfRow || + ( pStartOfRow->IsEmptyPage() && pPageFrame == pStartOfRow->GetNext() && !mbBookMode ); + + const bool bEmptyPage = pPageFrame->IsEmptyPage() && !mbBookMode; + + // no half doc border space for first page in each row and + tools::Long nPageWidth = 0; + tools::Long nPageHeight = 0; + + if ( mbBookMode ) + { + const SwFrame& rFormatPage = pPageFrame->GetFormatPage(); + + nPageWidth = rFormatPage.getFrameArea().Width() + nSidebarWidth + ((bStartOfRow || 1 == (pPageFrame->GetPhyPageNum()%2)) ? 0 : nGapBetweenPages); + nPageHeight = rFormatPage.getFrameArea().Height() + nGapBetweenPages; + } + else + { + if ( !pPageFrame->IsEmptyPage() ) + { + nPageWidth = pPageFrame->getFrameArea().Width() + nSidebarWidth + (bStartOfRow ? 0 : nGapBetweenPages); + nPageHeight = pPageFrame->getFrameArea().Height() + nGapBetweenPages; + } + } + + if ( !bEmptyPage ) + ++nNumberOfPagesInRow; + + // finish current row if + // 1. in dynamic mode the current page does not fit anymore or + // 2. the current page exceeds the maximum number of columns + bool bRowFinished = (0 == mnColumns && nWidthRemain < nPageWidth ) || + (0 != mnColumns && mnColumns < nNumberOfPagesInRow); + + // make sure that at least one page goes to the current row: + if ( !bRowFinished || bStartOfRow ) + { + // current page is allowed to be in current row + nWidthRemain = nWidthRemain - nPageWidth; + + nCurrentRowWidth = nCurrentRowWidth + nPageWidth; + nCurrentRowHeight = std::max( nCurrentRowHeight, nPageHeight ); + + pPageFrame = static_cast<SwPageFrame*>(pPageFrame->GetNext()); + + if ( !pPageFrame ) + bRowFinished = true; + } + + if ( bRowFinished ) + { + // pPageFrame now points to the first page in the new row or null + // pStartOfRow points to the first page in the current row + + // special centering for last row. pretend to fill the last row with virtual copies of the last page before centering: + if ( !pPageFrame && nWidthRemain > 0 ) + { + // find last page in current row: + const SwPageFrame* pLastPageInCurrentRow = pStartOfRow; + while( pLastPageInCurrentRow->GetNext() ) + pLastPageInCurrentRow = static_cast<const SwPageFrame*>(pLastPageInCurrentRow->GetNext()); + + if ( pLastPageInCurrentRow->IsEmptyPage() ) + pLastPageInCurrentRow = static_cast<const SwPageFrame*>(pLastPageInCurrentRow->GetPrev()); + + // check how many times the last page would still fit into the remaining space: + sal_uInt16 nNumberOfVirtualPages = 0; + const sal_uInt16 nMaxNumberOfVirtualPages = mnColumns > 0 ? mnColumns - nNumberOfPagesInRow : USHRT_MAX; + SwTwips nRemain = nWidthRemain; + SwTwips nVirtualPagesWidth = 0; + SwTwips nLastPageWidth = pLastPageInCurrentRow->getFrameArea().Width() + nSidebarWidth; + + while ( ( mnColumns > 0 || nRemain > 0 ) && nNumberOfVirtualPages < nMaxNumberOfVirtualPages ) + { + SwTwips nLastPageWidthWithGap = nLastPageWidth; + if ( !mbBookMode || ( 0 == (nNumberOfVirtualPages + nNumberOfPagesInRow) %2) ) + nLastPageWidthWithGap += nGapBetweenPages; + + if ( mnColumns > 0 || nLastPageWidthWithGap < nRemain ) + { + ++nNumberOfVirtualPages; + nVirtualPagesWidth += nLastPageWidthWithGap; + } + nRemain = nRemain - nLastPageWidthWithGap; + } + + nCurrentRowWidth = nCurrentRowWidth + nVirtualPagesWidth; + } + + // first page in book mode is always special: + if ( bFirstRow && mbBookMode ) + { + // #i88036# + nCurrentRowWidth += + pStartOfRow->GetFormatPage().getFrameArea().Width() + nSidebarWidth; + } + + // center page if possible + tools::Long nSizeDiff = 0; + if (nVisWidth > nCurrentRowWidth && !comphelper::LibreOfficeKit::isActive()) + nSizeDiff = ( nVisWidth - nCurrentRowWidth ) / 2; + + // adjust positions of pages in current row + tools::Long nX = nSizeDiff; + + const tools::Long nRowStart = nBorder + nSizeDiff; + const tools::Long nRowEnd = nRowStart + nCurrentRowWidth; + + if ( bFirstRow && mbBookMode ) + { + // #i88036# + nX += pStartOfRow->GetFormatPage().getFrameArea().Width() + nSidebarWidth; + } + + SwPageFrame* pEndOfRow = pPageFrame; + SwPageFrame* pPageToAdjust = pStartOfRow; + + do + { + const SwPageFrame* pFormatPage = pPageToAdjust; + if ( mbBookMode ) + pFormatPage = &pPageToAdjust->GetFormatPage(); + + const SwTwips nCurrentPageWidth = pFormatPage->getFrameArea().Width() + (pFormatPage->IsEmptyPage() ? 0 : nSidebarWidth); + const Point aOldPagePos = pPageToAdjust->getFrameArea().Pos(); + const bool bLeftSidebar = pPageToAdjust->SidebarPosition() == sw::sidebarwindows::SidebarPosition::LEFT; + const SwTwips nLeftPageAddOffset = bLeftSidebar ? + nSidebarWidth : + 0; + + Point aNewPagePos( nBorder + nX, nBorder + nSumRowHeight ); + Point aNewPagePosWithLeftOffset( nBorder + nX + nLeftPageAddOffset, nBorder + nSumRowHeight ); + + // RTL view layout: Calculate mirrored page position + if ( bRTL ) + { + const tools::Long nXOffsetInRow = aNewPagePos.getX() - nRowStart; + aNewPagePos.setX(nRowEnd - nXOffsetInRow - nCurrentPageWidth); + aNewPagePosWithLeftOffset = aNewPagePos; + aNewPagePosWithLeftOffset.setX(aNewPagePosWithLeftOffset.getX() + nLeftPageAddOffset); + } + + if ( aNewPagePosWithLeftOffset != aOldPagePos ) + { + lcl_MoveAllLowers( pPageToAdjust, aNewPagePosWithLeftOffset - aOldPagePos ); + pPageToAdjust->SetCompletePaint(); + bPageChanged = true; + } + + // calculate area covered by the current page and store to + // maPageRects. This is used e.g., for cursor setting + const bool bFirstColumn = pPageToAdjust == pStartOfRow; + const bool bLastColumn = pPageToAdjust->GetNext() == pEndOfRow; + const bool bLastRow = !pEndOfRow; + + nMinPageLeft = std::min( nMinPageLeft, SwTwips(aNewPagePos.getX()) ); + nMaxPageRight = std::max( nMaxPageRight, SwTwips(aNewPagePos.getX() + nCurrentPageWidth)); + + // border of nGapBetweenPages around the current page: + SwRect aPageRectWithBorders( aNewPagePos.getX() - nGapBetweenPages, + aNewPagePos.getY(), + pPageToAdjust->getFrameArea().SSize().Width() + nGapBetweenPages + nSidebarWidth, + nCurrentRowHeight ); + + static const tools::Long nOuterClickDiff = 1000000; + + // adjust borders for these special cases: + if ( (bFirstColumn && !bRTL) || (bLastColumn && bRTL) ) + aPageRectWithBorders.SubLeft( nOuterClickDiff ); + if ( (bLastColumn && !bRTL) || (bFirstColumn && bRTL) ) + aPageRectWithBorders.AddRight( nOuterClickDiff ); + if ( bFirstRow ) + aPageRectWithBorders.SubTop( nOuterClickDiff ); + if ( bLastRow ) + aPageRectWithBorders.AddBottom( nOuterClickDiff ); + + maPageRects.push_back( aPageRectWithBorders ); + + nX = nX + nCurrentPageWidth; + pPageToAdjust = static_cast<SwPageFrame*>(pPageToAdjust->GetNext()); + + // distance to next page + if ( pPageToAdjust && pPageToAdjust != pEndOfRow ) + { + // in book view, we add the x gap before left (even) pages: + if ( mbBookMode ) + { + if ( 0 == (pPageToAdjust->GetPhyPageNum()%2) ) + nX = nX + nGapBetweenPages; + } + else + { + // in non-book view, don't add x gap before + // 1. the last empty page in a row + // 2. after an empty page + const bool bDontAddGap = ( pPageToAdjust->IsEmptyPage() && pPageToAdjust->GetNext() == pEndOfRow ) || + ( static_cast<SwPageFrame*>(pPageToAdjust->GetPrev())->IsEmptyPage() ); + + if ( !bDontAddGap ) + nX = nX + nGapBetweenPages; + } + } + } + while (pPageToAdjust && pPageToAdjust != pEndOfRow); + + // adjust values for root frame size + nSumRowHeight = nSumRowHeight + nCurrentRowHeight; + + // start new row: + nCurrentRowHeight = 0; + nCurrentRowWidth = 0; + pStartOfRow = pEndOfRow; + nWidthRemain = nVisWidth; + nNumberOfPagesInRow = 0; + bFirstRow = false; + } // end row finished + } // end while + + // set size of root frame: + const Size aOldSize( getFrameArea().SSize() ); + const Size aNewSize( nMaxPageRight - nBorder, nSumRowHeight - nGapBetweenPages ); + + if ( bPageChanged || aNewSize != aOldSize ) + { + ChgSize( aNewSize ); + ::AdjustSizeChgNotify( this ); + Calc(pRenderContext); + + if ( pSh && pSh->GetDoc()->GetDocShell() ) + { + pSh->SetFirstVisPageInvalid(); + if (bOldCallbackActionEnabled) + { + pSh->InvalidateWindows( SwRect( 0, 0, SAL_MAX_INT32, SAL_MAX_INT32 ) ); + pSh->GetDoc()->GetDocShell()->Broadcast(SfxHint(SfxHintId::DocChanged)); + } + } + } + + maPagesArea.Pos( getFrameArea().Pos() ); + maPagesArea.SSize( aNewSize ); + if ( TWIPS_MAX != nMinPageLeft ) + maPagesArea.Left_( nMinPageLeft ); + + SetCallbackActionEnabled( bOldCallbackActionEnabled ); +} + +bool SwRootFrame::IsLeftToRightViewLayout() const +{ + // Layout direction determined by layout direction of the first page. + // #i88036# + // Only ask a non-empty page frame for its layout direction + assert(dynamic_cast<const SwPageFrame *>(Lower()) != nullptr); + const SwPageFrame& rPage = static_cast<const SwPageFrame&>(*Lower()).GetFormatPage(); + return !rPage.IsRightToLeft() && !rPage.IsVertical(); +} + +const SwPageFrame& SwPageFrame::GetFormatPage() const +{ + const SwPageFrame* pRet = this; + if ( IsEmptyPage() ) + { + pRet = static_cast<const SwPageFrame*>( OnRightPage() ? GetNext() : GetPrev() ); + // #i88035# + // Typically a right empty page frame has a next non-empty page frame and + // a left empty page frame has a previous non-empty page frame. + // But under certain circumstances this assumption is not true - + // e.g. during insertion of a left page at the end of the document right + // after a left page in an intermediate state a right empty page does not + // have a next page frame. + if ( pRet == nullptr ) + { + if ( OnRightPage() ) + { + pRet = static_cast<const SwPageFrame*>( GetPrev() ); + } + else + { + pRet = static_cast<const SwPageFrame*>( GetNext() ); + } + } + assert(pRet && + "<SwPageFrame::GetFormatPage()> - inconsistent layout: empty page without previous and next page frame --> crash."); + } + return *pRet; +} + +bool SwPageFrame::IsOverHeaderFooterArea( const Point& rPt, FrameControlType &rControl ) const +{ + tools::Long nUpperLimit = 0; + tools::Long nLowerLimit = 0; + const SwFrame* pFrame = Lower(); + while ( pFrame ) + { + if ( pFrame->IsBodyFrame() ) + { + nUpperLimit = pFrame->getFrameArea().Top(); + nLowerLimit = pFrame->getFrameArea().Bottom(); + } + else if ( pFrame->IsFootnoteContFrame() ) + nLowerLimit = pFrame->getFrameArea().Bottom(); + + pFrame = pFrame->GetNext(); + } + + SwRect aHeaderArea( getFrameArea().TopLeft(), + Size( getFrameArea().Width(), nUpperLimit - getFrameArea().Top() ) ); + + SwViewShell* pViewShell = getRootFrame()->GetCurrShell(); + const bool bHideWhitespaceMode = pViewShell->GetViewOptions()->IsHideWhitespaceMode(); + if ( aHeaderArea.Contains( rPt ) ) + { + if (!bHideWhitespaceMode || static_cast<const SwFrameFormat*>(GetDep())->GetHeader().IsActive()) + { + rControl = FrameControlType::Header; + return true; + } + } + else + { + SwRect aFooterArea( Point( getFrameArea().Left(), nLowerLimit ), + Size( getFrameArea().Width(), getFrameArea().Bottom() - nLowerLimit ) ); + + if ( aFooterArea.Contains( rPt ) && + (!bHideWhitespaceMode || static_cast<const SwFrameFormat*>(GetDep())->GetFooter().IsActive()) ) + { + rControl = FrameControlType::Footer; + return true; + } + } + + return false; +} + +bool SwPageFrame::CheckPageHeightValidForHideWhitespace(SwTwips nDiff) +{ + SwViewShell* pShell = getRootFrame()->GetCurrShell(); + if (pShell && pShell->GetViewOptions()->IsWhitespaceHidden()) + { + // When whitespace is hidden, the page frame has two heights: the + // nominal (defined by the frame format), and the actual (which is + // at most the nominal height, but can be smaller in case there is + // no content for the whole page). + // The layout size is the actual one, but we want to move the + // content frame to a new page only in case it doesn't fit the + // nominal size. + if (nDiff < 0) + { + // Content frame doesn't fit the actual size, check if it fits the nominal one. + const SwFrameFormat* pPageFormat = static_cast<const SwFrameFormat*>(GetDep()); + const Size& rPageSize = pPageFormat->GetFrameSize().GetSize(); + tools::Long nWhitespace = rPageSize.getHeight() - getFrameArea().Height(); + if (nWhitespace > -nDiff) + { + // It does: don't move it and invalidate our page frame so + // that it gets a larger height. + return false; + } + } + } + + return true; +} + +const SwHeaderFrame* SwPageFrame::GetHeaderFrame() const +{ + const SwFrame* pLowerFrame = Lower(); + while (pLowerFrame) + { + if (pLowerFrame->IsHeaderFrame()) + return dynamic_cast<const SwHeaderFrame*>(pLowerFrame); + pLowerFrame = pLowerFrame->GetNext(); + } + return nullptr; +} + +const SwFooterFrame* SwPageFrame::GetFooterFrame() const +{ + const SwFrame* pLowerFrame = Lower(); + while (pLowerFrame) + { + if (pLowerFrame->IsFooterFrame()) + return dynamic_cast<const SwFooterFrame*>(pLowerFrame); + pLowerFrame = pLowerFrame->GetNext(); + } + return nullptr; +} + +SwTextGridItem const* GetGridItem(SwPageFrame const*const pPage) +{ + if (pPage && pPage->HasGrid()) + { + SwTextGridItem const& rGridItem( + pPage->GetPageDesc()->GetMaster().GetTextGrid()); + if (GRID_NONE != rGridItem.GetGridType()) + { + return &rGridItem; + } + } + return nullptr; +} + +sal_uInt16 GetGridWidth(SwTextGridItem const& rG, SwDoc const& rDoc) +{ + return (rDoc.IsSquaredPageMode()) ? rG.GetBaseHeight() : rG.GetBaseWidth(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/pagedesc.cxx b/sw/source/core/layout/pagedesc.cxx new file mode 100644 index 000000000..d93b47517 --- /dev/null +++ b/sw/source/core/layout/pagedesc.cxx @@ -0,0 +1,803 @@ +/* -*- 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 <libxml/xmlwriter.h> + +#include <editeng/pbinitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/frmdiritem.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <fmtclds.hxx> +#include <fmtfsize.hxx> +#include <pagefrm.hxx> +#include <pagedesc.hxx> +#include <swtable.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <doc.hxx> +#include <node.hxx> +#include <strings.hrc> +#include <IDocumentLayoutAccess.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <poolfmt.hxx> +#include <calbck.hxx> + +SwPageDesc::SwPageDesc(const OUString& rName, SwFrameFormat *pFormat, SwDoc *const pDoc) + : sw::BroadcastingModify() + , m_StyleName( rName ) + , m_Master( pDoc->GetAttrPool(), rName, pFormat ) + , m_Left( pDoc->GetAttrPool(), rName, pFormat ) + , m_FirstMaster( pDoc->GetAttrPool(), rName, pFormat ) + , m_FirstLeft( pDoc->GetAttrPool(), rName, pFormat ) + , m_aStashedHeader() + , m_aStashedFooter() + , m_aDepends(*this) + , m_pTextFormatColl(nullptr) + , m_pFollow( this ) + , m_nRegHeight( 0 ) + , m_nRegAscent( 0 ) + , m_nVerticalAdjustment( drawing::TextVerticalAdjust_TOP ) + , m_eUse( UseOnPage::All | UseOnPage::HeaderShare | UseOnPage::FooterShare | UseOnPage::FirstShare ) + , m_IsLandscape( false ) + , m_IsHidden( false ) + , m_pdList( nullptr ) +{ +} + +SwPageDesc::SwPageDesc( const SwPageDesc &rCpy ) + : sw::BroadcastingModify() + , m_StyleName( rCpy.GetName() ) + , m_NumType( rCpy.GetNumType() ) + , m_Master( rCpy.GetMaster() ) + , m_Left( rCpy.GetLeft() ) + , m_FirstMaster( rCpy.GetFirstMaster() ) + , m_FirstLeft( rCpy.GetFirstLeft() ) + , m_aDepends(*this) + , m_pTextFormatColl(nullptr) + , m_pFollow( rCpy.m_pFollow ) + , m_nRegHeight( rCpy.GetRegHeight() ) + , m_nRegAscent( rCpy.GetRegAscent() ) + , m_nVerticalAdjustment( rCpy.GetVerticalAdjustment() ) + , m_eUse( rCpy.ReadUseOn() ) + , m_IsLandscape( rCpy.GetLandscape() ) + , m_IsHidden( rCpy.IsHidden() ) + , m_FootnoteInfo( rCpy.GetFootnoteInfo() ) + , m_pdList( nullptr ) +{ + m_aStashedHeader.m_pStashedFirst = rCpy.m_aStashedHeader.m_pStashedFirst; + m_aStashedHeader.m_pStashedLeft = rCpy.m_aStashedHeader.m_pStashedLeft; + m_aStashedHeader.m_pStashedFirstLeft = rCpy.m_aStashedHeader.m_pStashedFirstLeft; + + m_aStashedFooter.m_pStashedFirst = rCpy.m_aStashedFooter.m_pStashedFirst; + m_aStashedFooter.m_pStashedLeft = rCpy.m_aStashedFooter.m_pStashedLeft; + m_aStashedFooter.m_pStashedFirstLeft = rCpy.m_aStashedFooter.m_pStashedFirstLeft; + + if (rCpy.m_pTextFormatColl && rCpy.m_aDepends.IsListeningTo(rCpy.m_pTextFormatColl)) + { + m_pTextFormatColl = rCpy.m_pTextFormatColl; + m_aDepends.StartListening(const_cast<SwTextFormatColl*>(m_pTextFormatColl)); + } +} + +SwPageDesc & SwPageDesc::operator = (const SwPageDesc & rSrc) +{ + if(this == &rSrc) + return *this; + + m_StyleName = rSrc.m_StyleName; + m_NumType = rSrc.m_NumType; + m_Master = rSrc.m_Master; + m_Left = rSrc.m_Left; + m_FirstMaster = rSrc.m_FirstMaster; + m_FirstLeft = rSrc.m_FirstLeft; + + m_aStashedHeader.m_pStashedFirst = rSrc.m_aStashedHeader.m_pStashedFirst; + m_aStashedHeader.m_pStashedLeft = rSrc.m_aStashedHeader.m_pStashedLeft; + m_aStashedHeader.m_pStashedFirstLeft = rSrc.m_aStashedHeader.m_pStashedFirstLeft; + + m_aStashedFooter.m_pStashedFirst = rSrc.m_aStashedFooter.m_pStashedFirst; + m_aStashedFooter.m_pStashedLeft = rSrc.m_aStashedFooter.m_pStashedLeft; + m_aStashedFooter.m_pStashedFirstLeft = rSrc.m_aStashedFooter.m_pStashedFirstLeft; + + m_aDepends.EndListeningAll(); + if (rSrc.m_pTextFormatColl && rSrc.m_aDepends.IsListeningTo(rSrc.m_pTextFormatColl)) + { + m_pTextFormatColl = rSrc.m_pTextFormatColl; + m_aDepends.StartListening(const_cast<SwTextFormatColl*>(m_pTextFormatColl)); + } + else + m_pTextFormatColl = nullptr; + + if (rSrc.m_pFollow == &rSrc) + m_pFollow = this; + else + m_pFollow = rSrc.m_pFollow; + + m_nRegHeight = rSrc.m_nRegHeight; + m_nRegAscent = rSrc.m_nRegAscent; + m_nVerticalAdjustment = rSrc.m_nVerticalAdjustment; + m_eUse = rSrc.m_eUse; + m_IsLandscape = rSrc.m_IsLandscape; + return *this; +} + +SwPageDesc::~SwPageDesc() +{ +} + +bool SwPageDesc::SetName( const OUString& rNewName ) +{ + bool renamed = true; + if (m_pdList) { + SwPageDescs::iterator it = m_pdList->find_( m_StyleName ); + if( m_pdList->end() == it ) { + SAL_WARN( "sw", "SwPageDesc not found in expected m_pdList" ); + return false; + } + renamed = m_pdList->m_PosIndex.modify( it, + change_name( rNewName ), change_name( m_StyleName ) ); + } + else + m_StyleName = rNewName; + return renamed; +} + +/// Only the margin is mirrored. +/// Attributes like borders and so on are copied 1:1. +void SwPageDesc::Mirror() +{ + //Only the margins are mirrored, all other values are just copied. + SvxLRSpaceItem aLR( RES_LR_SPACE ); + const SvxLRSpaceItem &rLR = m_Master.GetLRSpace(); + aLR.SetLeft( rLR.GetRight() ); + aLR.SetRight( rLR.GetLeft() ); + aLR.SetRightGutterMargin(rLR.GetGutterMargin()); + + SfxItemSet aSet( *m_Master.GetAttrSet().GetPool(), + m_Master.GetAttrSet().GetRanges() ); + aSet.Put( aLR ); + aSet.Put( m_Master.GetFrameSize() ); + aSet.Put( m_Master.GetPaperBin() ); + aSet.Put( m_Master.GetULSpace() ); + aSet.Put( m_Master.GetBox() ); + aSet.Put( m_Master.makeBackgroundBrushItem() ); + aSet.Put( m_Master.GetShadow() ); + aSet.Put( m_Master.GetCol() ); + aSet.Put( m_Master.GetFrameDir() ); + m_Left.SetFormatAttr( aSet ); +} + +void SwPageDesc::ResetAllAttr() +{ + SwFrameFormat& rFormat = GetMaster(); + + // #i73790# - method renamed + rFormat.ResetAllFormatAttr(); + rFormat.SetFormatAttr( SvxFrameDirectionItem(SvxFrameDirection::Horizontal_LR_TB, RES_FRAMEDIR) ); +} + +// gets information from Modify +bool SwPageDesc::GetInfo( SfxPoolItem & rInfo ) const +{ + if (!m_Master.GetInfo(rInfo)) + return false; // found + if (!m_Left.GetInfo(rInfo)) + return false ; + if ( !m_FirstMaster.GetInfo( rInfo ) ) + return false; + return m_FirstLeft.GetInfo( rInfo ); +} + +/// set the style for the grid alignment +void SwPageDesc::SetRegisterFormatColl(const SwTextFormatColl* pFormat) +{ + if(pFormat != m_pTextFormatColl) + { + m_aDepends.EndListeningAll(); + m_pTextFormatColl = pFormat; + m_aDepends.StartListening(const_cast<SwTextFormatColl*>(m_pTextFormatColl)); + RegisterChange(); + } +} + +/// retrieve the style for the grid alignment +const SwTextFormatColl* SwPageDesc::GetRegisterFormatColl() const +{ + if (!m_aDepends.IsListeningTo(m_pTextFormatColl)) + m_pTextFormatColl = nullptr; + return m_pTextFormatColl; +} + +/// notify all affected page frames +void SwPageDesc::RegisterChange() +{ + // #117072# - During destruction of the document <SwDoc> + // the page description is modified. Thus, do nothing, if the document + // is in destruction respectively if no viewshell exists. + SwDoc* pDoc = GetMaster().GetDoc(); + if ( !pDoc || pDoc->IsInDtor() ) + { + return; + } + SwViewShell* pSh = pDoc->getIDocumentLayoutAccess().GetCurrentViewShell(); + if ( !pSh ) + { + return; + } + + m_nRegHeight = 0; + { + SwIterator<SwFrame,SwFormat> aIter( GetMaster() ); + for( SwFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if( pLast->IsPageFrame() ) + static_cast<SwPageFrame*>(pLast)->PrepareRegisterChg(); + } + } + { + SwIterator<SwFrame,SwFormat> aIter( GetLeft() ); + for( SwFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if( pLast->IsPageFrame() ) + static_cast<SwPageFrame*>(pLast)->PrepareRegisterChg(); + } + } + { + SwIterator<SwFrame,SwFormat> aIter( GetFirstMaster() ); + for( SwFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if( pLast->IsPageFrame() ) + static_cast<SwPageFrame*>(pLast)->PrepareRegisterChg(); + } + } + { + SwIterator<SwFrame,SwFormat> aIter( GetFirstLeft() ); + for( SwFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if( pLast->IsPageFrame() ) + static_cast<SwPageFrame*>(pLast)->PrepareRegisterChg(); + } + } +} + +/// special handling if the style of the grid alignment changes +void SwPageDesc::SwClientNotify(const SwModify& rModify, const SfxHint& rHint) +{ + if (rHint.GetId() == SfxHintId::SwLegacyModify) + { + auto pLegacyHint = static_cast<const sw::LegacyModifyHint*>(&rHint); + const sal_uInt16 nWhich = pLegacyHint->m_pOld + ? pLegacyHint->m_pOld->Which() + : pLegacyHint->m_pNew + ? pLegacyHint->m_pNew->Which() + : 0; + CallSwClientNotify(rHint); + if((RES_ATTRSET_CHG == nWhich) + || (RES_FMT_CHG == nWhich) + || isCHRATR(nWhich) + || (RES_PARATR_LINESPACING == nWhich)) + RegisterChange(); + } + else if (auto pModifyChangedHint = dynamic_cast<const sw::ModifyChangedHint*>(&rHint)) + { + if(m_pTextFormatColl == &rModify) + m_pTextFormatColl = static_cast<const SwTextFormatColl*>(pModifyChangedHint->m_pNew); + else + assert(false); + } +} + +static const SwFrame* lcl_GetFrameOfNode( const SwNode& rNd ) +{ + const sw::BroadcastingModify* pMod; + SwFrameType nFrameType = FRM_CNTNT; + + if( rNd.IsContentNode() ) + { + pMod = &static_cast<const SwContentNode&>(rNd); + } + else if( rNd.IsTableNode() ) + { + pMod = static_cast<const SwTableNode&>(rNd).GetTable().GetFrameFormat(); + nFrameType = SwFrameType::Tab; + } + else + pMod = nullptr; + + Point aNullPt; + std::pair<Point, bool> const tmp(aNullPt, false); + return pMod ? ::GetFrameOfModify(nullptr, *pMod, nFrameType, nullptr, &tmp) + : nullptr; +} + +const SwPageDesc* SwPageDesc::GetPageDescOfNode(const SwNode& rNd) +{ + const SwPageDesc* pRet = nullptr; + const SwFrame* pChkFrame = lcl_GetFrameOfNode( rNd ); + if (pChkFrame && nullptr != (pChkFrame = pChkFrame->FindPageFrame())) + pRet = static_cast<const SwPageFrame*>(pChkFrame)->GetPageDesc(); + return pRet; +} + +const SwFrameFormat* SwPageDesc::GetPageFormatOfNode( const SwNode& rNd, + bool bCheckForThisPgDc ) const +{ + // which PageDescFormat is valid for this node? + const SwFrameFormat* pRet; + const SwFrame* pChkFrame = lcl_GetFrameOfNode( rNd ); + + if( pChkFrame && nullptr != ( pChkFrame = pChkFrame->FindPageFrame() )) + { + const SwPageDesc* pPd = bCheckForThisPgDc ? this : + static_cast<const SwPageFrame*>(pChkFrame)->GetPageDesc(); + pRet = &pPd->GetMaster(); + OSL_ENSURE( static_cast<const SwPageFrame*>(pChkFrame)->GetPageDesc() == pPd, "Wrong node for detection of page format!" ); + // this page is assigned to which format? + if( !pChkFrame->KnowsFormat(*pRet) ) + { + pRet = &pPd->GetLeft(); + OSL_ENSURE( pChkFrame->KnowsFormat(*pRet), "Wrong node for detection of page format!" ); + } + } + else + pRet = &GetMaster(); + return pRet; +} + +bool SwPageDesc::IsFollowNextPageOfNode( const SwNode& rNd ) const +{ + bool bRet = false; + if( GetFollow() && this != GetFollow() ) + { + const SwFrame* pChkFrame = lcl_GetFrameOfNode( rNd ); + if( pChkFrame && nullptr != ( pChkFrame = pChkFrame->FindPageFrame() ) && + pChkFrame->IsPageFrame() && + ( !pChkFrame->GetNext() || GetFollow() == + static_cast<const SwPageFrame*>(pChkFrame->GetNext())->GetPageDesc() )) + // the page on which the follow points was found + bRet = true; + } + return bRet; +} + +SwFrameFormat *SwPageDesc::GetLeftFormat(bool const bFirst) +{ + return (UseOnPage::Left & m_eUse) + ? (bFirst ? &m_FirstLeft : &m_Left) + : nullptr; +} + +SwFrameFormat *SwPageDesc::GetRightFormat(bool const bFirst) +{ + return (UseOnPage::Right & m_eUse) + ? (bFirst ? &m_FirstMaster : &m_Master) + : nullptr; +} + +bool SwPageDesc::IsFirstShared() const +{ + return bool(m_eUse & UseOnPage::FirstShare); +} + +void SwPageDesc::ChgFirstShare( bool bNew ) +{ + if ( bNew ) + m_eUse |= UseOnPage::FirstShare; + else + m_eUse &= UseOnPage::NoFirstShare; +} + +void SwPageDesc::StashFrameFormat(const SwFrameFormat& rFormat, bool bHeader, bool bLeft, bool bFirst) +{ + assert(rFormat.GetRegisteredIn()); + std::shared_ptr<SwFrameFormat>* pFormat = nullptr; + + if (bHeader) + { + if (bLeft && !bFirst) + pFormat = &m_aStashedHeader.m_pStashedLeft; + else if (!bLeft && bFirst) + pFormat = &m_aStashedHeader.m_pStashedFirst; + else if (bLeft && bFirst) + pFormat = &m_aStashedHeader.m_pStashedFirstLeft; + } + else + { + if (bLeft && !bFirst) + pFormat = &m_aStashedFooter.m_pStashedLeft; + else if (!bLeft && bFirst) + pFormat = &m_aStashedFooter.m_pStashedFirst; + else if (bLeft && bFirst) + pFormat = &m_aStashedFooter.m_pStashedFirstLeft; + } + + if (pFormat) + { + *pFormat = std::make_shared<SwFrameFormat>(rFormat); + } + else + { + SAL_WARN( + "sw", + "SwPageDesc::StashFrameFormat: Stashing the right page header/footer is pointless."); + } +} + +const SwFrameFormat* SwPageDesc::GetStashedFrameFormat(bool bHeader, bool bLeft, bool bFirst) const +{ + std::shared_ptr<SwFrameFormat>* pFormat = nullptr; + + if (bLeft && !bFirst) + { + pFormat = bHeader ? &m_aStashedHeader.m_pStashedLeft : &m_aStashedFooter.m_pStashedLeft; + } + else if (!bLeft && bFirst) + { + pFormat = bHeader ? &m_aStashedHeader.m_pStashedFirst : &m_aStashedFooter.m_pStashedFirst; + } + else if (bLeft && bFirst) + { + pFormat = bHeader ? &m_aStashedHeader.m_pStashedFirstLeft : &m_aStashedFooter.m_pStashedFirstLeft; + } + + if (pFormat) + { + return pFormat->get(); + } + else + { + SAL_WARN("sw", "SwPageDesc::GetStashedFrameFormat: Right page format is never stashed."); + return nullptr; + } +} + +bool SwPageDesc::HasStashedFormat(bool bHeader, bool bLeft, bool bFirst) +{ + if (bHeader) + { + if (bLeft && !bFirst) + { + return m_aStashedHeader.m_pStashedLeft != nullptr; + } + else if (!bLeft && bFirst) + { + return m_aStashedHeader.m_pStashedFirst != nullptr; + } + else if (bLeft && bFirst) + { + return m_aStashedHeader.m_pStashedFirstLeft != nullptr; + } + else + { + SAL_WARN("sw", "SwPageDesc::HasStashedFormat: Right page format is never stashed."); + return false; + } + } + else + { + if (bLeft && !bFirst) + { + return m_aStashedFooter.m_pStashedLeft != nullptr; + } + else if (!bLeft && bFirst) + { + return m_aStashedFooter.m_pStashedFirst != nullptr; + } + else if (bLeft && bFirst) + { + return m_aStashedFooter.m_pStashedFirstLeft != nullptr; + } + else + { + SAL_WARN("sw", "SwPageDesc::HasStashedFormat: Right page format is never stashed."); + return false; + } + } +} + +void SwPageDesc::RemoveStashedFormat(bool bHeader, bool bLeft, bool bFirst) +{ + if (bHeader) + { + if (bLeft && !bFirst) + { + m_aStashedHeader.m_pStashedLeft.reset(); + } + else if (!bLeft && bFirst) + { + m_aStashedHeader.m_pStashedFirst.reset(); + } + else if (bLeft && bFirst) + { + m_aStashedHeader.m_pStashedFirstLeft.reset(); + } + else + { + SAL_WARN("sw", "SwPageDesc::RemoveStashedFormat: Right page format is never stashed."); + } + } + else + { + if (bLeft && !bFirst) + { + m_aStashedFooter.m_pStashedLeft.reset(); + } + else if (!bLeft && bFirst) + { + m_aStashedFooter.m_pStashedFirst.reset(); + } + else if (bLeft && bFirst) + { + m_aStashedFooter.m_pStashedFirstLeft.reset(); + } + else + { + SAL_WARN("sw", "SwPageDesc::RemoveStashedFormat: Right page format is never stashed."); + } + } +} + +// Page styles +const TranslateId STR_POOLPAGE[] = +{ + STR_POOLPAGE_STANDARD, + STR_POOLPAGE_FIRST, + STR_POOLPAGE_LEFT, + STR_POOLPAGE_RIGHT, + STR_POOLPAGE_ENVELOPE, + STR_POOLPAGE_REGISTER, + STR_POOLPAGE_HTML, + STR_POOLPAGE_FOOTNOTE, + STR_POOLPAGE_ENDNOTE, + STR_POOLPAGE_LANDSCAPE +}; + +SwPageDesc* SwPageDesc::GetByName(SwDoc& rDoc, std::u16string_view rName) +{ + const size_t nDCount = rDoc.GetPageDescCnt(); + + for( size_t i = 0; i < nDCount; i++ ) + { + SwPageDesc* pDsc = &rDoc.GetPageDesc( i ); + if(pDsc->GetName() == rName) + { + return pDsc; + } + } + + for (size_t i = 0; i < SAL_N_ELEMENTS(STR_POOLPAGE); ++i) + { + if (rName == SwResId(STR_POOLPAGE[i])) + { + return rDoc.getIDocumentStylePoolAccess().GetPageDescFromPool( static_cast< sal_uInt16 >( + i + RES_POOLPAGE_BEGIN) ); + } + } + + return nullptr; +} + +void SwPageDesc::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwPageDesc")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("m_StyleName"), "%s", + BAD_CAST(m_StyleName.toUtf8().getStr())); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("m_pFollow"), "%p", m_pFollow); + (void)xmlTextWriterWriteFormatAttribute( + pWriter, BAD_CAST("m_eUse"), "0x%s", + BAD_CAST(OString::number(static_cast<int>(m_eUse), 16).getStr())); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_Master")); + m_Master.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_Left")); + m_Left.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_FirstMaster")); + m_FirstMaster.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_FirstLeft")); + m_FirstLeft.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +SwPageFootnoteInfo::SwPageFootnoteInfo() + : m_nMaxHeight( 0 ) + , m_nLineWidth(10) + , m_eLineStyle( SvxBorderLineStyle::SOLID ) + , m_Width( 25, 100 ) + , m_nTopDist( 57 ) //1mm + , m_nBottomDist( 57 ) +{ + m_eAdjust = SvxFrameDirection::Horizontal_RL_TB == GetDefaultFrameDirection(GetAppLanguage()) ? + css::text::HorizontalAdjust_RIGHT : + css::text::HorizontalAdjust_LEFT; +} + +SwPageFootnoteInfo::SwPageFootnoteInfo( const SwPageFootnoteInfo &rCpy ) + : m_nMaxHeight(rCpy.GetHeight()) + , m_nLineWidth(rCpy.m_nLineWidth) + , m_eLineStyle(rCpy.m_eLineStyle) + , m_LineColor(rCpy.m_LineColor) + , m_Width(rCpy.GetWidth()) + , m_eAdjust(rCpy.GetAdj()) + , m_nTopDist(rCpy.GetTopDist()) + , m_nBottomDist(rCpy.GetBottomDist()) +{ +} + +SwPageFootnoteInfo &SwPageFootnoteInfo::operator=( const SwPageFootnoteInfo& rCpy ) +{ + m_nMaxHeight = rCpy.GetHeight(); + m_nLineWidth = rCpy.m_nLineWidth; + m_eLineStyle = rCpy.m_eLineStyle; + m_LineColor = rCpy.m_LineColor; + m_Width = rCpy.GetWidth(); + m_eAdjust = rCpy.GetAdj(); + m_nTopDist = rCpy.GetTopDist(); + m_nBottomDist = rCpy.GetBottomDist(); + return *this; +} + +bool SwPageFootnoteInfo::operator==( const SwPageFootnoteInfo& rCmp ) const +{ + return m_nMaxHeight == rCmp.GetHeight() + && m_nLineWidth == rCmp.m_nLineWidth + && m_eLineStyle == rCmp.m_eLineStyle + && m_LineColor == rCmp.m_LineColor + && m_Width == rCmp.GetWidth() + && m_eAdjust == rCmp.GetAdj() + && m_nTopDist == rCmp.GetTopDist() + && m_nBottomDist== rCmp.GetBottomDist(); +} + +SwPageDescExt::SwPageDescExt(const SwPageDesc & rPageDesc, SwDoc *const pDoc) + : m_PageDesc(rPageDesc) + , m_pDoc(pDoc) +{ + SetPageDesc(rPageDesc); +} + +SwPageDescExt::SwPageDescExt(const SwPageDescExt & rSrc) + : m_PageDesc(rSrc.m_PageDesc) + , m_pDoc(rSrc.m_pDoc) +{ + SetPageDesc(rSrc.m_PageDesc); +} + +SwPageDescExt::~SwPageDescExt() +{ +} + +OUString const & SwPageDescExt::GetName() const +{ + return m_PageDesc.GetName(); +} + +void SwPageDescExt::SetPageDesc(const SwPageDesc & rPageDesc) +{ + m_PageDesc = rPageDesc; + + if (m_PageDesc.GetFollow()) + m_sFollow = m_PageDesc.GetFollow()->GetName(); +} + +SwPageDescExt & SwPageDescExt::operator = (const SwPageDesc & rSrc) +{ + SetPageDesc(rSrc); + + return *this; +} + +SwPageDescExt & SwPageDescExt::operator = (const SwPageDescExt & rSrc) +{ + operator=(rSrc.m_PageDesc); + return *this; +} + +SwPageDescExt::operator SwPageDesc() const +{ + SwPageDesc aResult(m_PageDesc); + + SwPageDesc * pPageDesc = m_pDoc->FindPageDesc(m_sFollow); + + if ( nullptr != pPageDesc ) + aResult.SetFollow(pPageDesc); + + return aResult; +} + +SwPageDescs::SwPageDescs() + : m_PosIndex( m_Array.get<0>() ) + , m_NameIndex( m_Array.get<1>() ) +{ +} + +SwPageDescs::~SwPageDescs() +{ + for(const_iterator it = begin(); it != end(); ++it) + delete *it; +} + +SwPageDescs::iterator SwPageDescs::find_(const OUString &name) const +{ + ByName::iterator it = m_NameIndex.find( name ); + return m_Array.iterator_to( *it ); +} + +std::pair<SwPageDescs::const_iterator,bool> SwPageDescs::push_back( const value_type& x ) +{ + // SwPageDesc is not already in a SwPageDescs list! + assert( x->m_pdList == nullptr ); + + std::pair<iterator,bool> res = m_PosIndex.push_back( x ); + if( res.second ) + x->m_pdList = this; + return res; +} + +void SwPageDescs::erase( const value_type& x ) +{ + // SwPageDesc is not in this SwPageDescs list! + assert( x->m_pdList == this ); + + iterator const ret = find_( x->GetName() ); + if (ret != end()) + m_PosIndex.erase( ret ); + else + SAL_WARN( "sw", "SwPageDesc is not in SwPageDescs m_pdList!" ); + x->m_pdList = nullptr; +} + +void SwPageDescs::erase( const_iterator const& position ) +{ + // SwPageDesc is not in this SwPageDescs list! + assert( (*position)->m_pdList == this ); + + (*position)->m_pdList = nullptr; + m_PosIndex.erase( position ); +} + +void SwPageDescs::erase( size_type index_ ) +{ + erase( begin() + index_ ); +} + +void SwPageDescs::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwPageDescs")); + + for (const auto& pPageDesc : m_PosIndex) + { + pPageDesc->dumpAsXml(pWriter); + } + + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/paintfrm.cxx b/sw/source/core/layout/paintfrm.cxx new file mode 100644 index 000000000..f168cbd5a --- /dev/null +++ b/sw/source/core/layout/paintfrm.cxx @@ -0,0 +1,7721 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/lazydelete.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/printer.hxx> +#include <sfx2/progress.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/prntitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/shaditem.hxx> +#include <svx/ctredlin.hxx> +#include <svx/framelink.hxx> +#include <drawdoc.hxx> +#include <tgrditem.hxx> +#include <calbck.hxx> +#include <fmtsrnd.hxx> +#include <fmtclds.hxx> +#include <strings.hrc> +#include <swmodule.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <section.hxx> +#include <sectfrm.hxx> +#include <viewimp.hxx> +#include <dflyobj.hxx> +#include <flyfrm.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <viewopt.hxx> +#include <dview.hxx> +#include <dcontact.hxx> +#include <txtfrm.hxx> +#include <ftnfrm.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <cellfrm.hxx> +#include <notxtfrm.hxx> +#include <layact.hxx> +#include <pagedesc.hxx> +#include <ptqueue.hxx> +#include <noteurl.hxx> +#include "virtoutp.hxx" +#include <lineinfo.hxx> +#include <dbg_lay.hxx> +#include <docsh.hxx> +#include <svx/svdogrp.hxx> +#include <sortedobjs.hxx> +#include <EnhancedPDFExportHelper.hxx> +#include <bodyfrm.hxx> +#include <hffrm.hxx> +#include <colfrm.hxx> +#include <sw_primitivetypes2d.hxx> +#include <swfont.hxx> + +#include <svx/sdr/primitive2d/sdrframeborderprimitive2d.hxx> +#include <svx/sdr/contact/viewobjectcontactredirector.hxx> +#include <svx/sdr/contact/viewobjectcontact.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <IDocumentDrawModelAccess.hxx> + +#include <ndole.hxx> +#include <PostItMgr.hxx> +#include <FrameControlsManager.hxx> +#include <vcl/settings.hxx> + +#include <svx/sdr/attribute/sdrallfillattributeshelper.hxx> +#include <drawinglayer/processor2d/processor2dtools.hxx> + +#include <svtools/borderhelper.hxx> + +#include <bitmaps.hlst> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/discreteshadowprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/processor2d/processorfromoutputdevice.hxx> +#include <svx/unoapi.hxx> +#include <svx/svdpagv.hxx> +#include <svx/xfillit0.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/color/bcolortools.hxx> +#include <basegfx/utils/b2dclipstate.hxx> +#include <sal/log.hxx> + +#include <memory> +#include <vector> +#include <algorithm> +#include <wrtsh.hxx> +#include <edtwin.hxx> +#include <view.hxx> +#include <paintfrm.hxx> +#include <textboxhelper.hxx> +#include <o3tl/typed_flags_set.hxx> + +#include <vcl/BitmapTools.hxx> +#include <comphelper/lok.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <vcl/GraphicLoader.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> + +using namespace ::editeng; +using namespace ::com::sun::star; + +namespace { + +struct SwPaintProperties; + +//Class declaration; here because they are only used in this file +enum class SubColFlags { + Page = 0x01, //Helplines of the page + Tab = 0x08, //Helplines inside tables + Fly = 0x10, //Helplines inside fly frames + Sect = 0x20, //Helplines inside sections +}; + +} + +namespace o3tl { + template<> struct typed_flags<SubColFlags> : is_typed_flags<SubColFlags, 0x39> {}; +} + +namespace { + +// Classes collecting the border lines and help lines +class SwLineRect : public SwRect +{ + Color m_aColor; + SvxBorderLineStyle m_nStyle; + const SwTabFrame* m_pTabFrame; + SubColFlags m_nSubColor; //colorize subsidiary lines + bool m_bPainted; //already painted? + sal_uInt8 m_nLock; //To distinguish the line and the hell layer. +public: + SwLineRect( const SwRect &rRect, const Color *pCol, const SvxBorderLineStyle nStyle, + const SwTabFrame *pT , const SubColFlags nSCol ); + + const Color& GetColor() const { return m_aColor; } + SvxBorderLineStyle GetStyle() const { return m_nStyle; } + const SwTabFrame* GetTab() const { return m_pTabFrame; } + void SetPainted() { m_bPainted = true; } + void Lock(bool bLock) + { + if (bLock) + ++m_nLock; + else if (m_nLock) + --m_nLock; + } + bool IsPainted() const { return m_bPainted; } + bool IsLocked() const { return m_nLock != 0; } + SubColFlags GetSubColor() const { return m_nSubColor; } + + bool MakeUnion(const SwRect& rRect, SwPaintProperties const& properties); +}; + +} + +#ifdef IOS +static void dummy_function() +{ + pid_t pid = getpid(); + (void) pid; +} +#endif + +namespace { + +class SwLineRects +{ +public: + std::vector<SwLineRect> m_aLineRects; + typedef std::vector< SwLineRect >::const_iterator const_iterator; + typedef std::vector< SwLineRect >::iterator iterator; + typedef std::vector< SwLineRect >::reverse_iterator reverse_iterator; + typedef std::vector< SwLineRect >::size_type size_type; + size_t m_nLastCount; //avoid unnecessary cycles in PaintLines + SwLineRects() + : m_nLastCount(0) + { +#ifdef IOS + // Work around what is either a compiler bug in Xcode 5.1.1, + // or some unknown problem in this file. If I ifdef out this + // call, I get a crash in SwSubsRects::PaintSubsidiary: the + // address of the rLi reference variable is claimed to be + // 0x4000000! + dummy_function(); +#endif + } + void AddLineRect( const SwRect& rRect, const Color *pColor, const SvxBorderLineStyle nStyle, + const SwTabFrame *pTab, const SubColFlags nSCol, SwPaintProperties const &properties ); + void ConnectEdges( OutputDevice const *pOut, SwPaintProperties const &properties ); + void PaintLines ( OutputDevice *pOut, SwPaintProperties const &properties ); + void LockLines( bool bLock ); + + //Limit lines to 100 + bool isFull() const { return m_aLineRects.size() > 100; } +}; + +class SwSubsRects : public SwLineRects +{ + void RemoveSuperfluousSubsidiaryLines( const SwLineRects &rRects, SwPaintProperties const &properties ); +public: + void PaintSubsidiary( OutputDevice *pOut, const SwLineRects *pRects, SwPaintProperties const &properties ); +}; + +class BorderLines +{ + drawinglayer::primitive2d::Primitive2DContainer m_Lines; +public: + void AddBorderLines(drawinglayer::primitive2d::Primitive2DContainer&& rContainer); + drawinglayer::primitive2d::Primitive2DContainer GetBorderLines_Clear() + { + drawinglayer::primitive2d::Primitive2DContainer lines; + lines.swap(m_Lines); + return lines; + } +}; + +} + +// Default zoom factor +const double aEdgeScale = 0.5; + +//To optimize the expensive RetouchColor determination +Color aGlobalRetoucheColor; + +namespace sw +{ +Color* GetActiveRetoucheColor() +{ + return &aGlobalRetoucheColor; +} +} + +namespace { + +/** + * Container for static properties + */ +struct SwPaintProperties { + // Only repaint the Fly content as well as the background of the Fly content if + // a metafile is taken of the Fly. + bool bSFlyMetafile; + VclPtr<OutputDevice> pSFlyMetafileOut; + SwViewShell *pSGlobalShell; + + // Retouch for transparent Flys is done by the background of the Flys. + // The Fly itself should certainly not be spared out. See PaintSwFrameBackground and + // lcl_SubtractFlys() + SwFlyFrame *pSRetoucheFly; + SwFlyFrame *pSRetoucheFly2; + SwFlyFrame *pSFlyOnlyDraw; + + // The borders will be collected in pSLines during the Paint and later + // possibly merge them. + // The help lines will be collected and merged in gProp.pSSubsLines. These will + // be compared with pSLines before the work in order to avoid help lines + // to hide borders. + std::unique_ptr<BorderLines> pBLines; + std::unique_ptr<SwLineRects> pSLines; + std::unique_ptr<SwSubsRects> pSSubsLines; + + // global variable for sub-lines of body, header, footer, section and footnote frames. + std::unique_ptr<SwSubsRects> pSSpecSubsLines; + SfxProgress *pSProgress; + + // Sizes of a pixel and the corresponding halves. Will be reset when + // entering SwRootFrame::PaintSwFrame + tools::Long nSPixelSzW; + tools::Long nSPixelSzH; + tools::Long nSHalfPixelSzW; + tools::Long nSHalfPixelSzH; + tools::Long nSMinDistPixelW; + tools::Long nSMinDistPixelH; + + Color aSGlobalRetoucheColor; + + // Current zoom factor + double aSScaleX; + double aSScaleY; + + SwPaintProperties() + : bSFlyMetafile(false) + , pSFlyMetafileOut(nullptr) + , pSGlobalShell(nullptr) + , pSRetoucheFly(nullptr) + , pSRetoucheFly2(nullptr) + , pSFlyOnlyDraw(nullptr) + , pSProgress(nullptr) + , nSPixelSzW(0) + , nSPixelSzH(0) + , nSHalfPixelSzW(0) + , nSHalfPixelSzH(0) + , nSMinDistPixelW(0) + , nSMinDistPixelH(0) + , aSScaleX(1) + , aSScaleY(1) + { + } + +}; + +} + +static SwPaintProperties gProp; + +static bool isSubsidiaryLinesFlysEnabled() +{ + return !gProp.pSGlobalShell->GetViewOptions()->IsPagePreview() && + !gProp.pSGlobalShell->GetViewOptions()->IsReadonly() && + !gProp.pSGlobalShell->GetViewOptions()->IsFormView() && + SwViewOption::IsObjectBoundaries(); +} +//other subsidiary lines enabled? +static bool isSubsidiaryLinesEnabled() +{ + return !gProp.pSGlobalShell->GetViewOptions()->IsPagePreview() && + !gProp.pSGlobalShell->GetViewOptions()->IsReadonly() && + !gProp.pSGlobalShell->GetViewOptions()->IsFormView() && + !gProp.pSGlobalShell->GetViewOptions()->IsWhitespaceHidden() && + SwViewOption::IsDocBoundaries(); +} +//subsidiary lines for sections +static bool isSubsidiaryLinesForSectionsEnabled() +{ + return !gProp.pSGlobalShell->GetViewOptions()->IsPagePreview() && + !gProp.pSGlobalShell->GetViewOptions()->IsReadonly() && + !gProp.pSGlobalShell->GetViewOptions()->IsFormView() && + SwViewOption::IsSectionBoundaries(); +} + + +namespace { + +bool isTableBoundariesEnabled() +{ + if (!gProp.pSGlobalShell->GetViewOptions()->IsTable()) + return false; + + if (gProp.pSGlobalShell->GetViewOptions()->IsPagePreview()) + return false; + + if (gProp.pSGlobalShell->GetViewOptions()->IsReadonly()) + return false; + + if (gProp.pSGlobalShell->GetViewOptions()->IsFormView()) + return false; + + return SwViewOption::IsTableBoundaries(); +} + +} + +/** + * Set borders alignment statics + * Adjustment for 'small' twip-to-pixel relations: + * For 'small' twip-to-pixel relations (less than 2:1) + * values of <gProp.nSHalfPixelSzW> and <gProp.nSHalfPixelSzH> are set to ZERO + */ +void SwCalcPixStatics( vcl::RenderContext const *pOut ) +{ + // determine 'small' twip-to-pixel relation + bool bSmallTwipToPxRelW = false; + bool bSmallTwipToPxRelH = false; + { + Size aCheckTwipToPxRelSz( pOut->PixelToLogic( Size( 100, 100 )) ); + if ( (aCheckTwipToPxRelSz.Width()/100.0) < 2.0 ) + { + bSmallTwipToPxRelW = true; + } + if ( (aCheckTwipToPxRelSz.Height()/100.0) < 2.0 ) + { + bSmallTwipToPxRelH = true; + } + } + + Size aSz( pOut->PixelToLogic( Size( 1,1 )) ); + + gProp.nSPixelSzW = aSz.Width(); + if( !gProp.nSPixelSzW ) + gProp.nSPixelSzW = 1; + gProp.nSPixelSzH = aSz.Height(); + if( !gProp.nSPixelSzH ) + gProp.nSPixelSzH = 1; + + // consider 'small' twip-to-pixel relations + if ( !bSmallTwipToPxRelW ) + { + gProp.nSHalfPixelSzW = gProp.nSPixelSzW / 2 + 1; + } + else + { + gProp.nSHalfPixelSzW = 0; + } + // consider 'small' twip-to-pixel relations + if ( !bSmallTwipToPxRelH ) + { + gProp.nSHalfPixelSzH = gProp.nSPixelSzH / 2 + 1; + } + else + { + gProp.nSHalfPixelSzH = 0; + } + + gProp.nSMinDistPixelW = gProp.nSPixelSzW * 2 + 1; + gProp.nSMinDistPixelH = gProp.nSPixelSzH * 2 + 1; + + const MapMode &rMap = pOut->GetMapMode(); + gProp.aSScaleX = double(rMap.GetScaleX()); + gProp.aSScaleY = double(rMap.GetScaleY()); +} + +namespace { + +/** + * To be able to save the statics so the paint is more or less reentrant + */ +class SwSavePaintStatics : public SwPaintProperties +{ +public: + SwSavePaintStatics(); + ~SwSavePaintStatics(); +}; + +} + +SwSavePaintStatics::SwSavePaintStatics() +{ + // Saving globales + bSFlyMetafile = gProp.bSFlyMetafile; + pSGlobalShell = gProp.pSGlobalShell; + pSFlyMetafileOut = gProp.pSFlyMetafileOut; + pSRetoucheFly = gProp.pSRetoucheFly; + pSRetoucheFly2 = gProp.pSRetoucheFly2; + pSFlyOnlyDraw = gProp.pSFlyOnlyDraw; + pBLines = std::move(gProp.pBLines); + pSLines = std::move(gProp.pSLines); + pSSubsLines = std::move(gProp.pSSubsLines); + pSSpecSubsLines = std::move(gProp.pSSpecSubsLines); + pSProgress = gProp.pSProgress; + nSPixelSzW = gProp.nSPixelSzW; + nSPixelSzH = gProp.nSPixelSzH; + nSHalfPixelSzW = gProp.nSHalfPixelSzW; + nSHalfPixelSzH = gProp.nSHalfPixelSzH; + nSMinDistPixelW = gProp.nSMinDistPixelW; + nSMinDistPixelH = gProp.nSMinDistPixelH ; + aSGlobalRetoucheColor = aGlobalRetoucheColor; + aSScaleX = gProp.aSScaleX; + aSScaleY = gProp.aSScaleY; + + // Restoring globales to default + gProp.bSFlyMetafile = false; + gProp.pSFlyMetafileOut = nullptr; + gProp.pSRetoucheFly = nullptr; + gProp.pSRetoucheFly2 = nullptr; + gProp.nSPixelSzW = gProp.nSPixelSzH = + gProp.nSHalfPixelSzW = gProp.nSHalfPixelSzH = + gProp.nSMinDistPixelW = gProp.nSMinDistPixelH = 0; + gProp.aSScaleX = gProp.aSScaleY = 1.0; + gProp.pSProgress = nullptr; +} + +SwSavePaintStatics::~SwSavePaintStatics() +{ + // Restoring globales to saved one + gProp.pSGlobalShell = pSGlobalShell; + gProp.bSFlyMetafile = bSFlyMetafile; + gProp.pSFlyMetafileOut = pSFlyMetafileOut; + gProp.pSRetoucheFly = pSRetoucheFly; + gProp.pSRetoucheFly2 = pSRetoucheFly2; + gProp.pSFlyOnlyDraw = pSFlyOnlyDraw; + gProp.pBLines = std::move(pBLines); + gProp.pSLines = std::move(pSLines); + gProp.pSSubsLines = std::move(pSSubsLines); + gProp.pSSpecSubsLines = std::move(pSSpecSubsLines); + gProp.pSProgress = pSProgress; + gProp.nSPixelSzW = nSPixelSzW; + gProp.nSPixelSzH = nSPixelSzH; + gProp.nSHalfPixelSzW = nSHalfPixelSzW; + gProp.nSHalfPixelSzH = nSHalfPixelSzH; + gProp.nSMinDistPixelW = nSMinDistPixelW; + gProp.nSMinDistPixelH = nSMinDistPixelH; + aGlobalRetoucheColor = aSGlobalRetoucheColor; + gProp.aSScaleX = aSScaleX; + gProp.aSScaleY = aSScaleY; +} + +void BorderLines::AddBorderLines(drawinglayer::primitive2d::Primitive2DContainer&& rContainer) +{ + if(!rContainer.empty()) + { + m_Lines.append(std::move(rContainer)); + } +} + +SwLineRect::SwLineRect(const SwRect& rRect, const Color* pCol, const SvxBorderLineStyle nStyl, + const SwTabFrame* pT, const SubColFlags nSCol) + : SwRect(rRect) + , m_nStyle(nStyl) + , m_pTabFrame(pT) + , m_nSubColor(nSCol) + , m_bPainted(false) + , m_nLock(0) +{ + if ( pCol != nullptr ) + m_aColor = *pCol; +} + +bool SwLineRect::MakeUnion( const SwRect &rRect, SwPaintProperties const & properties) +{ + // It has already been tested outside, whether the rectangles have + // the same orientation (horizontal or vertical), color, etc. + if ( Height() > Width() ) //Vertical line + { + if ( Left() == rRect.Left() && Width() == rRect.Width() ) + { + // Merge when there is no gap between the lines + const tools::Long nAdd = properties.nSPixelSzW + properties.nSHalfPixelSzW; + if ( Bottom() + nAdd >= rRect.Top() && + Top() - nAdd <= rRect.Bottom() ) + { + Bottom( std::max( Bottom(), rRect.Bottom() ) ); + Top ( std::min( Top(), rRect.Top() ) ); + return true; + } + } + } + else + { + if ( Top() == rRect.Top() && Height() == rRect.Height() ) + { + // Merge when there is no gap between the lines + const tools::Long nAdd = properties.nSPixelSzW + properties.nSHalfPixelSzW; + if ( Right() + nAdd >= rRect.Left() && + Left() - nAdd <= rRect.Right() ) + { + Right( std::max( Right(), rRect.Right() ) ); + Left ( std::min( Left(), rRect.Left() ) ); + return true; + } + } + } + return false; +} + +void SwLineRects::AddLineRect( const SwRect &rRect, const Color *pCol, const SvxBorderLineStyle nStyle, + const SwTabFrame *pTab, const SubColFlags nSCol, SwPaintProperties const & properties ) +{ + // Loop backwards because lines which can be combined, can usually be painted + // in the same context + for (reverse_iterator it = m_aLineRects.rbegin(); it != m_aLineRects.rend(); ++it) + { + SwLineRect &rLRect = *it; + // Test for the orientation, color, table + if ( rLRect.GetTab() == pTab && + !rLRect.IsPainted() && rLRect.GetSubColor() == nSCol && + (rLRect.Height() > rLRect.Width()) == (rRect.Height() > rRect.Width()) && + (pCol && rLRect.GetColor() == *pCol) ) + { + if ( rLRect.MakeUnion( rRect, properties ) ) + return; + } + } + m_aLineRects.emplace_back(rRect, pCol, nStyle, pTab, nSCol); +} + +void SwLineRects::ConnectEdges( OutputDevice const *pOut, SwPaintProperties const & properties ) +{ + if ( pOut->GetOutDevType() != OUTDEV_PRINTER ) + { + // I'm not doing anything for a too small zoom + if ( properties.aSScaleX < aEdgeScale || properties.aSScaleY < aEdgeScale ) + return; + } + + static const tools::Long nAdd = 20; + + std::vector<SwLineRect*> aCheck; + + for (size_t i = 0; i < m_aLineRects.size(); ++i) + { + SwLineRect& rL1 = m_aLineRects[i]; + if ( !rL1.GetTab() || rL1.IsPainted() || rL1.IsLocked() ) + continue; + + aCheck.clear(); + + const bool bVert = rL1.Height() > rL1.Width(); + tools::Long nL1a, nL1b, nL1c, nL1d; + + if ( bVert ) + { + nL1a = rL1.Top(); nL1b = rL1.Left(); + nL1c = rL1.Right(); nL1d = rL1.Bottom(); + } + else + { + nL1a = rL1.Left(); nL1b = rL1.Top(); + nL1c = rL1.Bottom(); nL1d = rL1.Right(); + } + + // Collect all lines to possibly link with i1 + for (iterator it2 = m_aLineRects.begin(); it2 != m_aLineRects.end(); ++it2) + { + SwLineRect &rL2 = *it2; + if ( rL2.GetTab() != rL1.GetTab() || + rL2.IsPainted() || + rL2.IsLocked() || + (bVert == (rL2.Height() > rL2.Width())) ) + continue; + + tools::Long nL2a, nL2b, nL2c, nL2d; + if ( bVert ) + { + nL2a = rL2.Top(); nL2b = rL2.Left(); + nL2c = rL2.Right(); nL2d = rL2.Bottom(); + } + else + { + nL2a = rL2.Left(); nL2b = rL2.Top(); + nL2c = rL2.Bottom(); nL2d = rL2.Right(); + } + + if ( (nL1a - nAdd < nL2d && nL1d + nAdd > nL2a) && + ((nL1b > nL2b && nL1c < nL2c) || + (nL1c >= nL2c && nL1b - nAdd < nL2c) || + (nL1b <= nL2b && nL1c + nAdd > nL2b)) ) + { + aCheck.push_back( &rL2 ); + } + } + if ( aCheck.size() < 2 ) + continue; + + bool bRemove = false; + + // For each line test all following ones. + for ( size_t k = 0; !bRemove && k < aCheck.size(); ++k ) + { + SwLineRect &rR1 = *aCheck[k]; + + for ( size_t k2 = k+1; !bRemove && k2 < aCheck.size(); ++k2 ) + { + SwLineRect &rR2 = *aCheck[k2]; + if ( bVert ) + { + SwLineRect *pLA = nullptr; + SwLineRect *pLB = nullptr; + if ( rR1.Top() < rR2.Top() ) + { + pLA = &rR1; pLB = &rR2; + } + else if ( rR1.Top() > rR2.Top() ) + { + pLA = &rR2; pLB = &rR1; + } + // are k1 and k2 describing a double line? + if ( pLA && pLA->Bottom() + 60 > pLB->Top() ) + { + if ( rL1.Top() < pLA->Top() ) + { + if ( rL1.Bottom() == pLA->Bottom() ) + continue; //Small mistake (where?) + + SwRect aIns( rL1 ); + aIns.Bottom( pLA->Bottom() ); + if ( !rL1.Contains( aIns ) ) + continue; + m_aLineRects.emplace_back(aIns, &rL1.GetColor(), + SvxBorderLineStyle::SOLID, rL1.GetTab(), + SubColFlags::Tab); + if ( isFull() ) + { + --i; + k = aCheck.size(); + break; + } + } + + if ( rL1.Bottom() > pLB->Bottom() ) + rL1.Top( pLB->Top() ); // extend i1 on the top + else + bRemove = true; //stopping, remove i1 + } + } + else + { + SwLineRect *pLA = nullptr; + SwLineRect *pLB = nullptr; + if ( rR1.Left() < rR2.Left() ) + { + pLA = &rR1; pLB = &rR2; + } + else if ( rR1.Left() > rR2.Left() ) + { + pLA = &rR2; pLB = &rR1; + } + // Is it double line? + if ( pLA && pLA->Right() + 60 > pLB->Left() ) + { + if ( rL1.Left() < pLA->Left() ) + { + if ( rL1.Right() == pLA->Right() ) + continue; //small error + + SwRect aIns( rL1 ); + aIns.Right( pLA->Right() ); + if ( !rL1.Contains( aIns ) ) + continue; + m_aLineRects.emplace_back(aIns, &rL1.GetColor(), + SvxBorderLineStyle::SOLID, rL1.GetTab(), + SubColFlags::Tab); + if ( isFull() ) + { + --i; + k = aCheck.size(); + break; + } + } + if ( rL1.Right() > pLB->Right() ) + rL1.Left( pLB->Left() ); + else + bRemove = true; + } + } + } + } + if ( bRemove ) + { + m_aLineRects.erase(m_aLineRects.begin() + i); + --i; + } + } +} + +void SwSubsRects::RemoveSuperfluousSubsidiaryLines( const SwLineRects &rRects, SwPaintProperties const & properties ) +{ + // All help lines that are covered by any border will be removed or split + for (size_t i = 0; i < m_aLineRects.size(); ++i) + { + // get a copy instead of a reference, because an <insert> may destroy + // the object due to a necessary array resize. + const SwLineRect aSubsLineRect(m_aLineRects[i]); + + // add condition <aSubsLineRect.IsLocked()> in order to consider only + // border lines, which are *not* locked. + if ( aSubsLineRect.IsPainted() || + aSubsLineRect.IsLocked() ) + continue; + + const bool bVerticalSubs = aSubsLineRect.Height() > aSubsLineRect.Width(); + SwRect aSubsRect( aSubsLineRect ); + if ( bVerticalSubs ) + { + aSubsRect.AddLeft ( - (properties.nSPixelSzW+properties.nSHalfPixelSzW) ); + aSubsRect.AddRight ( properties.nSPixelSzW+properties.nSHalfPixelSzW ); + } + else + { + aSubsRect.AddTop ( - (properties.nSPixelSzH+properties.nSHalfPixelSzH) ); + aSubsRect.AddBottom( properties.nSPixelSzH+properties.nSHalfPixelSzH ); + } + for (const_iterator itK = rRects.m_aLineRects.begin(); itK != rRects.m_aLineRects.end(); + ++itK) + { + const SwLineRect &rLine = *itK; + + // do *not* consider painted or locked border lines. + // #i1837# - locked border lines have to be considered. + if ( rLine.IsLocked () ) + continue; + + if ( !bVerticalSubs == ( rLine.Height() > rLine.Width() ) ) //same direction? + continue; + + if ( aSubsRect.Overlaps( rLine ) ) + { + if ( bVerticalSubs ) // Vertical? + { + if ( aSubsRect.Left() <= rLine.Right() && + aSubsRect.Right() >= rLine.Left() ) + { + tools::Long nTmp = rLine.Top()-(properties.nSPixelSzH+1); + if ( aSubsLineRect.Top() < nTmp ) + { + SwRect aNewSubsRect( aSubsLineRect ); + aNewSubsRect.Bottom( nTmp ); + m_aLineRects.emplace_back(aNewSubsRect, nullptr, + aSubsLineRect.GetStyle(), nullptr, + aSubsLineRect.GetSubColor()); + } + nTmp = rLine.Bottom()+properties.nSPixelSzH+1; + if ( aSubsLineRect.Bottom() > nTmp ) + { + SwRect aNewSubsRect( aSubsLineRect ); + aNewSubsRect.Top( nTmp ); + m_aLineRects.emplace_back(aNewSubsRect, nullptr, + aSubsLineRect.GetStyle(), nullptr, + aSubsLineRect.GetSubColor()); + } + m_aLineRects.erase(m_aLineRects.begin() + i); + --i; + break; + } + } + else // Horizontal + { + if ( aSubsRect.Top() <= rLine.Bottom() && + aSubsRect.Bottom() >= rLine.Top() ) + { + tools::Long nTmp = rLine.Left()-(properties.nSPixelSzW+1); + if ( aSubsLineRect.Left() < nTmp ) + { + SwRect aNewSubsRect( aSubsLineRect ); + aNewSubsRect.Right( nTmp ); + m_aLineRects.emplace_back(aNewSubsRect, nullptr, + aSubsLineRect.GetStyle(), nullptr, + aSubsLineRect.GetSubColor()); + } + nTmp = rLine.Right()+properties.nSPixelSzW+1; + if ( aSubsLineRect.Right() > nTmp ) + { + SwRect aNewSubsRect( aSubsLineRect ); + aNewSubsRect.Left( nTmp ); + m_aLineRects.emplace_back(aNewSubsRect, nullptr, + aSubsLineRect.GetStyle(), nullptr, + aSubsLineRect.GetSubColor()); + } + m_aLineRects.erase(m_aLineRects.begin() + i); + --i; + break; + } + } + } + } + } +} + +void SwLineRects::LockLines( bool bLock ) +{ + for (SwLineRect& rLRect : m_aLineRects) + rLRect.Lock(bLock); +} + +static void lcl_DrawDashedRect( OutputDevice * pOut, SwLineRect const & rLRect ) +{ + tools::Long startX = rLRect.Left( ), endX; + tools::Long startY = rLRect.Top( ), endY; + + // Discriminate vertically stretched rect from horizontally stretched + // and restrict minimum nHalfLWidth to 1 + tools::Long nHalfLWidth = std::max( std::min( rLRect.Width( ), rLRect.Height( ) ) / 2, tools::Long(1) ); + + if ( rLRect.Height( ) > rLRect.Width( ) ) + { + startX += nHalfLWidth; + endX = startX; + endY = startY + rLRect.Height( ); + } + else + { + startY += nHalfLWidth; + endY = startY; + endX = startX + rLRect.Width( ); + } + + svtools::DrawLine( *pOut, Point( startX, startY ), Point( endX, endY ), + sal_uInt32( nHalfLWidth * 2 ), rLRect.GetStyle( ) ); +} + +void SwLineRects::PaintLines( OutputDevice *pOut, SwPaintProperties const &properties ) +{ + // Paint the borders. Sadly two passes are needed. + // Once for the inside and once for the outside edges of tables + if (m_aLineRects.size() == m_nLastCount) + return; + + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pOut ); + + pOut->Push( vcl::PushFlags::FILLCOLOR|vcl::PushFlags::LINECOLOR ); + pOut->SetFillColor(); + pOut->SetLineColor(); + ConnectEdges( pOut, properties ); + const Color *pLast = nullptr; + + bool bPaint2nd = false; + size_t nMinCount = m_aLineRects.size(); + + for (size_t i = 0; i < m_aLineRects.size(); ++i) + { + SwLineRect& rLRect = m_aLineRects[i]; + + if ( rLRect.IsPainted() ) + continue; + + if ( rLRect.IsLocked() ) + { + nMinCount = std::min( nMinCount, i ); + continue; + } + + // Paint it now or in the second pass? + bool bPaint = true; + if ( rLRect.GetTab() ) + { + if ( rLRect.Height() > rLRect.Width() ) + { + // Vertical edge, overlapping with the table edge? + SwTwips nLLeft = rLRect.Left() - 30, + nLRight = rLRect.Right() + 30, + nTLeft = rLRect.GetTab()->getFrameArea().Left() + rLRect.GetTab()->getFramePrintArea().Left(), + nTRight = rLRect.GetTab()->getFrameArea().Left() + rLRect.GetTab()->getFramePrintArea().Right(); + if ( (nTLeft >= nLLeft && nTLeft <= nLRight) || + (nTRight>= nLLeft && nTRight<= nLRight) ) + bPaint = false; + } + else + { + // Horizontal edge, overlapping with the table edge? + SwTwips nLTop = rLRect.Top() - 30, + nLBottom = rLRect.Bottom() + 30, + nTTop = rLRect.GetTab()->getFrameArea().Top() + rLRect.GetTab()->getFramePrintArea().Top(), + nTBottom = rLRect.GetTab()->getFrameArea().Top() + rLRect.GetTab()->getFramePrintArea().Bottom(); + if ( (nTTop >= nLTop && nTTop <= nLBottom) || + (nTBottom >= nLTop && nTBottom <= nLBottom) ) + bPaint = false; + } + } + if ( bPaint ) + { + if ( !pLast || *pLast != rLRect.GetColor() ) + { + pLast = &rLRect.GetColor(); + + DrawModeFlags nOldDrawMode = pOut->GetDrawMode(); + if( properties.pSGlobalShell->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + pOut->SetDrawMode( DrawModeFlags::Default ); + + pOut->SetLineColor( *pLast ); + pOut->SetFillColor( *pLast ); + pOut->SetDrawMode( nOldDrawMode ); + } + + if( !rLRect.IsEmpty() ) + lcl_DrawDashedRect( pOut, rLRect ); + rLRect.SetPainted(); + } + else + bPaint2nd = true; + } + if ( bPaint2nd ) + { + for (size_t i = 0; i < m_aLineRects.size(); ++i) + { + SwLineRect& rLRect = m_aLineRects[i]; + if ( rLRect.IsPainted() ) + continue; + + if ( rLRect.IsLocked() ) + { + nMinCount = std::min( nMinCount, i ); + continue; + } + + if ( !pLast || *pLast != rLRect.GetColor() ) + { + pLast = &rLRect.GetColor(); + + DrawModeFlags nOldDrawMode = pOut->GetDrawMode(); + if( properties.pSGlobalShell->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + pOut->SetDrawMode( DrawModeFlags::Default ); + } + + pOut->SetFillColor( *pLast ); + pOut->SetDrawMode( nOldDrawMode ); + } + if( !rLRect.IsEmpty() ) + lcl_DrawDashedRect( pOut, rLRect ); + rLRect.SetPainted(); + } + } + m_nLastCount = nMinCount; + pOut->Pop(); + +} + +void SwSubsRects::PaintSubsidiary( OutputDevice *pOut, + const SwLineRects *pRects, + SwPaintProperties const & properties ) +{ + if (m_aLineRects.empty()) + return; + + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pOut ); + + // Remove all help line that are almost covered (tables) + for (size_type i = 0; i != m_aLineRects.size(); ++i) + { + SwLineRect& rLi = m_aLineRects[i]; + const bool bVerticalSubs = rLi.Height() > rLi.Width(); + + for (size_type k = i + 1; k != m_aLineRects.size(); ++k) + { + SwLineRect& rLk = m_aLineRects[k]; + if ( rLi.SSize() == rLk.SSize() ) + { + if ( bVerticalSubs == ( rLk.Height() > rLk.Width() ) ) + { + if ( bVerticalSubs ) + { + tools::Long nLi = rLi.Right(); + tools::Long nLk = rLk.Right(); + if ( rLi.Top() == rLk.Top() && + ((nLi < rLk.Left() && nLi+21 > rLk.Left()) || + (nLk < rLi.Left() && nLk+21 > rLi.Left()))) + { + m_aLineRects.erase(m_aLineRects.begin() + i); + // don't continue with inner loop any more: + // the array may shrink! + --i; + break; + } + } + else + { + tools::Long nLi = rLi.Bottom(); + tools::Long nLk = rLk.Bottom(); + if ( rLi.Left() == rLk.Left() && + ((nLi < rLk.Top() && nLi+21 > rLk.Top()) || + (nLk < rLi.Top() && nLk+21 > rLi.Top()))) + { + m_aLineRects.erase(m_aLineRects.begin() + i); + // don't continue with inner loop any more: + // the array may shrink! + --i; + break; + } + } + } + } + } + } + + if (pRects && (!pRects->m_aLineRects.empty())) + RemoveSuperfluousSubsidiaryLines( *pRects, properties ); + + if (m_aLineRects.empty()) + return; + + pOut->Push( vcl::PushFlags::FILLCOLOR|vcl::PushFlags::LINECOLOR ); + pOut->SetLineColor(); + + // Reset draw mode in high contrast mode in order to get fill color + // set at output device. Recover draw mode after draw of lines. + // Necessary for the subsidiary lines painted by the fly frames. + DrawModeFlags nOldDrawMode = pOut->GetDrawMode(); + if( gProp.pSGlobalShell->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + pOut->SetDrawMode( DrawModeFlags::Default ); + } + + for (SwLineRect& rLRect : m_aLineRects) + { + // Add condition <!rLRect.IsLocked()> to prevent paint of locked subsidiary lines. + if ( !rLRect.IsPainted() && + !rLRect.IsLocked() ) + { + const Color *pCol = nullptr; + switch ( rLRect.GetSubColor() ) + { + case SubColFlags::Page: pCol = &SwViewOption::GetDocBoundariesColor(); break; + case SubColFlags::Fly: pCol = &SwViewOption::GetObjectBoundariesColor(); break; + case SubColFlags::Tab: pCol = &SwViewOption::GetTableBoundariesColor(); break; + case SubColFlags::Sect: pCol = &SwViewOption::GetSectionBoundColor(); break; + } + + if (pCol && pOut->GetFillColor() != *pCol) + pOut->SetFillColor( *pCol ); + pOut->DrawRect( rLRect.SVRect() ); + + rLRect.SetPainted(); + } + } + + pOut->SetDrawMode( nOldDrawMode ); + + pOut->Pop(); +} + +// Various functions that are use in this file. + +/** + * Function <SwAlignRect(..)> is also used outside this file + * + * Correction: adjust rectangle on pixel level in order to make sure, + * that the border "leaves its original pixel", if it has to + * No prior adjustments for odd relation between pixel and twip + */ +void SwAlignRect( SwRect &rRect, const SwViewShell *pSh, const vcl::RenderContext* pRenderContext ) +{ + if( !rRect.HasArea() ) + return; + + // Make sure that view shell (parameter <pSh>) exists, if the output device + // is taken from this view shell --> no output device, no alignment + // Output device taken from view shell <pSh>, if <gProp.bSFlyMetafile> not set + if ( !gProp.bSFlyMetafile && !pSh ) + { + return; + } + + const vcl::RenderContext *pOut = gProp.bSFlyMetafile ? + gProp.pSFlyMetafileOut.get() : pRenderContext; + + // Hold original rectangle in pixel + const tools::Rectangle aOrgPxRect = pOut->LogicToPixel( rRect.SVRect() ); + // Determine pixel-center rectangle in twip + const SwRect aPxCenterRect( pOut->PixelToLogic( aOrgPxRect ) ); + + // Perform adjustments on pixel level. + SwRect aAlignedPxRect( aOrgPxRect ); + if ( rRect.Top() > aPxCenterRect.Top() ) + { + // 'leave pixel overlapping on top' + aAlignedPxRect.AddTop( 1 ); + } + + if ( rRect.Bottom() < aPxCenterRect.Bottom() ) + { + // 'leave pixel overlapping on bottom' + aAlignedPxRect.AddBottom( - 1 ); + } + + if ( rRect.Left() > aPxCenterRect.Left() ) + { + // 'leave pixel overlapping on left' + aAlignedPxRect.AddLeft( 1 ); + } + + if ( rRect.Right() < aPxCenterRect.Right() ) + { + // 'leave pixel overlapping on right' + aAlignedPxRect.AddRight( - 1 ); + } + + // Consider negative width/height check, if aligned SwRect has negative width/height. + // If Yes, adjust it to width/height = 0 twip. + // NOTE: A SwRect with negative width/height can occur, if the width/height + // of the given SwRect in twip was less than a pixel in twip and that + // the alignment calculates that the aligned SwRect should not contain + // the pixels the width/height is on. + if ( aAlignedPxRect.Width() < 0 ) + { + aAlignedPxRect.Width(0); + } + if ( aAlignedPxRect.Height() < 0 ) + { + aAlignedPxRect.Height(0); + } + // Consider zero width/height for converting a rectangle from + // pixel to logic it needs a width/height. Thus, set width/height + // to one, if it's zero and correct this on the twip level after the conversion. + bool bZeroWidth = false; + if ( aAlignedPxRect.Width() == 0 ) + { + aAlignedPxRect.Width(1); + bZeroWidth = true; + } + bool bZeroHeight = false; + if ( aAlignedPxRect.Height() == 0 ) + { + aAlignedPxRect.Height(1); + bZeroHeight = true; + } + + rRect = SwRect(pOut->PixelToLogic( aAlignedPxRect.SVRect() )); + + // Consider zero width/height and adjust calculated aligned twip rectangle. + // Reset width/height to zero; previous negative width/height haven't to be considered. + if ( bZeroWidth ) + { + rRect.Width(0); + } + if ( bZeroHeight ) + { + rRect.Height(0); + } +} + +/** + * Method to pixel-align rectangle for drawing graphic object + * + * Because we are drawing graphics from the left-top-corner in conjunction + * with size coordinates, these coordinates have to be calculated at a pixel + * level. + * Thus, we convert the rectangle to pixel and then convert to left-top-corner + * and then get size of pixel rectangle back to logic. + * This calculation is necessary, because there's a different between + * the conversion from logic to pixel of a normal rectangle with its left-top- + * and right-bottom-corner and the same conversion of the same rectangle + * with left-top-corner and size. + * + * NOTE: Call this method before each <GraphicObject.Draw(...)> +*/ +void SwAlignGrfRect( SwRect *pGrfRect, const vcl::RenderContext &rOut ) +{ + tools::Rectangle aPxRect = rOut.LogicToPixel( pGrfRect->SVRect() ); + pGrfRect->Pos( rOut.PixelToLogic( aPxRect.TopLeft() ) ); + pGrfRect->SSize( rOut.PixelToLogic( aPxRect.GetSize() ) ); +} + +static tools::Long lcl_AlignWidth( const tools::Long nWidth, SwPaintProperties const & properties ) +{ + if ( nWidth ) + { + const tools::Long nW = nWidth % properties.nSPixelSzW; + + if ( !nW || nW > properties.nSHalfPixelSzW ) + return std::max(tools::Long(1), nWidth - properties.nSHalfPixelSzW); + } + return nWidth; +} + +static tools::Long lcl_AlignHeight( const tools::Long nHeight, SwPaintProperties const & properties ) +{ + if ( nHeight ) + { + const tools::Long nH = nHeight % properties.nSPixelSzH; + + if ( !nH || nH > properties.nSHalfPixelSzH ) + return std::max(tools::Long(1), nHeight - properties.nSHalfPixelSzH); + } + return nHeight; +} + +/** + * Calculate PrtArea plus surrounding plus shadow + */ +static void lcl_CalcBorderRect( SwRect &rRect, const SwFrame *pFrame, + const SwBorderAttrs &rAttrs, + const bool bShadow, + SwPaintProperties const & properties) +{ + // Special handling for cell frames. + // The printing area of a cell frame is completely enclosed in the frame area + // and a cell frame has no shadow. Thus, for cell frames the calculated + // area equals the frame area. + // Notes: Borders of cell frames in R2L text direction will switch its side + // - left border is painted on the right; right border on the left. + // See <lcl_PaintLeftLine> and <lcl_PaintRightLine>. + if( pFrame->IsSctFrame() ) + { + rRect = pFrame->getFramePrintArea(); + rRect.Pos() += pFrame->getFrameArea().Pos(); + } + else if ( pFrame->IsCellFrame() ) + rRect = pFrame->getFrameArea(); + else + { + rRect = pFrame->getFramePrintArea(); + rRect.Pos() += pFrame->getFrameArea().Pos(); + + SwRectFn fnRect = pFrame->IsVertical() ? ( pFrame->IsVertLR() ? (pFrame->IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ) : fnRectHori; + + const SvxBoxItem &rBox = rAttrs.GetBox(); + const bool bTop = 0 != (pFrame->*fnRect->fnGetTopMargin)(); + if ( bTop || rBox.GetTop() ) + { + SwTwips nDiff = rBox.GetTop() ? + rBox.CalcLineSpace( SvxBoxItemLine::TOP, /*bEvenIfNoLine=*/false, /*bAllowNegative=*/true ) : + rBox.GetDistance( SvxBoxItemLine::TOP ); + if( nDiff ) + (rRect.*fnRect->fnSubTop)( nDiff ); + } + + const bool bBottom = 0 != (pFrame->*fnRect->fnGetBottomMargin)(); + if ( bBottom ) + { + SwTwips nDiff = 0; + // #i29550# + if ( pFrame->IsTabFrame() && + static_cast<const SwTabFrame*>(pFrame)->IsCollapsingBorders() ) + { + // For collapsing borders, we have to add the height of + // the height of the last line + nDiff = static_cast<const SwTabFrame*>(pFrame)->GetBottomLineSize(); + } + else + { + nDiff = rBox.GetBottom() ? + rBox.CalcLineSpace( SvxBoxItemLine::BOTTOM ) : + rBox.GetDistance( SvxBoxItemLine::BOTTOM ); + } + if( nDiff ) + (rRect.*fnRect->fnAddBottom)( nDiff ); + } + + if ( rBox.GetLeft() ) + (rRect.*fnRect->fnSubLeft)( rBox.CalcLineSpace( SvxBoxItemLine::LEFT ) ); + else + (rRect.*fnRect->fnSubLeft)( rBox.GetDistance( SvxBoxItemLine::LEFT ) ); + + if ( rBox.GetRight() ) + (rRect.*fnRect->fnAddRight)( rBox.CalcLineSpace( SvxBoxItemLine::RIGHT ) ); + else + (rRect.*fnRect->fnAddRight)( rBox.GetDistance( SvxBoxItemLine::RIGHT ) ); + + if ( bShadow && rAttrs.GetShadow().GetLocation() != SvxShadowLocation::NONE ) + { + const SvxShadowItem &rShadow = rAttrs.GetShadow(); + if ( bTop ) + (rRect.*fnRect->fnSubTop)(rShadow.CalcShadowSpace(SvxShadowItemSide::TOP)); + (rRect.*fnRect->fnSubLeft)(rShadow.CalcShadowSpace(SvxShadowItemSide::LEFT)); + if ( bBottom ) + (rRect.*fnRect->fnAddBottom) + (rShadow.CalcShadowSpace( SvxShadowItemSide::BOTTOM )); + (rRect.*fnRect->fnAddRight)(rShadow.CalcShadowSpace(SvxShadowItemSide::RIGHT)); + } + } + + ::SwAlignRect( rRect, properties.pSGlobalShell, properties.pSGlobalShell ? properties.pSGlobalShell->GetOut() : nullptr ); +} + +/** + * Extend left/right border/shadow rectangle to bottom of previous frame/to + * top of next frame, if border/shadow is joined with previous/next frame + */ +static void lcl_ExtendLeftAndRight( SwRect& _rRect, + const SwFrame& _rFrame, + const SwBorderAttrs& _rAttrs, + const SwRectFn& _rRectFn ) +{ + if ( _rAttrs.JoinedWithPrev( _rFrame ) ) + { + const SwFrame* pPrevFrame = _rFrame.GetPrev(); + (_rRect.*_rRectFn->fnSetTop)( (pPrevFrame->*_rRectFn->fnGetPrtBottom)() ); + } + if ( _rAttrs.JoinedWithNext( _rFrame ) ) + { + const SwFrame* pNextFrame = _rFrame.GetNext(); + (_rRect.*_rRectFn->fnSetBottom)( (pNextFrame->*_rRectFn->fnGetPrtTop)() ); + } +} + +/// Returns a range suitable for subtraction when lcl_SubtractFlys() is used. +/// Otherwise DrawFillAttributes() expands the clip path itself. +static basegfx::B2DRange lcl_ShrinkFly(const SwRect& rRect) +{ + static MapMode aMapMode(MapUnit::MapTwip); + static const Size aSingleUnit = Application::GetDefaultDevice()->PixelToLogic(Size(1, 1), aMapMode); + + double x1 = rRect.Left() + aSingleUnit.getWidth(); + double y1 = rRect.Top() + aSingleUnit.getHeight(); + double x2 = rRect.Right() - aSingleUnit.getWidth(); + double y2 = rRect.Bottom() - aSingleUnit.getHeight(); + + return basegfx::B2DRange(x1, y1, x2, y2); +} + +static void lcl_SubtractFlys( const SwFrame *pFrame, const SwPageFrame *pPage, + const SwRect &rRect, SwRegionRects &rRegion, basegfx::utils::B2DClipState& rClipState, SwPaintProperties const & rProperties) +{ + const SwSortedObjs& rObjs = *pPage->GetSortedObjs(); + const SwFlyFrame* pSelfFly = pFrame->IsInFly() ? pFrame->FindFlyFrame() : gProp.pSRetoucheFly2; + if (!gProp.pSRetoucheFly) + gProp.pSRetoucheFly = gProp.pSRetoucheFly2; + + for (size_t j = 0; (j < rObjs.size()) && !rRegion.empty(); ++j) + { + const SwAnchoredObject* pAnchoredObj = rObjs[j]; + const SdrObject* pSdrObj = pAnchoredObj->GetDrawObj(); + + // Do not consider invisible objects + if (!pPage->GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId(pSdrObj->GetLayer())) + continue; + + const SwFlyFrame *pFly = pAnchoredObj->DynCastFlyFrame(); + if (!pFly) + continue; + + if (pSelfFly == pFly || gProp.pSRetoucheFly == pFly || !rRect.Overlaps(pFly->getFrameArea())) + continue; + + if (!pFly->GetFormat()->GetPrint().GetValue() && + (OUTDEV_PRINTER == gProp.pSGlobalShell->GetOut()->GetOutDevType() || + gProp.pSGlobalShell->IsPreview())) + continue; + + const bool bLowerOfSelf = pSelfFly && pFly->IsLowerOf( pSelfFly ); + + //For character bound Flys only examine those Flys in which it is not + //anchored itself. + //Why only for character bound ones you may ask? It never makes sense to + //subtract frames in which it is anchored itself right? + if (pSelfFly && pSelfFly->IsLowerOf(pFly)) + continue; + + //Any why does it not apply for the RetoucheFly too? + if (gProp.pSRetoucheFly && gProp.pSRetoucheFly->IsLowerOf(pFly)) + continue; + +#if OSL_DEBUG_LEVEL > 0 + //Flys who are anchored inside their own one, must have a bigger OrdNum + //or be character bound. + if (pSelfFly && bLowerOfSelf) + { + OSL_ENSURE( pFly->IsFlyInContentFrame() || + pSdrObj->GetOrdNumDirect() > pSelfFly->GetVirtDrawObj()->GetOrdNumDirect(), + "Fly with wrong z-Order" ); + } +#endif + + bool bStopOnHell = true; + if (pSelfFly) + { + const SdrObject *pTmp = pSelfFly->GetVirtDrawObj(); + if (pSdrObj->GetLayer() == pTmp->GetLayer()) + { + if (pSdrObj->GetOrdNumDirect() < pTmp->GetOrdNumDirect()) + //In the same layer we only observe those that are above. + continue; + } + else + { + if (!bLowerOfSelf && !pFly->GetFormat()->GetOpaque().GetValue()) + //From other layers we are only interested in non + //transparent ones or those that are internal + continue; + bStopOnHell = false; + } + } + if (gProp.pSRetoucheFly) + { + const SdrObject *pTmp = gProp.pSRetoucheFly->GetVirtDrawObj(); + if ( pSdrObj->GetLayer() == pTmp->GetLayer() ) + { + if ( pSdrObj->GetOrdNumDirect() < pTmp->GetOrdNumDirect() ) + //In the same layer we only observe those that are above. + continue; + } + else + { + if (!pFly->IsLowerOf( gProp.pSRetoucheFly ) && !pFly->GetFormat()->GetOpaque().GetValue()) + //From other layers we are only interested in non + //transparent ones or those that are internal + continue; + bStopOnHell = false; + } + } + + //If the content of the Fly is transparent, we subtract it only if it's + //contained in the hell layer. + const IDocumentDrawModelAccess& rIDDMA = pFly->GetFormat()->getIDocumentDrawModelAccess(); + bool bHell = pSdrObj->GetLayer() == rIDDMA.GetHellId(); + if ( (bStopOnHell && bHell) || + /// Change internal order of condition + /// first check "!bHell", then "..->Lower()" and "..->IsNoTextFrame()" + /// have not to be performed, if frame is in "Hell" + ( !bHell && pFly->Lower() && pFly->Lower()->IsNoTextFrame() && + (static_cast<SwNoTextFrame const*>(pFly->Lower())->IsTransparent() || + static_cast<SwNoTextFrame const*>(pFly->Lower())->HasAnimation() || + pFly->GetFormat()->GetSurround().IsContour() + ) + ) + ) + continue; + + // Own if-statements for transparent background/shadow of fly frames + // in order to handle special conditions. + if (pFly->IsBackgroundTransparent()) + { + // Background <pFly> is transparent drawn. Thus normally, its region + // have not to be subtracted from given region. + // But, if method is called for a fly frame and + // <pFly> is a direct lower of this fly frame and + // <pFly> inherites its transparent background brush from its parent, + // then <pFly> frame area have to be subtracted from given region. + // NOTE: Because in Status Quo transparent backgrounds can only be + // assigned to fly frames, the handle of this special case + // avoids drawing of transparent areas more than once, if + // a fly frame inherites a transparent background from its + // parent fly frame. + if (pFrame->IsFlyFrame() && + (pFly->GetAnchorFrame()->FindFlyFrame() == pFrame) && + pFly->GetFormat()->IsBackgroundBrushInherited() + ) + { + SwRect aRect; + SwBorderAttrAccess aAccess( SwFrame::GetCache(), static_cast<SwFrame const *>(pFly) ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + ::lcl_CalcBorderRect( aRect, pFly, rAttrs, true, rProperties ); + rRegion -= aRect; + rClipState.subtractRange(lcl_ShrinkFly(aRect)); + continue; + } + else + { + continue; + } + } + + if (bHell && pFly->GetAnchorFrame()->IsInFly()) + { + //So the border won't get dismantled by the background of the other + //Fly. + SwRect aRect; + SwBorderAttrAccess aAccess( SwFrame::GetCache(), static_cast<SwFrame const *>(pFly) ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + ::lcl_CalcBorderRect( aRect, pFly, rAttrs, true, rProperties ); + rRegion -= aRect; + rClipState.subtractRange(lcl_ShrinkFly(aRect)); + } + else + { + SwRect aRect( pFly->getFramePrintArea() ); + aRect += pFly->getFrameArea().Pos(); + rRegion -= aRect; + rClipState.subtractRange(lcl_ShrinkFly(aRect)); + } + } + if (gProp.pSRetoucheFly == gProp.pSRetoucheFly2) + gProp.pSRetoucheFly = nullptr; +} + +static void lcl_implDrawGraphicBackground(const SvxBrushItem& _rBackgrdBrush, + vcl::RenderContext& _rOut, + const SwRect& _rAlignedPaintRect, + const GraphicObject& _rGraphicObj, + SwPaintProperties const & properties) +{ + /// determine color of background + /// If color of background brush is not "no fill"/"auto fill" or + /// <SwPaintProperties.bSFlyMetafile> is set, use color of background brush, otherwise + /// use global retouche color. + const Color aColor( ( (_rBackgrdBrush.GetColor() != COL_TRANSPARENT) || properties.bSFlyMetafile ) + ? _rBackgrdBrush.GetColor() + : aGlobalRetoucheColor ); + + /// determine, if background color have to be drawn transparent + /// and calculate transparency percent value + sal_Int8 nTransparencyPercent = 0; + bool bDrawTransparent = false; + if ( aColor.IsTransparent() ) + /// background color is transparent --> draw transparent. + { + bDrawTransparent = true; + nTransparencyPercent = ((255 - aColor.GetAlpha())*100 + 0x7F)/0xFF; + } + else if ( (_rGraphicObj.GetAttr().IsTransparent()) && + (_rBackgrdBrush.GetColor() == COL_TRANSPARENT) ) + /// graphic is drawn transparent and background color is + /// "no fill"/"auto fill" --> draw transparent + { + bDrawTransparent = true; + nTransparencyPercent = 100 - (_rGraphicObj.GetAttr().GetAlpha() * 100 + 127) / 255; + } + + if ( bDrawTransparent ) + { + /// draw background transparent + if( _rOut.GetFillColor() != aColor.GetRGBColor() ) + _rOut.SetFillColor( aColor.GetRGBColor() ); + tools::PolyPolygon aPoly( _rAlignedPaintRect.SVRect() ); + _rOut.DrawTransparent( aPoly, nTransparencyPercent ); + } + else + { + /// draw background opaque + if ( _rOut.GetFillColor() != aColor ) + _rOut.SetFillColor( aColor ); + _rOut.DrawRect( _rAlignedPaintRect.SVRect() ); + } +} + +/** + * This is a local help method to draw a background for a graphic + * + * Under certain circumstances we have to draw a background for a graphic. + * This method takes care of the conditions and draws the background with the + * corresponding color. + * Method introduced for bug fix #103876# in order to optimize drawing tiled + * background graphics. Previously, this code was integrated in method + * <lcl_DrawGraphic>. + * Method implemented as an inline, checking the conditions and calling method + * method <lcl_implDrawGraphicBackground(..)> for the intrinsic drawing. + * + * @param _rBackgrdBrush + * background brush contain the color the background has to be drawn. + * + * @param _rOut + * output device the background has to be drawn in. + * + * @param _rAlignedPaintRect + * paint rectangle in the output device, which has to be drawn with the background. + * rectangle have to be aligned by method ::SwAlignRect + * + * @param _rGraphicObj + * graphic object, for which the background has to be drawn. Used for checking + * the transparency of its bitmap, its type and if the graphic is drawn transparent + * + * @param _bNumberingGraphic + * boolean indicating that graphic is used as a numbering. + * + * @param _bBackgrdAlreadyDrawn + * boolean (optional; default: false) indicating, if the background is already drawn. +*/ +static void lcl_DrawGraphicBackground( const SvxBrushItem& _rBackgrdBrush, + OutputDevice& _rOut, + const SwRect& _rAlignedPaintRect, + const GraphicObject& _rGraphicObj, + bool _bNumberingGraphic, + SwPaintProperties const & properties, + bool _bBackgrdAlreadyDrawn = false) +{ + // draw background with background color, if + // (1) graphic is not used as a numbering AND + // (2) background is not already drawn AND + // (3) intrinsic graphic is transparent OR intrinsic graphic doesn't exists + if ( !_bNumberingGraphic && + !_bBackgrdAlreadyDrawn && + ( _rGraphicObj.IsTransparent() || _rGraphicObj.GetType() == GraphicType::NONE ) + ) + { + lcl_implDrawGraphicBackground( _rBackgrdBrush, _rOut, _rAlignedPaintRect, _rGraphicObj, properties ); + } +} + +/** + * NNOTE: the transparency of the background graphic is saved in + * SvxBrushItem.GetGraphicObject(<shell>).GetAttr().Set/GetTransparency() + * and is considered in the drawing of the graphic + * + * Thus, to provide transparent background graphic for text frames nothing + * has to be coded + * + * Use align rectangle for drawing graphic Pixel-align coordinates for + * drawing graphic + * Outsource code for drawing background of the graphic + * with a background color in method <lcl_DrawGraphicBackground> + * + * Also, change type of <bGrfNum> and <bClip> from <bool> to <bool> + */ +static void lcl_DrawGraphic( const SvxBrushItem& rBrush, vcl::RenderContext &rOutDev, + const SwViewShell &rSh, const SwRect &rGrf, const SwRect &rOut, + bool bGrfNum, + SwPaintProperties const & properties, + bool bBackgrdAlreadyDrawn ) + // add parameter <bBackgrdAlreadyDrawn> to indicate + // that the background is already drawn. +{ + // Calculate align rectangle from parameter <rGrf> and use aligned + // rectangle <aAlignedGrfRect> in the following code + SwRect aAlignedGrfRect = rGrf; + ::SwAlignRect( aAlignedGrfRect, &rSh, &rOutDev ); + + // Change type from <bool> to <bool>. + const bool bNotInside = !rOut.Contains( aAlignedGrfRect ); + if ( bNotInside ) + { + rOutDev.Push( vcl::PushFlags::CLIPREGION ); + rOutDev.IntersectClipRegion( rOut.SVRect() ); + } + + GraphicObject *pGrf = const_cast<GraphicObject*>(rBrush.GetGraphicObject()); + + OUString aOriginURL = pGrf->GetGraphic().getOriginURL(); + if (pGrf->GetGraphic().GetType() == GraphicType::Default && !aOriginURL.isEmpty()) + { + Graphic aGraphic = vcl::graphic::loadFromURL(aOriginURL); + pGrf->SetGraphic(aGraphic); + } + + // Outsource drawing of background with a background color + ::lcl_DrawGraphicBackground( rBrush, rOutDev, aAlignedGrfRect, *pGrf, bGrfNum, properties, bBackgrdAlreadyDrawn ); + + // Because for drawing a graphic left-top-corner and size coordinates are + // used, these coordinates have to be determined on pixel level. + ::SwAlignGrfRect( &aAlignedGrfRect, rOutDev ); + + const basegfx::B2DHomMatrix aGraphicTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aAlignedGrfRect.Width(), aAlignedGrfRect.Height(), + aAlignedGrfRect.Left(), aAlignedGrfRect.Top())); + + paintGraphicUsingPrimitivesHelper( + rOutDev, + *pGrf, + pGrf->GetAttr(), + aGraphicTransform, + OUString(), + OUString(), + OUString()); + + if ( bNotInside ) + rOutDev.Pop(); +} + +bool DrawFillAttributes( + const drawinglayer::attribute::SdrAllFillAttributesHelperPtr& rFillAttributes, + const SwRect& rOriginalLayoutRect, + const SwRegionRects& rPaintRegion, + const basegfx::utils::B2DClipState& rClipState, + vcl::RenderContext& rOut) +{ + if(rFillAttributes && rFillAttributes->isUsed()) + { + basegfx::B2DRange aPaintRange( + rPaintRegion.GetOrigin().Left(), + rPaintRegion.GetOrigin().Top(), + rPaintRegion.GetOrigin().Right(), + rPaintRegion.GetOrigin().Bottom()); + + if (!aPaintRange.isEmpty() && + !rPaintRegion.empty() && + !basegfx::fTools::equalZero(aPaintRange.getWidth()) && + !basegfx::fTools::equalZero(aPaintRange.getHeight())) + { + // need to expand for correct AAed and non-AAed visualization as primitive. + // This must probably be removed again when we will be able to get all Writer visualization + // as primitives and Writer prepares all it's stuff in high precision coordinates (also + // needs to avoid moving boundaries around to better show overlapping stuff...) + if(SvtOptionsDrawinglayer::IsAntiAliasing()) + { + // if AAed in principle expand by 0.5 in all directions. Since painting edges of + // AAed regions does not add to no transparence (0.5 opacity covered by 0.5 opacity + // is not full opacity but 0.75 opacity) we need some overlap here to avoid paint + // artifacts. Checked experimentally - a little bit more in Y is needed, probably + // due to still existing integer alignment and crunching in writer. + static const double fExpandX = 0.55; + static const double fExpandY = 0.70; + const basegfx::B2DVector aSingleUnit(rOut.GetInverseViewTransformation() * basegfx::B2DVector(fExpandX, fExpandY)); + + aPaintRange.expand(aPaintRange.getMinimum() - aSingleUnit); + aPaintRange.expand(aPaintRange.getMaximum() + aSingleUnit); + } + else + { + // if not AAed expand by one unit to bottom right due to the missing unit + // from SwRect/Rectangle integer handling + const basegfx::B2DVector aSingleUnit(rOut.GetInverseViewTransformation() * basegfx::B2DVector(1.0, 1.0)); + + aPaintRange.expand(aPaintRange.getMaximum() + aSingleUnit); + } + + const basegfx::B2DRange aDefineRange( + rOriginalLayoutRect.Left(), + rOriginalLayoutRect.Top(), + rOriginalLayoutRect.Right(), + rOriginalLayoutRect.Bottom()); + + const drawinglayer::primitive2d::Primitive2DContainer& rSequence = rFillAttributes->getPrimitive2DSequence( + aPaintRange, + aDefineRange); + + if(rSequence.size()) + { + drawinglayer::primitive2d::Primitive2DContainer const* + pPrimitives(&rSequence); + drawinglayer::primitive2d::Primitive2DContainer primitives; + // tdf#86578 the awful lcl_SubtractFlys hack + if (rPaintRegion.size() > 1 || rPaintRegion[0] != rPaintRegion.GetOrigin()) + { + basegfx::B2DPolyPolygon const& maskRegion(rClipState.getClipPoly()); + primitives.resize(1); + primitives[0] = new drawinglayer::primitive2d::MaskPrimitive2D( + maskRegion, drawinglayer::primitive2d::Primitive2DContainer(rSequence)); + pPrimitives = &primitives; + } + assert(pPrimitives && pPrimitives->size()); + + const drawinglayer::geometry::ViewInformation2D aViewInformation2D( + basegfx::B2DHomMatrix(), + rOut.GetViewTransformation(), + aPaintRange, + nullptr, + 0.0); + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor(drawinglayer::processor2d::createProcessor2DFromOutputDevice( + rOut, + aViewInformation2D) ); + pProcessor->process(*pPrimitives); + return true; + } + } + } + + return false; +} + +void DrawGraphic( + const SvxBrushItem *pBrush, + vcl::RenderContext &rOutDev, + const SwRect &rOrg, + const SwRect &rOut, + const sal_uInt8 nGrfNum, + const bool bConsiderBackgroundTransparency ) + // Add 6th parameter to indicate that method should + // consider background transparency, saved in the color of the brush item +{ + SwViewShell &rSh = *gProp.pSGlobalShell; + bool bReplaceGrfNum = GRFNUM_REPLACE == nGrfNum; + bool bGrfNum = GRFNUM_NO != nGrfNum; + Size aGrfSize; + SvxGraphicPosition ePos = GPOS_NONE; + if( pBrush && !bReplaceGrfNum ) + { + if( rSh.GetViewOptions()->IsGraphic() ) + { + OUString referer; + SfxObjectShell * sh = rSh.GetDoc()->GetPersist(); + if (sh != nullptr && sh->HasName()) { + referer = sh->GetMedium()->GetName(); + } + const Graphic* pGrf = pBrush->GetGraphic(referer); + if( pGrf && GraphicType::NONE != pGrf->GetType() ) + { + ePos = pBrush->GetGraphicPos(); + if( pGrf->IsSupportedGraphic() ) + // don't the use the specific output device! Bug 94802 + aGrfSize = ::GetGraphicSizeTwip( *pGrf, nullptr ); + } + } + else + bReplaceGrfNum = bGrfNum; + } + + SwRect aGrf; + aGrf.SSize( aGrfSize ); + bool bDraw = true; + bool bRetouche = true; + switch ( ePos ) + { + case GPOS_LT: + aGrf.Pos() = rOrg.Pos(); + break; + + case GPOS_MT: + aGrf.Pos().setY( rOrg.Top() ); + aGrf.Pos().setX( rOrg.Left() + rOrg.Width()/2 - aGrfSize.Width()/2 ); + break; + + case GPOS_RT: + aGrf.Pos().setY( rOrg.Top() ); + aGrf.Pos().setX( rOrg.Right() - aGrfSize.Width() ); + break; + + case GPOS_LM: + aGrf.Pos().setY( rOrg.Top() + rOrg.Height()/2 - aGrfSize.Height()/2 ); + aGrf.Pos().setX( rOrg.Left() ); + break; + + case GPOS_MM: + aGrf.Pos().setY( rOrg.Top() + rOrg.Height()/2 - aGrfSize.Height()/2 ); + aGrf.Pos().setX( rOrg.Left() + rOrg.Width()/2 - aGrfSize.Width()/2 ); + break; + + case GPOS_RM: + aGrf.Pos().setY( rOrg.Top() + rOrg.Height()/2 - aGrfSize.Height()/2 ); + aGrf.Pos().setX( rOrg.Right() - aGrfSize.Width() ); + break; + + case GPOS_LB: + aGrf.Pos().setY( rOrg.Bottom() - aGrfSize.Height() ); + aGrf.Pos().setX( rOrg.Left() ); + break; + + case GPOS_MB: + aGrf.Pos().setY( rOrg.Bottom() - aGrfSize.Height() ); + aGrf.Pos().setX( rOrg.Left() + rOrg.Width()/2 - aGrfSize.Width()/2 ); + break; + + case GPOS_RB: + aGrf.Pos().setY( rOrg.Bottom() - aGrfSize.Height() ); + aGrf.Pos().setX( rOrg.Right() - aGrfSize.Width() ); + break; + + case GPOS_AREA: + aGrf = rOrg; + // Despite the fact that the background graphic has to fill the complete + // area, we already checked, whether the graphic will completely fill out + // the region the <rOut> that is to be painted. Thus, nothing has to be + // touched again. + // E.g. this is the case for a Fly Frame without a background + // brush positioned on the border of the page which inherited the background + // brush from the page. + bRetouche = !rOut.Contains( aGrf ); + break; + + case GPOS_TILED: + { + // draw background of tiled graphic before drawing tiled graphic in loop + // determine graphic object + GraphicObject* pGraphicObj = const_cast< GraphicObject* >(pBrush->GetGraphicObject()); + // calculate aligned paint rectangle + SwRect aAlignedPaintRect = rOut; + ::SwAlignRect( aAlignedPaintRect, &rSh, &rOutDev ); + // draw background color for aligned paint rectangle + lcl_DrawGraphicBackground( *pBrush, rOutDev, aAlignedPaintRect, *pGraphicObj, bGrfNum, gProp ); + + // set left-top-corner of background graphic to left-top-corner of the + // area, from which the background brush is determined. + aGrf.Pos() = rOrg.Pos(); + // setup clipping at output device + rOutDev.Push( vcl::PushFlags::CLIPREGION ); + rOutDev.IntersectClipRegion( rOut.SVRect() ); + // use new method <GraphicObject::DrawTiled(::)> + { + // calculate paint offset + Point aPaintOffset( aAlignedPaintRect.Pos() - aGrf.Pos() ); + // draw background graphic tiled for aligned paint rectangle + // #i42643# + // For PDF export, every draw operation for bitmaps takes a + // noticeable amount of place (~50 characters). Thus, optimize + // between tile bitmap size and number of drawing operations here. + + // A_out + // n_chars = k1 * ---------- + k2 * A_bitmap + // A_bitmap + + // minimum n_chars is obtained for (derive for A_bitmap, + // set to 0, take positive solution): + // k1 + // A_bitmap = Sqrt( ---- A_out ) + // k2 + + // where k1 is the number of chars per draw operation, and + // k2 is the number of chars per bitmap pixel. + // This is approximately 50 and 7 for current PDF writer, respectively. + + const double k1( 50 ); + const double k2( 7 ); + const Size aSize( aAlignedPaintRect.SSize() ); + const double Abitmap( k1/k2 * static_cast<double>(aSize.Width())*aSize.Height() ); + + pGraphicObj->DrawTiled( rOutDev, + aAlignedPaintRect.SVRect(), + aGrf.SSize(), + Size( aPaintOffset.X(), aPaintOffset.Y() ), + std::max( 128, static_cast<int>( sqrt(sqrt( Abitmap)) + .5 ) ) ); + } + // reset clipping at output device + rOutDev.Pop(); + // set <bDraw> and <bRetouche> to false, indicating that background + // graphic and background are already drawn. + bDraw = bRetouche = false; + } + break; + + case GPOS_NONE: + bDraw = false; + break; + + default: OSL_ENSURE( false, "new Graphic position?" ); + } + + /// init variable <bGrfBackgrdAlreadDrawn> to indicate, if background of + /// graphic is already drawn or not. + bool bGrfBackgrdAlreadyDrawn = false; + if ( bRetouche ) + { + rOutDev.Push( vcl::PushFlags::FILLCOLOR|vcl::PushFlags::LINECOLOR ); + rOutDev.SetLineColor(); + + // check, if an existing background graphic (not filling the complete + // background) is transparent drawn and the background color is + // "no fill" respectively "auto fill", if background transparency + // has to be considered. + // If YES, memorize transparency of background graphic. + // check also, if background graphic bitmap is transparent. + bool bTransparentGrfWithNoFillBackgrd = false; + sal_Int32 nGrfTransparency = 0; + bool bGrfIsTransparent = false; + if ( (ePos != GPOS_NONE) && + (ePos != GPOS_TILED) && (ePos != GPOS_AREA) + ) + { + GraphicObject *pGrf = const_cast<GraphicObject*>(pBrush->GetGraphicObject()); + if ( bConsiderBackgroundTransparency ) + { + GraphicAttr aGrfAttr = pGrf->GetAttr(); + if ( (aGrfAttr.IsTransparent()) && + (pBrush->GetColor() == COL_TRANSPARENT) + ) + { + bTransparentGrfWithNoFillBackgrd = true; + nGrfTransparency = 255 - aGrfAttr.GetAlpha(); + } + } + if ( pGrf->IsTransparent() ) + { + bGrfIsTransparent = true; + } + } + + // to get color of brush, check background color against COL_TRANSPARENT ("no fill"/"auto fill") + // instead of checking, if transparency is not set. + const Color aColor( pBrush && + ( (pBrush->GetColor() != COL_TRANSPARENT) || + gProp.bSFlyMetafile ) + ? pBrush->GetColor() + : aGlobalRetoucheColor ); + + // determine, if background region have to be + // drawn transparent. + // background region has to be drawn transparent, if + // background transparency have to be considered + // AND + // ( background color is transparent OR + // background graphic is transparent and background color is "no fill" + // ) + + enum DrawStyle { + Default, + Transparent, + } eDrawStyle = Default; + + if (bConsiderBackgroundTransparency && + ( ( aColor.IsTransparent()) || + bTransparentGrfWithNoFillBackgrd ) ) + { + eDrawStyle = Transparent; + } + + // #i75614# reset draw mode in high contrast mode in order to get fill color set + const DrawModeFlags nOldDrawMode = rOutDev.GetDrawMode(); + if ( gProp.pSGlobalShell->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + rOutDev.SetDrawMode( DrawModeFlags::Default ); + } + + // If background region has to be drawn transparent, set only the RGB values of the background color as + // the fill color for the output device. + switch (eDrawStyle) + { + case Transparent: + { + if( rOutDev.GetFillColor() != aColor.GetRGBColor() ) + rOutDev.SetFillColor( aColor.GetRGBColor() ); + break; + } + default: + { + if( rOutDev.GetFillColor() != aColor ) + rOutDev.SetFillColor( aColor ); + break; + } + } + + // #i75614# + // restore draw mode + rOutDev.SetDrawMode( nOldDrawMode ); + + switch (eDrawStyle) + { + case Transparent: + { + // background region have to be drawn transparent. + // Thus, create a poly-polygon from the region and draw it with + // the corresponding transparency percent. + tools::PolyPolygon aDrawPoly( rOut.SVRect() ); + if ( aGrf.HasArea() ) + { + if ( !bGrfIsTransparent ) + { + // subtract area of background graphic from draw area + // Consider only that part of the graphic area that is overlapping with draw area. + SwRect aTmpGrf = aGrf; + aTmpGrf.Intersection( rOut ); + if ( aTmpGrf.HasArea() ) + { + tools::Polygon aGrfPoly( aTmpGrf.SVRect() ); + aDrawPoly.Insert( aGrfPoly ); + } + } + else + bGrfBackgrdAlreadyDrawn = true; + } + // calculate transparency percent: + // ( <transparency value[0x01..0xFF]>*100 + 0x7F ) / 0xFF + // If there is a background graphic with a background color "no fill"/"auto fill", + // the transparency value is taken from the background graphic, + // otherwise take the transparency value from the color. + sal_Int8 nTransparencyPercent = static_cast<sal_Int8>( + (( bTransparentGrfWithNoFillBackgrd ? nGrfTransparency : (255 - aColor.GetAlpha()) + )*100 + 0x7F)/0xFF); + // draw poly-polygon transparent + rOutDev.DrawTransparent( aDrawPoly, nTransparencyPercent ); + + break; + } + case Default: + default: + { + SwRegionRects aRegion( rOut, 4 ); + if ( !bGrfIsTransparent ) + aRegion -= aGrf; + else + bGrfBackgrdAlreadyDrawn = true; + // loop rectangles of background region, which has to be drawn + for( size_t i = 0; i < aRegion.size(); ++i ) + { + rOutDev.DrawRect( aRegion[i].SVRect() ); + } + } + } + rOutDev.Pop(); + } + + if( bDraw && aGrf.Overlaps( rOut ) ) + lcl_DrawGraphic( *pBrush, rOutDev, rSh, aGrf, rOut, bGrfNum, gProp, + bGrfBackgrdAlreadyDrawn ); + + if( bReplaceGrfNum ) + { + const BitmapEx& rBmp = rSh.GetReplacementBitmap(false); + vcl::Font aTmp( rOutDev.GetFont() ); + Graphic::DrawEx(rOutDev, OUString(), aTmp, rBmp, rOrg.Pos(), rOrg.SSize()); + } +} + +/** + * Local helper for SwRootFrame::PaintSwFrame(..) - Adjust given rectangle to pixel size + * + * By OD at 27.09.2002 for #103636# + * In order to avoid paint errors caused by multiple alignments (e.g. ::SwAlignRect(..)) + * and other changes to the to be painted rectangle, this method is called for the + * rectangle to be painted in order to adjust it to the pixel it is overlapping +*/ +static void lcl_AdjustRectToPixelSize( SwRect& io_aSwRect, const vcl::RenderContext &aOut ) +{ + // local constant object of class <Size> to determine number of Twips + // representing a pixel. + const Size aTwipToPxSize( aOut.PixelToLogic( Size( 1,1 )) ); + + // local object of class <Rectangle> in Twip coordinates + // calculated from given rectangle aligned to pixel centers. + const tools::Rectangle aPxCenterRect = aOut.PixelToLogic( + aOut.LogicToPixel( io_aSwRect.SVRect() ) ); + + // local constant object of class <Rectangle> representing given rectangle + // in pixel. + const tools::Rectangle aOrgPxRect = aOut.LogicToPixel( io_aSwRect.SVRect() ); + + // calculate adjusted rectangle from pixel centered rectangle. + // Due to rounding differences <aPxCenterRect> doesn't exactly represents + // the Twip-centers. Thus, adjust borders by half of pixel width/height plus 1. + // Afterwards, adjust calculated Twip-positions of the all borders. + tools::Rectangle aSizedRect = aPxCenterRect; + aSizedRect.AdjustLeft( -(aTwipToPxSize.Width()/2 + 1) ); + aSizedRect.AdjustRight( aTwipToPxSize.Width()/2 + 1 ); + aSizedRect.AdjustTop( -(aTwipToPxSize.Height()/2 + 1) ); + aSizedRect.AdjustBottom(aTwipToPxSize.Height()/2 + 1); + + // adjust left() + while ( aOut.LogicToPixel(aSizedRect).Left() < aOrgPxRect.Left() ) + { + aSizedRect.AdjustLeft( 1 ); + } + // adjust right() + while ( aOut.LogicToPixel(aSizedRect).Right() > aOrgPxRect.Right() ) + { + aSizedRect.AdjustRight( -1 ); + } + // adjust top() + while ( aOut.LogicToPixel(aSizedRect).Top() < aOrgPxRect.Top() ) + { + aSizedRect.AdjustTop( 1 ); + } + // adjust bottom() + while ( aOut.LogicToPixel(aSizedRect).Bottom() > aOrgPxRect.Bottom() ) + { + aSizedRect.AdjustBottom( -1 ); + } + + io_aSwRect = SwRect( aSizedRect ); + +#if OSL_DEBUG_LEVEL > 0 + tools::Rectangle aTestOrgPxRect = aOut.LogicToPixel( io_aSwRect.SVRect() ); + tools::Rectangle aTestNewPxRect = aOut.LogicToPixel( aSizedRect ); + OSL_ENSURE( aTestOrgPxRect == aTestNewPxRect, + "Error in lcl_AlignRectToPixelSize(..): Adjusted rectangle has incorrect position or size"); + // check Left() + aSizedRect.AdjustLeft( -1 ); + aTestNewPxRect = aOut.LogicToPixel( aSizedRect ); + OSL_ENSURE( aTestOrgPxRect.Left() >= (aTestNewPxRect.Left()+1), + "Error in lcl_AlignRectToPixelSize(..): Left() not correct adjusted"); + aSizedRect.AdjustLeft( 1 ); + // check Right() + aSizedRect.AdjustRight( 1 ); + aTestNewPxRect = aOut.LogicToPixel( aSizedRect ); + OSL_ENSURE( aTestOrgPxRect.Right() <= (aTestNewPxRect.Right()-1), + "Error in lcl_AlignRectToPixelSize(..): Right() not correct adjusted"); + aSizedRect.AdjustRight( -1 ); + // check Top() + aSizedRect.AdjustTop( -1 ); + aTestNewPxRect = aOut.LogicToPixel( aSizedRect ); + OSL_ENSURE( aTestOrgPxRect.Top() >= (aTestNewPxRect.Top()+1), + "Error in lcl_AlignRectToPixelSize(..): Top() not correct adjusted"); + aSizedRect.AdjustTop( 1 ); + // check Bottom() + aSizedRect.AdjustBottom( 1 ); + aTestNewPxRect = aOut.LogicToPixel( aSizedRect ); + OSL_ENSURE( aTestOrgPxRect.Bottom() <= (aTestNewPxRect.Bottom()-1), + "Error in lcl_AlignRectToPixelSize(..): Bottom() not correct adjusted"); + aSizedRect.AdjustBottom( -1 ); +#endif +} + +// FUNCTIONS USED FOR COLLAPSING TABLE BORDER LINES START + +namespace { + +struct SwLineEntry +{ + SwTwips mnKey; + SwTwips mnStartPos; + SwTwips mnEndPos; + SwTwips mnLimitedEndPos; + bool mbOuter; + + svx::frame::Style maAttribute; + + enum OverlapType { NO_OVERLAP, OVERLAP1, OVERLAP2, OVERLAP3 }; + + enum class VerticalType { LEFT, RIGHT }; + +public: + SwLineEntry( SwTwips nKey, + SwTwips nStartPos, + SwTwips nEndPos, + bool bOuter, + const svx::frame::Style& rAttribute ); + + OverlapType Overlaps( const SwLineEntry& rComp ) const; + + /** + * Assuming that this entry is for a Word-style covering cell and the border matching eType is + * set, limit the end position of this border in case covered cells have no borders set. + */ + void LimitVerticalEndPos(const SwFrame& rFrame, VerticalType eType); +}; + +} + +SwLineEntry::SwLineEntry( SwTwips nKey, + SwTwips nStartPos, + SwTwips nEndPos, + bool bOuter, + const svx::frame::Style& rAttribute ) + : mnKey( nKey ), + mnStartPos( nStartPos ), + mnEndPos( nEndPos ), + mnLimitedEndPos(0), + mbOuter(bOuter), + maAttribute( rAttribute ) +{ +} + +/* + + 1. ---------- rOld + ---------- rNew + + 2. ---------- rOld + ------------- rNew + + 3. ------- rOld + ------------- rNew + + 4. ------------- rOld + ---------- rNew + + 5. ---------- rOld + ---- rNew + + 6. ---------- rOld + ---------- rNew + + 7. ------------- rOld + ---------- rNew + + 8. ---------- rOld + ------------- rNew + + 9. ---------- rOld + ---------- rNew +*/ + +SwLineEntry::OverlapType SwLineEntry::Overlaps( const SwLineEntry& rNew ) const +{ + SwLineEntry::OverlapType eRet = OVERLAP3; + + if ( mnStartPos >= rNew.mnEndPos || mnEndPos <= rNew.mnStartPos ) + eRet = NO_OVERLAP; + + // 1, 2, 3 + else if ( mnEndPos < rNew.mnEndPos ) + eRet = OVERLAP1; + + // 4, 5, 6, 7 + else if (mnStartPos <= rNew.mnStartPos) + eRet = OVERLAP2; + + // 8, 9 + return eRet; +} + +void SwLineEntry::LimitVerticalEndPos(const SwFrame& rFrame, VerticalType eType) +{ + if (!rFrame.IsCellFrame()) + { + return; + } + + const auto& rCellFrame = static_cast<const SwCellFrame&>(rFrame); + std::vector<const SwCellFrame*> aCoveredCells = rCellFrame.GetCoveredCells(); + // Iterate in reverse order, so we can stop at the first cell that has a border. This can + // determine what is the minimal end position that is safe to use as a limit. + for (auto it = aCoveredCells.rbegin(); it != aCoveredCells.rend(); ++it) + { + const SwCellFrame* pCoveredCell = *it; + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pCoveredCell ); + const SwBorderAttrs& rAttrs = *aAccess.Get(); + const SvxBoxItem& rBox = rAttrs.GetBox(); + if (eType == VerticalType::LEFT && rBox.GetLeft()) + { + break; + } + + if (eType == VerticalType::RIGHT && rBox.GetRight()) + { + break; + } + + mnLimitedEndPos = pCoveredCell->getFrameArea().Top(); + } +} + +namespace { + +struct lt_SwLineEntry +{ + bool operator()( const SwLineEntry& e1, const SwLineEntry& e2 ) const + { + return e1.mnStartPos < e2.mnStartPos; + } +}; + +} + +typedef std::set< SwLineEntry, lt_SwLineEntry > SwLineEntrySet; +typedef std::map< SwTwips, SwLineEntrySet > SwLineEntryMap; + +namespace { + +class SwTabFramePainter +{ + SwLineEntryMap maVertLines; + SwLineEntryMap maHoriLines; + const SwTabFrame& mrTabFrame; + + void Insert( SwLineEntry&, bool bHori ); + void Insert(const SwFrame& rFrame, const SvxBoxItem& rBoxItem, const SwRect &rPaintArea); + void HandleFrame(const SwLayoutFrame& rFrame, const SwRect& rPaintArea); + void FindStylesForLine( Point&, + Point&, + svx::frame::Style*, + bool bHori, + bool bOuter ) const; + +public: + explicit SwTabFramePainter( const SwTabFrame& rTabFrame ); + + void PaintLines( OutputDevice& rDev, const SwRect& rRect ) const; +}; + +} + +SwTabFramePainter::SwTabFramePainter( const SwTabFrame& rTabFrame ) + : mrTabFrame( rTabFrame ) +{ + SwRect aPaintArea = rTabFrame.GetUpper()->GetPaintArea(); + HandleFrame(rTabFrame, aPaintArea); +} + +void SwTabFramePainter::HandleFrame(const SwLayoutFrame& rLayoutFrame, const SwRect& rPaintArea) +{ + // Add border lines of cell frames. Skip covered cells. Skip cells + // in special row span row, which do not have a negative row span: + if ( rLayoutFrame.IsCellFrame() && !rLayoutFrame.IsCoveredCell() ) + { + const SwCellFrame* pThisCell = static_cast<const SwCellFrame*>(&rLayoutFrame); + const SwRowFrame* pRowFrame = static_cast<const SwRowFrame*>(pThisCell->GetUpper()); + const tools::Long nRowSpan = pThisCell->GetTabBox()->getRowSpan(); + if ( !pRowFrame->IsRowSpanLine() || nRowSpan > 1 || nRowSpan < -1 ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), &rLayoutFrame ); + const SwBorderAttrs& rAttrs = *aAccess.Get(); + const SvxBoxItem& rBox = rAttrs.GetBox(); + Insert(rLayoutFrame, rBox, rPaintArea); + } + } + + // Recurse into lower layout frames, but do not recurse into lower tabframes. + const SwFrame* pLower = rLayoutFrame.Lower(); + while ( pLower ) + { + if (pLower->IsLayoutFrame() && !pLower->IsTabFrame()) + { + const SwLayoutFrame* pLowerLayFrame = static_cast<const SwLayoutFrame*>(pLower); + HandleFrame(*pLowerLayFrame, rPaintArea); + } + pLower = pLower->GetNext(); + } +} + +void SwTabFramePainter::PaintLines(OutputDevice& rDev, const SwRect& rRect) const +{ + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, rDev ); + + SwLineEntryMap::const_iterator aIter = maHoriLines.begin(); + bool bHori = true; + + // color for subsidiary lines: + const Color& rCol( SwViewOption::GetTableBoundariesColor() ); + + // high contrast mode: + // overrides the color of non-subsidiary lines. + const Color* pHCColor = nullptr; + DrawModeFlags nOldDrawMode = rDev.GetDrawMode(); + if( gProp.pSGlobalShell->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + pHCColor = &SwViewOption::GetFontColor(); + rDev.SetDrawMode( DrawModeFlags::Default ); + } + + const SwFrame* pUpper = mrTabFrame.GetUpper(); + SwRect aUpper( pUpper->getFramePrintArea() ); + aUpper.Pos() += pUpper->getFrameArea().Pos(); + SwRect aUpperAligned( aUpper ); + ::SwAlignRect( aUpperAligned, gProp.pSGlobalShell, &rDev ); + + // prepare SdrFrameBorderDataVector + std::shared_ptr<drawinglayer::primitive2d::SdrFrameBorderDataVector> aData( + std::make_shared<drawinglayer::primitive2d::SdrFrameBorderDataVector>()); + + while ( true ) + { + if ( bHori && aIter == maHoriLines.end() ) + { + aIter = maVertLines.begin(); + bHori = false; + } + + if ( !bHori && aIter == maVertLines.end() ) + break; + + const SwLineEntrySet& rEntrySet = (*aIter).second; + for (const SwLineEntry& rEntry : rEntrySet) + { + const svx::frame::Style& rEntryStyle( rEntry.maAttribute ); + + Point aStart, aEnd; + if ( bHori ) + { + aStart.setX( rEntry.mnStartPos ); + aStart.setY( rEntry.mnKey ); + aEnd.setX( rEntry.mnEndPos ); + aEnd.setY( rEntry.mnKey ); + } + else + { + aStart.setX( rEntry.mnKey ); + aStart.setY( rEntry.mnStartPos ); + aEnd.setX( rEntry.mnKey ); + aEnd.setY( rEntry.mnEndPos ); + } + + svx::frame::Style aStyles[ 7 ]; + aStyles[ 0 ] = rEntryStyle; + FindStylesForLine(aStart, aEnd, aStyles, bHori, rEntry.mbOuter); + + if (!bHori && rEntry.mnLimitedEndPos) + { + aEnd.setY(rEntry.mnLimitedEndPos); + } + + SwRect aRepaintRect( aStart, aEnd ); + + // the repaint rectangle has to be moved a bit for the centered lines: + SwTwips nRepaintRectSize = !rEntryStyle.GetWidth() ? 1 : rEntryStyle.GetWidth(); + if ( bHori ) + { + aRepaintRect.Height( 2 * nRepaintRectSize ); + aRepaintRect.Pos().AdjustY( -nRepaintRectSize ); + + // To decide on visibility it is also necessary to expand the RepaintRect + // to left/right according existing BorderLine overlap matchings, else there + // will be repaint errors when scrolling in e.t TripleLine BorderLines. + // aStyles[1] == aLFromT, aStyles[3] == aLFromB, aStyles[4] == aRFromT, aStyles[6] == aRFromB + if(aStyles[1].IsUsed() || aStyles[3].IsUsed() || aStyles[4].IsUsed() || aStyles[6].IsUsed()) + { + const double fLineWidthMaxLeft(std::max(aStyles[1].GetWidth(), aStyles[3].GetWidth())); + const double fLineWidthMaxRight(std::max(aStyles[4].GetWidth(), aStyles[6].GetWidth())); + aRepaintRect.Width(aRepaintRect.Width() + (fLineWidthMaxLeft + fLineWidthMaxRight)); + aRepaintRect.Pos().AdjustX( -fLineWidthMaxLeft ); + } + } + else + { + aRepaintRect.Width( 2 * nRepaintRectSize ); + aRepaintRect.Pos().AdjustX( -nRepaintRectSize ); + + // Accordingly to horizontal case, but for top/bottom + // aStyles[3] == aTFromR, aStyles[1] == aTFromL, aStyles[6] == aBFromR, aStyles[4] == aBFromL + if(aStyles[3].IsUsed() || aStyles[1].IsUsed() || aStyles[6].IsUsed() || aStyles[4].IsUsed()) + { + const double fLineWidthMaxTop(std::max(aStyles[3].GetWidth(), aStyles[1].GetWidth())); + const double fLineWidthMaxBottom(std::max(aStyles[6].GetWidth(), aStyles[4].GetWidth())); + aRepaintRect.Height(aRepaintRect.Height() + (fLineWidthMaxTop + fLineWidthMaxBottom)); + aRepaintRect.Pos().AdjustY( -fLineWidthMaxTop ); + } + } + + if (!rRect.Overlaps(aRepaintRect)) + { + continue; + } + + // subsidiary lines + const Color* pTmpColor = nullptr; + if (0 == aStyles[ 0 ].GetWidth()) + { + if (isTableBoundariesEnabled() && gProp.pSGlobalShell->GetWin()) + aStyles[ 0 ].Set( rCol, rCol, rCol, false, 1, 0, 0 ); + else + aStyles[0].SetType(SvxBorderLineStyle::NONE); + } + else + pTmpColor = pHCColor; + + // The (twip) positions will be adjusted to meet these requirements: + // 1. The y coordinates are located in the middle of the pixel grid + // 2. The x coordinated are located at the beginning of the pixel grid + // This is done, because the horizontal lines are painted "at + // beginning", whereas the vertical lines are painted "centered". + // By making the line sizes a multiple of one pixel size, we can + // assure that all lines having the same twip size have the same + // pixel size, independent of their position on the screen. + Point aPaintStart = rDev.PixelToLogic( rDev.LogicToPixel(aStart) ); + Point aPaintEnd = rDev.PixelToLogic( rDev.LogicToPixel(aEnd) ); + + if (gProp.pSGlobalShell->GetWin()) + { + // The table borders do not use SwAlignRect, but all the other frames do. + // Therefore we tweak the outer borders a bit to achieve that the outer + // borders match the subsidiary lines of the upper: + if (aStart.X() == aUpper.Left()) + aPaintStart.setX( aUpperAligned.Left() ); + else if (aStart.X() == aUpper.Right_()) + aPaintStart.setX( aUpperAligned.Right_() ); + if (aStart.Y() == aUpper.Top()) + aPaintStart.setY( aUpperAligned.Top() ); + else if (aStart.Y() == aUpper.Bottom_()) + aPaintStart.setY( aUpperAligned.Bottom_() ); + + if (aEnd.X() == aUpper.Left()) + aPaintEnd.setX( aUpperAligned.Left() ); + else if (aEnd.X() == aUpper.Right_()) + aPaintEnd.setX( aUpperAligned.Right_() ); + if (aEnd.Y() == aUpper.Top()) + aPaintEnd.setY( aUpperAligned.Top() ); + else if (aEnd.Y() == aUpper.Bottom_()) + aPaintEnd.setY( aUpperAligned.Bottom_() ); + } + + if(aStyles[0].IsUsed()) + { + if (bHori) + { + const basegfx::B2DPoint aOrigin(aPaintStart.X(), aPaintStart.Y()); + const basegfx::B2DVector aX(basegfx::B2DPoint(aPaintEnd.X(), aPaintEnd.Y()) - aOrigin); + + if(!aX.equalZero()) + { + const basegfx::B2DVector aY(basegfx::getNormalizedPerpendicular(aX)); + aData->emplace_back( + aOrigin, + aX, + aStyles[0], + pTmpColor); + drawinglayer::primitive2d::SdrFrameBorderData& rInstance(aData->back()); + + rInstance.addSdrConnectStyleData(true, aStyles[1], -aY, true); // aLFromT + rInstance.addSdrConnectStyleData(true, aStyles[2], -aX, true); // aLFromL + rInstance.addSdrConnectStyleData(true, aStyles[3], aY, false); // aLFromB + + rInstance.addSdrConnectStyleData(false, aStyles[4], -aY, true); // aRFromT + rInstance.addSdrConnectStyleData(false, aStyles[5], aX, false); // aRFromR + rInstance.addSdrConnectStyleData(false, aStyles[6], aY, false); // aRFromB + } + } + else // vertical + { + const basegfx::B2DPoint aOrigin(aPaintStart.X(), aPaintStart.Y()); + const basegfx::B2DVector aX(basegfx::B2DPoint(aPaintEnd.X(), aPaintEnd.Y()) - aOrigin); + + if(!aX.equalZero()) + { + const basegfx::B2DVector aY(basegfx::getNormalizedPerpendicular(aX)); + aData->emplace_back( + aOrigin, + aX, + aStyles[0], + pTmpColor); + drawinglayer::primitive2d::SdrFrameBorderData& rInstance(aData->back()); + + rInstance.addSdrConnectStyleData(true, aStyles[3], -aY, false); // aTFromR + rInstance.addSdrConnectStyleData(true, aStyles[2], -aX, true); // aTFromT + rInstance.addSdrConnectStyleData(true, aStyles[1], aY, true); // aTFromL + + rInstance.addSdrConnectStyleData(false, aStyles[6], -aY, false); // aBFromR + rInstance.addSdrConnectStyleData(false, aStyles[5], aX, false); // aBFromB + rInstance.addSdrConnectStyleData(false, aStyles[4], aY, true); // aBFromL + } + } + } + } + ++aIter; + } + + // create instance of SdrFrameBorderPrimitive2D if + // SdrFrameBorderDataVector is used + if(!aData->empty()) + { + drawinglayer::primitive2d::Primitive2DContainer aSequence; + aSequence.append( + drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::SdrFrameBorderPrimitive2D( + aData, + true))); // force visualization to minimal one discrete unit (pixel) + // paint + mrTabFrame.ProcessPrimitives(aSequence); + } + + // restore output device: + rDev.SetDrawMode( nOldDrawMode ); +} + +/** + * Finds the lines that join the line defined by (StartPoint, EndPoint) in either + * StartPoint or Endpoint. The styles of these lines are required for DR's magic + * line painting functions + */ +void SwTabFramePainter::FindStylesForLine( Point& rStartPoint, + Point& rEndPoint, + svx::frame::Style* pStyles, + bool bHori, bool bOuter ) const +{ + // For example, aLFromB means: this vertical line intersects my horizontal line at its left end, + // from bottom. + // pStyles[ 1 ] = bHori ? aLFromT : TFromL + // pStyles[ 2 ] = bHori ? aLFromL : TFromT, + // pStyles[ 3 ] = bHori ? aLFromB : TFromR, + // pStyles[ 4 ] = bHori ? aRFromT : BFromL, + // pStyles[ 5 ] = bHori ? aRFromR : BFromB, + // pStyles[ 6 ] = bHori ? aRFromB : BFromR, + + bool bWordTableCell = false; + SwViewShell* pShell = mrTabFrame.getRootFrame()->GetCurrShell(); + if (pShell) + { + const IDocumentSettingAccess& rIDSA = pShell->GetDoc()->getIDocumentSettingAccess(); + bWordTableCell = rIDSA.get(DocumentSettingId::TABLE_ROW_KEEP); + } + + SwLineEntryMap::const_iterator aMapIter = maVertLines.find( rStartPoint.X() ); + OSL_ENSURE( aMapIter != maVertLines.end(), "FindStylesForLine: Error" ); + const SwLineEntrySet& rVertSet = (*aMapIter).second; + + for ( const SwLineEntry& rEntry : rVertSet ) + { + if ( bHori ) + { + if ( rStartPoint.Y() == rEntry.mnStartPos ) + pStyles[ 3 ] = rEntry.maAttribute; + else if ( rStartPoint.Y() == rEntry.mnEndPos ) + pStyles[ 1 ] = rEntry.maAttribute; + + if (bWordTableCell && rStartPoint.X() == rEntry.mnKey && !bOuter && rEntry.mbOuter) + { + rStartPoint.AdjustX(rEntry.maAttribute.GetWidth()); + } + } + else + { + if ( rStartPoint.Y() == rEntry.mnEndPos ) + pStyles[ 2 ] = rEntry.maAttribute; + else if ( rEndPoint.Y() == rEntry.mnStartPos ) + pStyles[ 5 ] = rEntry.maAttribute; + } + } + + aMapIter = maHoriLines.find( rStartPoint.Y() ); + OSL_ENSURE( aMapIter != maHoriLines.end(), "FindStylesForLine: Error" ); + const SwLineEntrySet& rHoriSet = (*aMapIter).second; + + for ( const SwLineEntry& rEntry : rHoriSet ) + { + if ( bHori ) + { + if ( rStartPoint.X() == rEntry.mnEndPos ) + pStyles[ 2 ] = rEntry.maAttribute; + else if ( rEndPoint.X() == rEntry.mnStartPos ) + pStyles[ 5 ] = rEntry.maAttribute; + } + else + { + if ( rStartPoint.X() == rEntry.mnEndPos ) + pStyles[ 1 ] = rEntry.maAttribute; + else if ( rStartPoint.X() == rEntry.mnStartPos ) + pStyles[ 3 ] = rEntry.maAttribute; + + if (bWordTableCell && rStartPoint.Y() == rEntry.mnKey && !bOuter && rEntry.mbOuter) + { + rStartPoint.AdjustY(rEntry.maAttribute.GetWidth()); + } + } + } + + if ( bHori ) + { + aMapIter = maVertLines.find( rEndPoint.X() ); + OSL_ENSURE( aMapIter != maVertLines.end(), "FindStylesForLine: Error" ); + const SwLineEntrySet& rVertSet2 = (*aMapIter).second; + + for ( const SwLineEntry& rEntry : rVertSet2 ) + { + if ( rEndPoint.Y() == rEntry.mnStartPos ) + pStyles[ 6 ] = rEntry.maAttribute; + else if ( rEndPoint.Y() == rEntry.mnEndPos ) + pStyles[ 4 ] = rEntry.maAttribute; + + if (bWordTableCell && rEndPoint.X() == rEntry.mnKey && !bOuter && rEntry.mbOuter) + { + rEndPoint.AdjustX(-rEntry.maAttribute.GetWidth()); + } + } + } + else + { + aMapIter = maHoriLines.find( rEndPoint.Y() ); + OSL_ENSURE( aMapIter != maHoriLines.end(), "FindStylesForLine: Error" ); + const SwLineEntrySet& rHoriSet2 = (*aMapIter).second; + + for ( const SwLineEntry& rEntry : rHoriSet2 ) + { + if ( rEndPoint.X() == rEntry.mnEndPos ) + pStyles[ 4 ] = rEntry.maAttribute; + else if ( rEndPoint.X() == rEntry.mnStartPos ) + pStyles[ 6 ] = rEntry.maAttribute; + + if (bWordTableCell && rEndPoint.Y() == rEntry.mnKey && !bOuter && rEntry.mbOuter) + { + rEndPoint.AdjustY(-rEntry.maAttribute.GetWidth()); + } + } + } +} + +/** + * Special case: #i9860# + * first line in follow table without repeated headlines + */ +static bool lcl_IsFirstRowInFollowTableWithoutRepeatedHeadlines( + SwTabFrame const& rTabFrame, SwFrame const& rFrame, SvxBoxItem const& rBoxItem) +{ + SwRowFrame const*const pThisRowFrame = + dynamic_cast<const SwRowFrame*>(rFrame.GetUpper()); + return (pThisRowFrame + && (pThisRowFrame->GetUpper() == &rTabFrame) + && rTabFrame.IsFollow() + && !rTabFrame.GetTable()->GetRowsToRepeat() + && ( !pThisRowFrame->GetPrev() + || static_cast<const SwRowFrame*>(pThisRowFrame->GetPrev()) + ->IsRowSpanLine()) + && !rBoxItem.GetTop() + && rBoxItem.GetBottom()); +} + +void SwTabFramePainter::Insert(const SwFrame& rFrame, const SvxBoxItem& rBoxItem, const SwRect& rPaintArea) +{ + // build 4 line entries for the 4 borders: + SwRect aBorderRect = rFrame.getFrameArea(); + + aBorderRect.Intersection(rPaintArea); + + bool const bBottomAsTop(lcl_IsFirstRowInFollowTableWithoutRepeatedHeadlines( + mrTabFrame, rFrame, rBoxItem)); + bool const bVert = mrTabFrame.IsVertical(); + bool const bR2L = mrTabFrame.IsRightToLeft(); + + bool bWordTableCell = false; + SwViewShell* pShell = rFrame.getRootFrame()->GetCurrShell(); + if (pShell) + { + const IDocumentSettingAccess& rIDSA = pShell->GetDoc()->getIDocumentSettingAccess(); + bWordTableCell = rIDSA.get(DocumentSettingId::TABLE_ROW_KEEP); + } + + // no scaling needed, it's all in the primitives and the target device + svx::frame::Style aL(rBoxItem.GetLeft(), 1.0); + aL.SetWordTableCell(bWordTableCell); + svx::frame::Style aR(rBoxItem.GetRight(), 1.0); + aR.SetWordTableCell(bWordTableCell); + svx::frame::Style aT(rBoxItem.GetTop(), 1.0); + aT.SetWordTableCell(bWordTableCell); + svx::frame::Style aB(rBoxItem.GetBottom(), 1.0); + aB.SetWordTableCell(bWordTableCell); + + // First cell in a row. + bool bLeftIsOuter = rFrame.IsCellFrame() && rFrame.GetUpper()->GetLower() == &rFrame; + // Last cell in a row. + bool bRightIsOuter = rFrame.IsCellFrame() && rFrame.GetNext() == nullptr; + // First row in a table. + bool bTopIsOuter = rFrame.IsCellFrame() && rFrame.GetUpper()->GetUpper()->GetLower() == rFrame.GetUpper(); + // Last row in a table. + bool bBottomIsOuter = rFrame.IsCellFrame() && rFrame.GetUpper()->GetNext() == nullptr; + + aR.MirrorSelf(); + if (!bWordTableCell || !bBottomIsOuter) + { + // Outer horizontal lines are never mirrored in Word. + aB.MirrorSelf(); + } + + const SwTwips nLeft = aBorderRect.Left_(); + const SwTwips nRight = aBorderRect.Right_(); + const SwTwips nTop = aBorderRect.Top_(); + const SwTwips nBottom = aBorderRect.Bottom_(); + + aL.SetRefMode( svx::frame::RefMode::Centered ); + aR.SetRefMode( svx::frame::RefMode::Centered ); + aT.SetRefMode( !bVert ? svx::frame::RefMode::Begin : svx::frame::RefMode::End ); + aB.SetRefMode( !bVert ? svx::frame::RefMode::Begin : svx::frame::RefMode::End ); + + if (bWordTableCell && bLeftIsOuter) + { + // Outer vertical lines are always mirrored in Word. + aL.MirrorSelf(); + } + + SwLineEntry aLeft (nLeft, nTop, nBottom, bLeftIsOuter, + bVert ? aB : (bR2L ? aR : aL)); + if (bWordTableCell && rBoxItem.GetLeft()) + { + aLeft.LimitVerticalEndPos(rFrame, SwLineEntry::VerticalType::LEFT); + } + + SwLineEntry aRight (nRight, nTop, nBottom, bRightIsOuter, + bVert ? (bBottomAsTop ? aB : aT) : (bR2L ? aL : aR)); + if (bWordTableCell && rBoxItem.GetRight()) + { + aRight.LimitVerticalEndPos(rFrame, SwLineEntry::VerticalType::RIGHT); + } + + SwLineEntry aTop (nTop, nLeft, nRight, bTopIsOuter, + bVert ? aL : (bBottomAsTop ? aB : aT)); + + SwLineEntry aBottom(nBottom, nLeft, nRight, bBottomIsOuter, + bVert ? aR : aB); + + Insert( aLeft, false ); + Insert( aRight, false ); + Insert( aTop, true ); + Insert( aBottom, true ); +} + +void SwTabFramePainter::Insert( SwLineEntry& rNew, bool bHori ) +{ + // get all lines from structure, that have key entry of pLE + SwLineEntryMap* pLine2 = bHori ? &maHoriLines : &maVertLines; + const SwTwips nKey = rNew.mnKey; + SwLineEntryMap::iterator aMapIter = pLine2->find( nKey ); + + SwLineEntrySet* pLineSet = aMapIter != pLine2->end() ? &((*aMapIter).second) : nullptr; + if ( !pLineSet ) + { + SwLineEntrySet aNewSet; + (*pLine2)[ nKey ] = aNewSet; + pLineSet = &(*pLine2)[ nKey ]; + } + SwLineEntrySet::iterator aIter = pLineSet->begin(); + + while ( aIter != pLineSet->end() && rNew.mnStartPos < rNew.mnEndPos ) + { + const SwLineEntry& rOld = *aIter; + + if (rOld.mnLimitedEndPos || rOld.mbOuter != rNew.mbOuter) + { + // Don't merge with this line entry as it ends sooner than mnEndPos. + ++aIter; + continue; + } + + const SwLineEntry::OverlapType nOverlapType = rOld.Overlaps( rNew ); + + const svx::frame::Style& rOldAttr = rOld.maAttribute; + const svx::frame::Style& rNewAttr = rNew.maAttribute; + const svx::frame::Style& rCmpAttr = std::max(rNewAttr, rOldAttr); + + if ( SwLineEntry::OVERLAP1 == nOverlapType ) + { + OSL_ENSURE( rNew.mnStartPos >= rOld.mnStartPos, "Overlap type 3? How this?" ); + + // new left segment + const SwLineEntry aLeft(nKey, rOld.mnStartPos, rNew.mnStartPos, rOld.mbOuter, rOldAttr); + + // new middle segment + const SwLineEntry aMiddle(nKey, rNew.mnStartPos, rOld.mnEndPos, rOld.mbOuter, rCmpAttr); + + // new right segment + rNew.mnStartPos = rOld.mnEndPos; + + // update current lines set + pLineSet->erase( aIter ); + if ( aLeft.mnStartPos < aLeft.mnEndPos ) pLineSet->insert( aLeft ); + if ( aMiddle.mnStartPos < aMiddle.mnEndPos ) pLineSet->insert( aMiddle ); + + aIter = pLineSet->begin(); + + continue; // start over + } + else if ( SwLineEntry::OVERLAP2 == nOverlapType ) + { + // new left segment + const SwLineEntry aLeft(nKey, rOld.mnStartPos, rNew.mnStartPos, rOld.mbOuter, rOldAttr); + + // new middle segment + const SwLineEntry aMiddle(nKey, rNew.mnStartPos, rNew.mnEndPos, rOld.mbOuter, rCmpAttr); + + // new right segment + const SwLineEntry aRight(nKey, rNew.mnEndPos, rOld.mnEndPos, rOld.mbOuter, rOldAttr); + + // update current lines set + pLineSet->erase( aIter ); + if ( aLeft.mnStartPos < aLeft.mnEndPos ) pLineSet->insert( aLeft ); + if ( aMiddle.mnStartPos < aMiddle.mnEndPos ) pLineSet->insert( aMiddle ); + if ( aRight.mnStartPos < aRight.mnEndPos ) pLineSet->insert( aRight ); + + rNew.mnStartPos = rNew.mnEndPos; // rNew should not be inserted! + + break; // we are finished + } + else if ( SwLineEntry::OVERLAP3 == nOverlapType ) + { + // new left segment + const SwLineEntry aLeft(nKey, rNew.mnStartPos, rOld.mnStartPos, rOld.mbOuter, rNewAttr); + + // new middle segment + const SwLineEntry aMiddle(nKey, rOld.mnStartPos, rNew.mnEndPos, rOld.mbOuter, rCmpAttr); + + // new right segment + const SwLineEntry aRight(nKey, rNew.mnEndPos, rOld.mnEndPos, rOld.mbOuter, rOldAttr); + + // update current lines set + pLineSet->erase( aIter ); + if ( aLeft.mnStartPos < aLeft.mnEndPos ) pLineSet->insert( aLeft ); + if ( aMiddle.mnStartPos < aMiddle.mnEndPos ) pLineSet->insert( aMiddle ); + if ( aRight.mnStartPos < aRight.mnEndPos ) pLineSet->insert( aRight ); + + rNew.mnStartPos = rNew.mnEndPos; // rNew should not be inserted! + + break; // we are finished + } + + ++aIter; + } + + if ( rNew.mnStartPos < rNew.mnEndPos ) // insert rest + pLineSet->insert( rNew ); +} + +/** + * FUNCTIONS USED FOR COLLAPSING TABLE BORDER LINES END + * --> OD #i76669# + */ +namespace +{ + class SwViewObjectContactRedirector : public sdr::contact::ViewObjectContactRedirector + { + private: + const SwViewShell& mrViewShell; + + public: + explicit SwViewObjectContactRedirector( const SwViewShell& rSh ) + : mrViewShell( rSh ) + {}; + + virtual void createRedirectedPrimitive2DSequence( + const sdr::contact::ViewObjectContact& rOriginal, + const sdr::contact::DisplayInfo& rDisplayInfo, + drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) override + { + bool bPaint( true ); + + SdrObject* pObj = rOriginal.GetViewContact().TryToGetSdrObject(); + if ( pObj ) + { + bPaint = SwFlyFrame::IsPaint( pObj, &mrViewShell ); + } + + if ( !bPaint ) + { + return; + } + + sdr::contact::ViewObjectContactRedirector::createRedirectedPrimitive2DSequence( + rOriginal, rDisplayInfo, rVisitor ); + } + }; + +} // end of anonymous namespace +// <-- + +/** + * Paint once for every visible page which is touched by Rect + * + * 1. Paint borders and backgrounds + * 2. Paint the draw layer (frames and drawing objects) that is + * below the document (hell) + * 3. Paint the document content (text) + * 4. Paint the draw layer that is above the document +|*/ +void SwRootFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const pPrintData) const +{ + OSL_ENSURE( Lower() && Lower()->IsPageFrame(), "Lower of root is no page." ); + + PROTOCOL( this, PROT::FileInit, DbgAction::NONE, nullptr) + + bool bResetRootPaint = false; + SwViewShell *pSh = mpCurrShell; + + if ( pSh->GetWin() ) + { + if ( pSh->GetOut() == pSh->GetWin()->GetOutDev() && !pSh->GetWin()->IsVisible() ) + { + return; + } + if (SwRootFrame::s_isInPaint) + { + SwPaintQueue::Add( pSh, rRect ); + return; + } + } + else + SwRootFrame::s_isInPaint = bResetRootPaint = true; + + std::unique_ptr<SwSavePaintStatics> pStatics; + if ( gProp.pSGlobalShell ) + pStatics.reset(new SwSavePaintStatics()); + gProp.pSGlobalShell = pSh; + + if( !pSh->GetWin() ) + gProp.pSProgress = SfxProgress::GetActiveProgress( static_cast<SfxObjectShell*>(pSh->GetDoc()->GetDocShell()) ); + + ::SwCalcPixStatics( pSh->GetOut() ); + aGlobalRetoucheColor = pSh->Imp()->GetRetoucheColor(); + + // Copy rRect; for one, rRect could become dangling during the below action, and for another it + // needs to be copied to aRect anyway as that is modified further down below: + SwRect aRect( rRect ); + + //Trigger an action to clear things up if needed. + //Using this trick we can ensure that all values are valid in all paints - + //no problems, no special case(s). + // #i92745# + // Extend check on certain states of the 'current' <SwViewShell> instance to + // all existing <SwViewShell> instances. + bool bPerformLayoutAction( true ); + { + for(SwViewShell& rTmpViewShell : pSh->GetRingContainer()) + { + if ( rTmpViewShell.IsInEndAction() || + rTmpViewShell.IsPaintInProgress() || + ( rTmpViewShell.Imp()->IsAction() && + rTmpViewShell.Imp()->GetLayAction().IsActionInProgress() ) ) + { + bPerformLayoutAction = false; + } + + if(!bPerformLayoutAction) + break; + } + } + if ( bPerformLayoutAction ) + { + const_cast<SwRootFrame*>(this)->ResetTurbo(); + SwLayAction aAction( const_cast<SwRootFrame*>(this), pSh->Imp() ); + aAction.SetPaint( false ); + aAction.SetComplete( false ); + aAction.SetReschedule( gProp.pSProgress != nullptr ); + aAction.Action(&rRenderContext); + ResetTurboFlag(); + if ( !pSh->ActionPend() ) + pSh->Imp()->DeletePaintRegion(); + } + + aRect.Intersection( pSh->VisArea() ); + + const bool bExtraData = ::IsExtraData( GetFormat()->GetDoc() ); + + gProp.pSLines.reset(new SwLineRects); // Container for borders. + + // #104289#. During painting, something (OLE) can + // load the linguistic, which in turn can cause a reformat + // of the document. Dangerous! We better set this flag to + // avoid the reformat. + const bool bOldAction = IsCallbackActionEnabled(); + const_cast<SwRootFrame*>(this)->SetCallbackActionEnabled( false ); + + const SwPageFrame *pPage = pSh->Imp()->GetFirstVisPage(&rRenderContext); + + // #126222. The positions of headers and footers of the previous + // pages have to be updated, else these headers and footers could + // get visible at a wrong position. + const SwPageFrame *pPageDeco = static_cast<const SwPageFrame*>(pPage->GetPrev()); + while (pPageDeco) + { + pPageDeco->PaintDecorators(); + OSL_ENSURE(!pPageDeco->GetPrev() || pPageDeco->GetPrev()->IsPageFrame(), + "Neighbour of page is not a page."); + pPageDeco = static_cast<const SwPageFrame*>(pPageDeco->GetPrev()); + } + + const bool bBookMode = gProp.pSGlobalShell->GetViewOptions()->IsViewLayoutBookMode(); + if ( bBookMode && pPage->GetPrev() && static_cast<const SwPageFrame*>(pPage->GetPrev())->IsEmptyPage() ) + pPage = static_cast<const SwPageFrame*>(pPage->GetPrev()); + + // #i68597# + const bool bGridPainting(pSh->GetWin() && pSh->Imp()->HasDrawView() && pSh->Imp()->GetDrawView()->IsGridVisible()); + + // Hide all page break controls before showing them again + SwWrtShell* pWrtSh = dynamic_cast< SwWrtShell* >( gProp.pSGlobalShell ); + if ( pWrtSh ) + { + SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin(); + SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager(); + const SwPageFrame* pHiddenPage = pPage; + while ( pHiddenPage->GetPrev() != nullptr ) + { + pHiddenPage = static_cast< const SwPageFrame* >( pHiddenPage->GetPrev() ); + SwFrameControlPtr pControl = rMngr.GetControl( FrameControlType::PageBreak, pHiddenPage ); + if ( pControl ) + pControl->ShowAll( false ); + } + } + + // #i76669# + SwViewObjectContactRedirector aSwRedirector( *pSh ); + + while ( pPage ) + { + const bool bPaintRightShadow = pPage->IsRightShadowNeeded(); + const bool bPaintLeftShadow = pPage->IsLeftShadowNeeded(); + const bool bRightSidebar = pPage->SidebarPosition() == sw::sidebarwindows::SidebarPosition::RIGHT; + + if ( !pPage->IsEmptyPage() ) + { + SwRect aPaintRect; + SwPageFrame::GetBorderAndShadowBoundRect( pPage->getFrameArea(), pSh, &rRenderContext, aPaintRect, + bPaintLeftShadow, bPaintRightShadow, bRightSidebar ); + + if ( aRect.Overlaps( aPaintRect ) ) + { + if ( pSh->GetWin() ) + { + gProp.pSSubsLines.reset(new SwSubsRects); + gProp.pSSpecSubsLines.reset(new SwSubsRects); + } + gProp.pBLines.reset(new BorderLines); + + aPaintRect.Intersection_( aRect ); + + if ( bExtraData && + pSh->GetWin() && pSh->IsInEndAction() ) + { + // enlarge paint rectangle to complete page width, subtract + // current paint area and invalidate the resulting region. + SwRectFnSet aRectFnSet(pPage); + SwRect aPageRectTemp( aPaintRect ); + aRectFnSet.SetLeftAndWidth( aPageRectTemp, + aRectFnSet.GetLeft(pPage->getFrameArea()), + aRectFnSet.GetWidth(pPage->getFrameArea()) ); + aPageRectTemp.Intersection_( pSh->VisArea() ); + vcl::Region aPageRectRegion( aPageRectTemp.SVRect() ); + aPageRectRegion.Exclude( aPaintRect.SVRect() ); + pSh->GetWin()->Invalidate( aPageRectRegion, InvalidateFlags::Children ); + } + + // #i80793# + // enlarge paint rectangle for objects overlapping the same pixel + // in all cases and before the DrawingLayer overlay is initialized. + lcl_AdjustRectToPixelSize( aPaintRect, *(pSh->GetOut()) ); + + // #i68597# + // moved paint pre-process for DrawingLayer overlay here since the above + // code dependent from bExtraData may expand the PaintRect + { + // #i75172# if called from SwViewShell::ImplEndAction it should no longer + // really be used but handled by SwViewShell::ImplEndAction already + const vcl::Region aDLRegion(aPaintRect.SVRect()); + pSh->DLPrePaint2(aDLRegion); + } + + if(OUTDEV_WINDOW == gProp.pSGlobalShell->GetOut()->GetOutDevType()) + { + // changed method SwLayVout::Enter(..) + // 2nd parameter is no longer <const> and will be set to the + // rectangle the virtual output device is calculated from <aPaintRect>, + // if the virtual output is used. + s_pVout->Enter(pSh, aPaintRect, !s_isNoVirDev); + + // Adjust paint rectangle to pixel size + // Thus, all objects overlapping on pixel level with the unadjusted + // paint rectangle will be considered in the paint. + lcl_AdjustRectToPixelSize( aPaintRect, *(pSh->GetOut()) ); + } + + // maybe this can be put in the above scope. Since we are not sure, just leave it ATM + s_pVout->SetOrgRect( aPaintRect ); + + // determine background color of page for <PaintLayer> method + // calls, paint <hell> or <heaven> + const Color aPageBackgrdColor(pPage->GetDrawBackgroundColor()); + + pPage->PaintBaBo( aPaintRect, pPage ); + + if ( pSh->Imp()->HasDrawView() ) + { + gProp.pSLines->LockLines( true ); + const IDocumentDrawModelAccess& rIDDMA = pSh->getIDocumentDrawModelAccess(); + pSh->Imp()->PaintLayer( rIDDMA.GetHellId(), + pPrintData, + *pPage, pPage->getFrameArea(), + &aPageBackgrdColor, + pPage->IsRightToLeft(), + &aSwRedirector ); + gProp.pSLines->PaintLines( pSh->GetOut(), gProp ); + gProp.pSLines->LockLines( false ); + } + + if ( pSh->GetDoc()->GetDocumentSettingManager().get( DocumentSettingId::BACKGROUND_PARA_OVER_DRAWINGS ) ) + pPage->PaintBaBo( aPaintRect, pPage, /*bOnlyTextBackground=*/true ); + + if( pSh->GetWin() ) + { + // collect sub-lines + pPage->RefreshSubsidiary( aPaintRect ); + // paint special sub-lines + gProp.pSSpecSubsLines->PaintSubsidiary( pSh->GetOut(), nullptr, gProp ); + } + + pPage->PaintSwFrame( rRenderContext, aPaintRect ); + + // no paint of page border and shadow, if writer is in place mode. + if( pSh->GetWin() && pSh->GetDoc()->GetDocShell() && + !pSh->GetDoc()->GetDocShell()->IsInPlaceActive() ) + { + SwPageFrame::PaintBorderAndShadow( pPage->getFrameArea(), pSh, bPaintLeftShadow, bPaintRightShadow, bRightSidebar ); + SwPageFrame::PaintNotesSidebar( pPage->getFrameArea(), pSh, pPage->GetPhyPageNum(), bRightSidebar); + } + + gProp.pSLines->PaintLines( pSh->GetOut(), gProp ); + if ( pSh->GetWin() ) + { + gProp.pSSubsLines->PaintSubsidiary( pSh->GetOut(), gProp.pSLines.get(), gProp ); + gProp.pSSubsLines.reset(); + gProp.pSSpecSubsLines.reset(); + } + // fdo#42750: delay painting these until after subsidiary lines + // fdo#45562: delay painting these until after hell layer + // fdo#47717: but do it before heaven layer + ProcessPrimitives(gProp.pBLines->GetBorderLines_Clear()); + + if ( pSh->Imp()->HasDrawView() ) + { + pSh->Imp()->PaintLayer( pSh->GetDoc()->getIDocumentDrawModelAccess().GetHeavenId(), + pPrintData, + *pPage, pPage->getFrameArea(), + &aPageBackgrdColor, + pPage->IsRightToLeft(), + &aSwRedirector ); + } + + if ( bExtraData ) + pPage->RefreshExtraData( aPaintRect ); + + gProp.pBLines.reset(); + s_pVout->Leave(); + + // #i68597# + // needed to move grid painting inside Begin/EndDrawLayer bounds and to change + // output rect for it accordingly + if(bGridPainting) + { + SdrPaintView* pPaintView = pSh->Imp()->GetDrawView(); + SdrPageView* pPageView = pPaintView->GetSdrPageView(); + pPageView->DrawPageViewGrid(*pSh->GetOut(), aPaintRect.SVRect(), SwViewOption::GetTextGridColor() ); + } + + // #i68597# + // moved paint post-process for DrawingLayer overlay here, see above + { + pSh->DLPostPaint2(true); + } + } + + pPage->PaintDecorators( ); + pPage->PaintBreak(); + } + else if ( bBookMode && pSh->GetWin() && !pSh->GetDoc()->GetDocShell()->IsInPlaceActive() ) + { + // paint empty page + SwRect aPaintRect; + SwRect aEmptyPageRect( pPage->getFrameArea() ); + + // code from vprint.cxx + const SwPageFrame& rFormatPage = pPage->GetFormatPage(); + aEmptyPageRect.SSize( rFormatPage.getFrameArea().SSize() ); + + SwPageFrame::GetBorderAndShadowBoundRect( aEmptyPageRect, pSh, &rRenderContext, aPaintRect, + bPaintLeftShadow, bPaintRightShadow, bRightSidebar ); + aPaintRect.Intersection_( aRect ); + + if ( aRect.Overlaps( aEmptyPageRect ) ) + { + // #i75172# if called from SwViewShell::ImplEndAction it should no longer + // really be used but handled by SwViewShell::ImplEndAction already + { + const vcl::Region aDLRegion(aPaintRect.SVRect()); + pSh->DLPrePaint2(aDLRegion); + } + + if( pSh->GetOut()->GetFillColor() != aGlobalRetoucheColor ) + pSh->GetOut()->SetFillColor( aGlobalRetoucheColor ); + // No line color + pSh->GetOut()->SetLineColor(); + // Use aligned page rectangle + { + SwRect aTmpPageRect( aEmptyPageRect ); + ::SwAlignRect( aTmpPageRect, pSh, &rRenderContext ); + aEmptyPageRect = aTmpPageRect; + } + + pSh->GetOut()->DrawRect( aEmptyPageRect.SVRect() ); + + // paint empty page text + const vcl::Font& rEmptyPageFont = SwPageFrame::GetEmptyPageFont(); + const vcl::Font aOldFont( pSh->GetOut()->GetFont() ); + + pSh->GetOut()->SetFont( rEmptyPageFont ); + pSh->GetOut()->DrawText( aEmptyPageRect.SVRect(), SwResId( STR_EMPTYPAGE ), + DrawTextFlags::VCenter | + DrawTextFlags::Center | + DrawTextFlags::Clip ); + + pSh->GetOut()->SetFont( aOldFont ); + // paint shadow and border for empty page + SwPageFrame::PaintBorderAndShadow( aEmptyPageRect, pSh, bPaintLeftShadow, bPaintRightShadow, bRightSidebar ); + SwPageFrame::PaintNotesSidebar( aEmptyPageRect, pSh, pPage->GetPhyPageNum(), bRightSidebar); + + { + pSh->DLPostPaint2(true); + } + } + } + + OSL_ENSURE( !pPage->GetNext() || pPage->GetNext()->IsPageFrame(), + "Neighbour of page is not a page." ); + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + } + + gProp.pSLines.reset(); + + if ( bResetRootPaint ) + SwRootFrame::s_isInPaint = false; + if ( pStatics ) + pStatics.reset(); + else + { + gProp.pSProgress = nullptr; + gProp.pSGlobalShell = nullptr; + } + + const_cast<SwRootFrame*>(this)->SetCallbackActionEnabled( bOldAction ); +} + +static void lcl_EmergencyFormatFootnoteCont( SwFootnoteContFrame *pCont ) +{ + vcl::RenderContext* pRenderContext = pCont->getRootFrame()->GetCurrShell()->GetOut(); + + //It's possible that the Cont will get destroyed. + SwContentFrame *pCnt = pCont->ContainsContent(); + while ( pCnt && pCnt->IsInFootnote() ) + { + pCnt->Calc(pRenderContext); + pCnt = pCnt->GetNextContentFrame(); + } +} + +namespace { + +class SwShortCut +{ + SwRectDist m_fnCheck; + tools::Long m_nLimit; + +public: + SwShortCut( const SwFrame& rFrame, const SwRect& rRect ); + bool Stop(const SwRect& rRect) const { return (rRect.*m_fnCheck)(m_nLimit) > 0; } +}; + +} + +SwShortCut::SwShortCut( const SwFrame& rFrame, const SwRect& rRect ) +{ + bool bVert = rFrame.IsVertical(); + bool bR2L = rFrame.IsRightToLeft(); + if( rFrame.IsNeighbourFrame() && bVert == bR2L ) + { + if( bVert ) + { + m_fnCheck = &SwRect::GetBottomDistance; + m_nLimit = rRect.Top(); + } + else + { + m_fnCheck = &SwRect::GetLeftDistance; + m_nLimit = rRect.Left() + rRect.Width(); + } + } + else if( bVert == rFrame.IsNeighbourFrame() ) + { + m_fnCheck = &SwRect::GetTopDistance; + m_nLimit = rRect.Top() + rRect.Height(); + } + else + { + if ( rFrame.IsVertLR() ) + { + m_fnCheck = &SwRect::GetLeftDistance; + m_nLimit = rRect.Right(); + } + else + { + m_fnCheck = &SwRect::GetRightDistance; + m_nLimit = rRect.Left(); + } + } +} + +void SwLayoutFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const) const +{ + // #i16816# tagged pdf support + Frame_Info aFrameInfo( *this ); + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, &aFrameInfo, nullptr, rRenderContext ); + + const SwFrame *pFrame = Lower(); + if ( !pFrame ) + return; + + SwFrameDeleteGuard g(const_cast<SwLayoutFrame*>(this)); // lock because Calc() and recursion + SwShortCut aShortCut( *pFrame, rRect ); + bool bCnt = pFrame->IsContentFrame(); + if ( bCnt ) + pFrame->Calc(&rRenderContext); + + if ( pFrame->IsFootnoteContFrame() ) + { + ::lcl_EmergencyFormatFootnoteCont( const_cast<SwFootnoteContFrame*>(static_cast<const SwFootnoteContFrame*>(pFrame)) ); + pFrame = Lower(); + } + + const SwPageFrame *pPage = nullptr; + bool bWin = gProp.pSGlobalShell->GetWin() != nullptr; + if (comphelper::LibreOfficeKit::isTiledPainting()) + // Tiled rendering is similar to printing in this case: painting transparently multiple + // times will result in darker colors: avoid that. + bWin = false; + + while ( IsAnLower( pFrame ) ) + { + SwRect aPaintRect( pFrame->GetPaintArea() ); + if( aShortCut.Stop( aPaintRect ) ) + break; + if ( bCnt && gProp.pSProgress ) + SfxProgress::Reschedule(); + + //We need to retouch if a frame explicitly requests it. + //First do the retouch, because this could flatten the borders. + if ( pFrame->IsRetouche() ) + { + if ( pFrame->IsRetoucheFrame() && bWin && !pFrame->GetNext() ) + { + if ( !pPage ) + pPage = FindPageFrame(); + pFrame->Retouch( pPage, rRect ); + } + pFrame->ResetRetouche(); + } + + if ( rRect.Overlaps( aPaintRect ) ) + { + if ( bCnt && pFrame->IsCompletePaint() && + !rRect.Contains( aPaintRect ) && Application::AnyInput( VclInputFlags::KEYBOARD ) ) + { + //fix(8104): It may happen, that the processing wasn't complete + //but some parts of the paragraph were still repainted. + //This could lead to the situation, that other parts of the + //paragraph won't be repainted at all. The only solution seems + //to be an invalidation of the window. + //To not make it too severe the rectangle is limited by + //painting the desired part and only invalidating the + //remaining paragraph parts. + if ( aPaintRect.Left() == rRect.Left() && + aPaintRect.Right() == rRect.Right() ) + { + aPaintRect.Bottom( rRect.Top() - 1 ); + if ( aPaintRect.Height() > 0 ) + gProp.pSGlobalShell->InvalidateWindows(aPaintRect); + aPaintRect.Top( rRect.Bottom() + 1 ); + aPaintRect.Bottom( pFrame->getFrameArea().Bottom() ); + if ( aPaintRect.Height() > 0 ) + gProp.pSGlobalShell->InvalidateWindows(aPaintRect); + aPaintRect.Top( pFrame->getFrameArea().Top() ); + aPaintRect.Bottom( pFrame->getFrameArea().Bottom() ); + } + else + { + gProp.pSGlobalShell->InvalidateWindows( aPaintRect ); + pFrame = pFrame->GetNext(); + if ( pFrame ) + { + bCnt = pFrame->IsContentFrame(); + if ( bCnt ) + pFrame->Calc(&rRenderContext); + } + continue; + } + } + pFrame->ResetCompletePaint(); + aPaintRect.Intersection_( rRect ); + + pFrame->PaintSwFrame( rRenderContext, aPaintRect ); + + if ( Lower() && Lower()->IsColumnFrame() ) + { + //Paint the column separator line if needed. The page is + //responsible for the page frame - not the upper. + const SwFrameFormat *pFormat = GetUpper() && GetUpper()->IsPageFrame() + ? GetUpper()->GetFormat() + : GetFormat(); + const SwFormatCol &rCol = pFormat->GetCol(); + if ( rCol.GetLineAdj() != COLADJ_NONE ) + { + if ( !pPage ) + pPage = pFrame->FindPageFrame(); + + PaintColLines( aPaintRect, rCol, pPage ); + } + } + } + if ( !bCnt && pFrame->GetNext() && pFrame->GetNext()->IsFootnoteContFrame() ) + ::lcl_EmergencyFormatFootnoteCont( const_cast<SwFootnoteContFrame*>(static_cast<const SwFootnoteContFrame*>(pFrame->GetNext())) ); + + pFrame = pFrame->GetNext(); + + if ( pFrame ) + { + bCnt = pFrame->IsContentFrame(); + if ( bCnt ) + pFrame->Calc(&rRenderContext); + } + } +} + +static drawinglayer::primitive2d::Primitive2DContainer lcl_CreateDashedIndicatorPrimitive( + const basegfx::B2DPoint& rStart, const basegfx::B2DPoint& rEnd, + basegfx::BColor aColor ) +{ + drawinglayer::primitive2d::Primitive2DContainer aSeq( 1 ); + + std::vector< double > aStrokePattern; + basegfx::B2DPolygon aLinePolygon; + aLinePolygon.append(rStart); + aLinePolygon.append(rEnd); + + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + if ( rSettings.GetHighContrastMode( ) ) + { + // Only a solid line in high contrast mode + aColor = rSettings.GetDialogTextColor().getBColor(); + } + else + { + // Get a color for the contrast + basegfx::BColor aHslLine = basegfx::utils::rgb2hsl( aColor ); + double nLuminance = aHslLine.getZ() * 2.5; + if ( nLuminance == 0 ) + nLuminance = 0.5; + else if ( nLuminance >= 1.0 ) + nLuminance = aHslLine.getZ() * 0.4; + aHslLine.setZ( nLuminance ); + const basegfx::BColor aOtherColor = basegfx::utils::hsl2rgb( aHslLine ); + + // Compute the plain line + aSeq[0] = + new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + aLinePolygon, aOtherColor ); + + // Dashed line in twips + aStrokePattern.push_back( 40 ); + aStrokePattern.push_back( 40 ); + + aSeq.resize( 2 ); + } + + // Compute the dashed line primitive + aSeq[ aSeq.size( ) - 1 ] = + new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D ( + basegfx::B2DPolyPolygon( aLinePolygon ), + drawinglayer::attribute::LineAttribute( aColor ), + drawinglayer::attribute::StrokeAttribute( std::move(aStrokePattern) ) ); + + + return aSeq; +} + +void SwPageFrame::PaintBreak( ) const +{ + if ( gProp.pSGlobalShell->GetOut()->GetOutDevType() == OUTDEV_PRINTER || + gProp.pSGlobalShell->GetViewOptions()->IsPDFExport() || + gProp.pSGlobalShell->GetViewOptions()->IsReadonly() || + gProp.pSGlobalShell->IsPreview() ) + return; + + const SwFrame* pBodyFrame = Lower(); + while ( pBodyFrame && !pBodyFrame->IsBodyFrame() ) + pBodyFrame = pBodyFrame->GetNext(); + + if ( pBodyFrame ) + { + const SwLayoutFrame* pLayBody = static_cast< const SwLayoutFrame* >( pBodyFrame ); + const SwFlowFrame *pFlowFrame = pLayBody->ContainsContent(); + + // Test if the first node is a table + const SwFrame* pFirstFrame = pLayBody->Lower(); + if ( pFirstFrame && pFirstFrame->IsTabFrame() ) + pFlowFrame = static_cast< const SwTabFrame* >( pFirstFrame ); + + SwWrtShell* pWrtSh = dynamic_cast< SwWrtShell* >( gProp.pSGlobalShell ); + if ( pWrtSh ) + { + SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin(); + SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager(); + + if ( pFlowFrame && pFlowFrame->IsPageBreak( true ) ) + rMngr.SetPageBreakControl( this ); + else + rMngr.RemoveControlsByType( FrameControlType::PageBreak, this ); + } + } + SwLayoutFrame::PaintBreak( ); +} + +void SwColumnFrame::PaintBreak( ) const +{ + if ( gProp.pSGlobalShell->GetOut()->GetOutDevType() == OUTDEV_PRINTER || + gProp.pSGlobalShell->GetViewOptions()->IsPDFExport() || + gProp.pSGlobalShell->GetViewOptions()->IsReadonly() || + gProp.pSGlobalShell->IsPreview() ) + return; + + const SwFrame* pBodyFrame = Lower(); + while ( pBodyFrame && !pBodyFrame->IsBodyFrame() ) + pBodyFrame = pBodyFrame->GetNext(); + + if ( !pBodyFrame ) + return; + + const SwContentFrame *pCnt = static_cast< const SwLayoutFrame* >( pBodyFrame )->ContainsContent(); + if ( !(pCnt && pCnt->IsColBreak( true )) ) + return; + + // Paint the break only if: + // * Not in header footer edition, to avoid conflicts with the + // header/footer marker + // * Non-printing characters are shown, as this is more consistent + // with other formatting marks + if ( !(!gProp.pSGlobalShell->IsShowHeaderFooterSeparator( FrameControlType::Header ) && + !gProp.pSGlobalShell->IsShowHeaderFooterSeparator( FrameControlType::Footer ) && + gProp.pSGlobalShell->GetViewOptions()->IsLineBreak()) ) + return; + + SwRect aRect( pCnt->getFramePrintArea() ); + aRect.Pos() += pCnt->getFrameArea().Pos(); + + // Draw the line + basegfx::B2DPoint aStart( double( aRect.Left() ), aRect.Top() ); + basegfx::B2DPoint aEnd( double( aRect.Right() ), aRect.Top() ); + double nWidth = aRect.Width(); + if ( IsVertical( ) ) + { + aStart = basegfx::B2DPoint( double( aRect.Right() ), double( aRect.Top() ) ); + aEnd = basegfx::B2DPoint( double( aRect.Right() ), double( aRect.Bottom() ) ); + nWidth = aRect.Height(); + } + + basegfx::BColor aLineColor = SwViewOption::GetPageBreakColor().getBColor(); + + drawinglayer::primitive2d::Primitive2DContainer aSeq = + lcl_CreateDashedIndicatorPrimitive( aStart, aEnd, aLineColor ); + + // Add the text above + OUString aBreakText = SwResId(STR_COLUMN_BREAK); + + basegfx::B2DVector aFontSize; + OutputDevice* pOut = gProp.pSGlobalShell->GetOut(); + vcl::Font aFont = pOut->GetSettings().GetStyleSettings().GetToolFont(); + aFont.SetFontHeight( 8 * 20 ); + pOut->SetFont( aFont ); + drawinglayer::attribute::FontAttribute aFontAttr = drawinglayer::primitive2d::getFontAttributeFromVclFont( + aFontSize, aFont, IsRightToLeft(), false ); + + tools::Rectangle aTextRect; + pOut->GetTextBoundRect( aTextRect, aBreakText ); + tools::Long nTextOff = ( nWidth - aTextRect.GetWidth() ) / 2; + + basegfx::B2DHomMatrix aTextMatrix( basegfx::utils::createScaleTranslateB2DHomMatrix( + aFontSize.getX(), aFontSize.getY(), + aRect.Left() + nTextOff, aRect.Top() ) ); + if ( IsVertical() ) + { + aTextMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix ( + aFontSize.getX(), aFontSize.getY(), 0.0, M_PI_2, + aRect.Right(), aRect.Top() + nTextOff ); + } + + aSeq.push_back( + new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + aTextMatrix, + aBreakText, 0, aBreakText.getLength(), + std::vector< double >(), + aFontAttr, + lang::Locale(), + aLineColor ) ); + + ProcessPrimitives( aSeq ); +} + +void SwLayoutFrame::PaintBreak( ) const +{ + const SwFrame* pFrame = Lower(); + while ( pFrame ) + { + if ( pFrame->IsLayoutFrame() ) + static_cast< const SwLayoutFrame*>( pFrame )->PaintBreak( ); + pFrame = pFrame->GetNext(); + } +} + +void SwPageFrame::PaintDecorators( ) const +{ + SwWrtShell* pWrtSh = dynamic_cast< SwWrtShell* >( gProp.pSGlobalShell ); + if ( !pWrtSh ) + return; + + SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin(); + + const SwLayoutFrame* pBody = FindBodyCont(); + if ( !pBody ) + return; + + SwRect aBodyRect( pBody->getFrameArea() ); + + if ( !(gProp.pSGlobalShell->GetOut()->GetOutDevType() != OUTDEV_PRINTER && + !gProp.pSGlobalShell->GetViewOptions()->IsPDFExport() && + !gProp.pSGlobalShell->IsPreview() && + !gProp.pSGlobalShell->GetViewOptions()->IsReadonly() && + !gProp.pSGlobalShell->GetViewOptions()->getBrowseMode() && + ( gProp.pSGlobalShell->IsShowHeaderFooterSeparator( FrameControlType::Header ) || + gProp.pSGlobalShell->IsShowHeaderFooterSeparator( FrameControlType::Footer ) )) ) + return; + + bool bRtl = AllSettings::GetLayoutRTL(); + const SwRect& rVisArea = gProp.pSGlobalShell->VisArea(); + tools::Long nXOff = std::min( aBodyRect.Right(), rVisArea.Right() ); + if ( bRtl ) + nXOff = std::max( aBodyRect.Left(), rVisArea.Left() ); + + // Header + if ( gProp.pSGlobalShell->IsShowHeaderFooterSeparator( FrameControlType::Header ) ) + { + const SwFrame* pHeaderFrame = Lower(); + if ( !pHeaderFrame->IsHeaderFrame() ) + pHeaderFrame = nullptr; + + tools::Long nHeaderYOff = aBodyRect.Top(); + Point nOutputOff = rEditWin.LogicToPixel( Point( nXOff, nHeaderYOff ) ); + rEditWin.GetFrameControlsManager().SetHeaderFooterControl( this, FrameControlType::Header, nOutputOff ); + } + + // Footer + if ( !gProp.pSGlobalShell->IsShowHeaderFooterSeparator( FrameControlType::Footer ) ) + return; + + const SwFrame* pFootnoteContFrame = Lower(); + while ( pFootnoteContFrame ) + { + if ( pFootnoteContFrame->IsFootnoteContFrame() ) + aBodyRect.AddBottom( pFootnoteContFrame->getFrameArea().Bottom() - aBodyRect.Bottom() ); + pFootnoteContFrame = pFootnoteContFrame->GetNext(); + } + + tools::Long nFooterYOff = aBodyRect.Bottom(); + Point nOutputOff = rEditWin.LogicToPixel( Point( nXOff, nFooterYOff ) ); + rEditWin.GetFrameControlsManager().SetHeaderFooterControl( this, FrameControlType::Footer, nOutputOff ); +} + +/** + * For feature #99657# + * + * OD 12.08.2002 + * determines, if background of fly frame has to be drawn transparent + * declaration found in /core/inc/flyfrm.cxx + * + * OD 08.10.2002 #103898# - If the background of the fly frame itself is not + * transparent and the background is inherited from its parent/grandparent, + * the background brush, used for drawing, has to be investigated for transparency. + * + * @return true, if background is transparent drawn +*/ +bool SwFlyFrame::IsBackgroundTransparent() const +{ + bool bBackgroundTransparent = GetFormat()->IsBackgroundTransparent(); + if ( !bBackgroundTransparent && + GetFormat()->IsBackgroundBrushInherited() ) + { + const SvxBrushItem* pBackgroundBrush = nullptr; + std::optional<Color> xSectionTOXColor; + SwRect aDummyRect; + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes; + + if ( GetBackgroundBrush( aFillAttributes, pBackgroundBrush, xSectionTOXColor, aDummyRect, false, /*bConsiderTextBox=*/false) ) + { + if ( xSectionTOXColor && + (xSectionTOXColor->IsTransparent()) && + (xSectionTOXColor != COL_TRANSPARENT) ) + { + bBackgroundTransparent = true; + } + else if(aFillAttributes && aFillAttributes->isUsed()) + { + bBackgroundTransparent = aFillAttributes->isTransparent(); + } + else if ( pBackgroundBrush ) + { + if ( (pBackgroundBrush->GetColor().IsTransparent()) && + (pBackgroundBrush->GetColor() != COL_TRANSPARENT) ) + { + bBackgroundTransparent = true; + } + else + { + const GraphicObject *pTmpGrf = + pBackgroundBrush->GetGraphicObject(); + if ( pTmpGrf && + (pTmpGrf->GetAttr().IsTransparent()) + ) + { + bBackgroundTransparent = true; + } + } + } + } + } + + return bBackgroundTransparent; +}; + +bool SwFlyFrame::IsPaint( SdrObject *pObj, const SwViewShell *pSh ) +{ + SdrObjUserCall *pUserCall = GetUserCall(pObj); + + if ( nullptr == pUserCall ) + return true; + + //Attribute dependent, don't paint for printer or Preview + bool bPaint = gProp.pSFlyOnlyDraw || + static_cast<SwContact*>(pUserCall)->GetFormat()->GetPrint().GetValue(); + if ( !bPaint ) + bPaint = pSh->GetWin() && !pSh->IsPreview(); + + if ( bPaint ) + { + //The paint may be prevented by the superior Flys. + SwFrame *pAnch = nullptr; + if ( dynamic_cast< const SwFlyDrawObj *>( pObj ) != nullptr ) // i#117962# + { + bPaint = false; + } + if ( auto pFlyDraw = dynamic_cast<SwVirtFlyDrawObj *>( pObj ) ) + { + SwFlyFrame *pFly = pFlyDraw->GetFlyFrame(); + if ( gProp.pSFlyOnlyDraw && gProp.pSFlyOnlyDraw == pFly ) + return true; + + //Try to avoid displaying the intermediate stage, Flys which don't + //overlap with the page on which they are anchored won't be + //painted. + //HACK: exception: printing of frames in tables, those can overlap + //a page once in a while when dealing with oversized tables (HTML). + SwPageFrame *pPage = pFly->FindPageFrame(); + if ( pPage && pPage->getFrameArea().Overlaps( pFly->getFrameArea() ) ) + { + pAnch = pFly->AnchorFrame(); + } + + } + else + { + // Consider 'virtual' drawing objects + SwDrawContact* pDrawContact = dynamic_cast<SwDrawContact*>(pUserCall); + pAnch = pDrawContact ? pDrawContact->GetAnchorFrame(pObj) : nullptr; + if ( pAnch ) + { + if ( !pAnch->isFrameAreaPositionValid() ) + pAnch = nullptr; + else if ( pSh->GetOut() == pSh->getIDocumentDeviceAccess().getPrinter( false )) + { + //HACK: we have to omit some of the objects for printing, + //otherwise they would be printed twice. + //The objects should get printed if the TableHack is active + //right now. Afterwards they must not be printed if the + //page over which they float position wise gets printed. + const SwPageFrame *pPage = pAnch->FindPageFrame(); + if ( !pPage->getFrameArea().Overlaps( SwRect(pObj->GetCurrentBoundRect()) ) ) + pAnch = nullptr; + } + } + else + { + if ( dynamic_cast< const SdrObjGroup *>( pObj ) == nullptr ) + { + OSL_FAIL( "<SwFlyFrame::IsPaint(..)> - paint of drawing object without anchor frame!?" ); + } + } + } + if ( pAnch ) + { + if ( pAnch->IsInFly() ) + bPaint = SwFlyFrame::IsPaint( pAnch->FindFlyFrame()->GetVirtDrawObj(), + pSh ); + else if ( gProp.pSFlyOnlyDraw ) + bPaint = false; + } + else + bPaint = false; + } + return bPaint; +} + +void SwCellFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const) const +{ + if ( GetLayoutRowSpan() >= 1 ) + SwLayoutFrame::PaintSwFrame( rRenderContext, rRect ); +} + +namespace { + +struct BorderLinesGuard +{ + explicit BorderLinesGuard() : m_pBorderLines(std::move(gProp.pBLines)) + { + gProp.pBLines.reset(new BorderLines); + } + ~BorderLinesGuard() + { + gProp.pBLines = std::move(m_pBorderLines); + } +private: + std::unique_ptr<BorderLines> m_pBorderLines; +}; + +} + +// set strikethrough for deleted objects anchored to character +void SwFrame::SetDrawObjsAsDeleted( bool bDeleted ) +{ + if ( SwSortedObjs *pObjs = GetDrawObjs() ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + pFly->SetDeleted(bDeleted); + } + } + } +} + +void SwFlyFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const) const +{ + //optimize thumbnail generation and store procedure to improve odt saving performance, #i120030# + SwViewShell *pShell = getRootFrame()->GetCurrShell(); + if (pShell && pShell->GetDoc() && pShell->GetDoc()->GetDocShell()) + { + bool bInGenerateThumbnail = pShell->GetDoc()->GetDocShell()->IsInGenerateAndStoreThumbnail(); + if (bInGenerateThumbnail) + { + const SwRect& aVisRect = pShell->VisArea(); + if (!aVisRect.Overlaps(getFrameArea())) + return; + } + } + + //because of the overlapping of frames and drawing objects the flys have to + //paint their borders (and those of the internal ones) directly. + //e.g. #33066# + gProp.pSLines->LockLines(true); + BorderLinesGuard blg; // this should not paint borders added from PaintBaBo + + SwRect aRect( rRect ); + aRect.Intersection_( getFrameArea() ); + + rRenderContext.Push( vcl::PushFlags::CLIPREGION ); + rRenderContext.SetClipRegion(); + const SwPageFrame* pPage = FindPageFrame(); + + const SwNoTextFrame *pNoText = Lower() && Lower()->IsNoTextFrame() + ? static_cast<const SwNoTextFrame*>(Lower()) : nullptr; + + bool bIsChart = false; //#i102950# don't paint additional borders for charts + //check whether we have a chart + if(pNoText) + { + const SwNoTextNode* pNoTNd = dynamic_cast<const SwNoTextNode*>(pNoText->GetNode()); + if( pNoTNd ) + { + SwOLENode* pOLENd = const_cast<SwOLENode*>(pNoTNd->GetOLENode()); + if( pOLENd && pOLENd->GetOLEObj().GetObject().IsChart() ) + bIsChart = true; + } + } + + { + bool bContour = GetFormat()->GetSurround().IsContour(); + tools::PolyPolygon aPoly; + if ( bContour ) + { + // add 2nd parameter with value <true> + // to indicate that method is called for paint in order to avoid + // load of the intrinsic graphic. + bContour = GetContour( aPoly, true ); + } + + // #i47804# - distinguish complete background paint + // and margin paint. + // paint complete background for Writer text fly frames + bool bPaintCompleteBack( !pNoText ); + // paint complete background for transparent graphic and contour, + // if own background color exists. + const bool bIsGraphicTransparent = pNoText && pNoText->IsTransparent(); + if ( !bPaintCompleteBack && + ( bIsGraphicTransparent|| bContour ) ) + { + const SwFlyFrameFormat* pSwFrameFormat = GetFormat(); + + if (pSwFrameFormat && pSwFrameFormat->supportsFullDrawingLayerFillAttributeSet()) + { + // check for transparency + const drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes(pSwFrameFormat->getSdrAllFillAttributesHelper()); + + // check if the new fill attributes are used + if(aFillAttributes && aFillAttributes->isUsed()) + { + bPaintCompleteBack = true; + } + } + else + { + std::unique_ptr<SvxBrushItem> aBack = GetFormat()->makeBackgroundBrushItem(); + // to determine, if background has to be painted, by checking, if + // background color is not COL_TRANSPARENT ("no fill"/"auto fill") + // or a background graphic exists. + bPaintCompleteBack = + aBack->GetColor() != COL_TRANSPARENT || + aBack->GetGraphicPos() != GPOS_NONE; + } + } + // paint of margin needed. + const bool bPaintMarginOnly( !bPaintCompleteBack && + getFramePrintArea().SSize() != getFrameArea().SSize() ); + + // #i47804# - paint background of parent fly frame + // for transparent graphics in layer Hell, if parent fly frame isn't + // in layer Hell. It's only painted the intersection between the + // parent fly frame area and the paint area <aRect> + const IDocumentDrawModelAccess& rIDDMA = GetFormat()->getIDocumentDrawModelAccess(); + + if (bIsGraphicTransparent && + GetFormat()->GetDoc()->getIDocumentSettingAccess().get(DocumentSettingId::SUBTRACT_FLYS) && + GetVirtDrawObj()->GetLayer() == rIDDMA.GetHellId() && + GetAnchorFrame()->FindFlyFrame() ) + { + const SwFlyFrame* pParentFlyFrame = GetAnchorFrame()->FindFlyFrame(); + if ( pParentFlyFrame->GetDrawObj()->GetLayer() != + rIDDMA.GetHellId() ) + { + SwFlyFrame* pOldRet = gProp.pSRetoucheFly2; + gProp.pSRetoucheFly2 = const_cast<SwFlyFrame*>(this); + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pParentFlyFrame ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + SwRect aPaintRect( aRect ); + aPaintRect.Intersection_( pParentFlyFrame->getFrameArea() ); + pParentFlyFrame->PaintSwFrameBackground( aPaintRect, pPage, rAttrs ); + + gProp.pSRetoucheFly2 = pOldRet; + } + } + + if ( bPaintCompleteBack || bPaintMarginOnly ) + { + //#24926# JP 01.02.96, PaintBaBo is here partially so PaintSwFrameShadowAndBorder + //receives the original Rect but PaintSwFrameBackground only the limited + //one. + + rRenderContext.Push( vcl::PushFlags::FILLCOLOR|vcl::PushFlags::LINECOLOR ); + rRenderContext.SetLineColor(); + + pPage = FindPageFrame(); + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), static_cast<SwFrame const *>(this) ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + // paint background + { + SwRegionRects aRegion( aRect ); + // #i80822# + // suppress painting of background in printing area for + // non-transparent graphics. + if ( bPaintMarginOnly || + ( pNoText && !bIsGraphicTransparent ) ) + { + //What we actually want to paint is the small stripe between + //PrtArea and outer border. + SwRect aTmp( getFramePrintArea() ); aTmp += getFrameArea().Pos(); + aRegion -= aTmp; + } + if ( bContour ) + { + rRenderContext.Push(); + // #i80822# + // apply clip region under the same conditions, which are + // used in <SwNoTextFrame::PaintSwFrame(..)> to set the clip region + // for painting the graphic/OLE. Thus, the clip region is + // also applied for the PDF export. + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + + if ( !rRenderContext.GetConnectMetaFile() || !pSh || !pSh->GetWin() ) + { + rRenderContext.SetClipRegion(vcl::Region(aPoly)); + } + + for ( size_t i = 0; i < aRegion.size(); ++i ) + { + PaintSwFrameBackground( aRegion[i], pPage, rAttrs, false, true ); + } + + rRenderContext.Pop(); + } + else + { + for ( size_t i = 0; i < aRegion.size(); ++i ) + { + PaintSwFrameBackground( aRegion[i], pPage, rAttrs, false, true ); + } + } + } + + // paint border before painting background + PaintSwFrameShadowAndBorder(rRect, pPage, rAttrs); + + rRenderContext.Pop(); + } + } + + // fly frame will paint it's subsidiary lines and + // the subsidiary lines of its lowers on its own, due to overlapping with + // other fly frames or other objects. + if( gProp.pSGlobalShell->GetWin() + && !bIsChart ) //#i102950# don't paint additional borders for charts + { + bool bSubsLineRectsCreated; + if ( gProp.pSSubsLines ) + { + // Lock already existing subsidiary lines + gProp.pSSubsLines->LockLines( true ); + bSubsLineRectsCreated = false; + } + else + { + // create new subsidiary lines + gProp.pSSubsLines.reset(new SwSubsRects); + bSubsLineRectsCreated = true; + } + + bool bSpecSubsLineRectsCreated; + if ( gProp.pSSpecSubsLines ) + { + // Lock already existing special subsidiary lines + gProp.pSSpecSubsLines->LockLines( true ); + bSpecSubsLineRectsCreated = false; + } + else + { + // create new special subsidiary lines + gProp.pSSpecSubsLines.reset(new SwSubsRects); + bSpecSubsLineRectsCreated = true; + } + // Add subsidiary lines of fly frame and its lowers + RefreshLaySubsidiary( pPage, aRect ); + // paint subsidiary lines of fly frame and its lowers + gProp.pSSpecSubsLines->PaintSubsidiary( &rRenderContext, nullptr, gProp ); + gProp.pSSubsLines->PaintSubsidiary(&rRenderContext, gProp.pSLines.get(), gProp); + if ( !bSubsLineRectsCreated ) + // unlock subsidiary lines + gProp.pSSubsLines->LockLines( false ); + else + { + // delete created subsidiary lines container + gProp.pSSubsLines.reset(); + } + + if ( !bSpecSubsLineRectsCreated ) + // unlock special subsidiary lines + gProp.pSSpecSubsLines->LockLines( false ); + else + { + // delete created special subsidiary lines container + gProp.pSSpecSubsLines.reset(); + } + } + + SwLayoutFrame::PaintSwFrame( rRenderContext, aRect ); + + Validate(); + + { + SwTaggedPDFHelper tag(nullptr, nullptr, nullptr, rRenderContext); + // first paint lines added by fly frame paint + // and then unlock other lines. + gProp.pSLines->PaintLines( &rRenderContext, gProp ); + gProp.pSLines->LockLines( false ); + // have to paint frame borders added in heaven layer here... + ProcessPrimitives(gProp.pBLines->GetBorderLines_Clear()); + } + + PaintDecorators(); + + // crossing out for tracked deletion + if ( GetAuthor() != std::string::npos && IsDeleted() ) + { + tools::Long startX = aRect.Left( ), endX = aRect.Right(); + tools::Long startY = aRect.Top( ), endY = aRect.Bottom(); + rRenderContext.SetLineColor( SwPostItMgr::GetColorAnchor(GetAuthor()) ); + rRenderContext.DrawLine(Point(startX, startY), Point(endX, endY)); + rRenderContext.DrawLine(Point(startX, endY), Point(endX, startY)); + } + + rRenderContext.Pop(); + + if ( gProp.pSProgress && pNoText ) + SfxProgress::Reschedule(); +} + +void SwFlyFrame::PaintDecorators() const +{ + // Show the un-float button + SwWrtShell* pWrtSh = dynamic_cast< SwWrtShell* >( gProp.pSGlobalShell ); + if ( pWrtSh ) + { + UpdateUnfloatButton(pWrtSh, IsShowUnfloatButton(pWrtSh)); + } +} + +void SwTextFrame::PaintOutlineContentVisibilityButton() const +{ + SwWrtShell* pWrtSh = dynamic_cast<SwWrtShell*>(gProp.pSGlobalShell); + if (pWrtSh && pWrtSh->GetViewOptions()->IsShowOutlineContentVisibilityButton()) + UpdateOutlineContentVisibilityButton(pWrtSh); +} + + +void SwTabFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const) const +{ + const SwViewOption* pViewOption = gProp.pSGlobalShell->GetViewOptions(); + if (pViewOption->IsTable()) + { + // #i29550# + if ( IsCollapsingBorders() ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), static_cast<SwFrame const *>(this) ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + // paint shadow + if ( rAttrs.GetShadow().GetLocation() != SvxShadowLocation::NONE ) + { + SwRect aRect; + ::lcl_CalcBorderRect( aRect, this, rAttrs, true, gProp ); + PaintShadow( rRect, aRect, rAttrs ); + } + + SwTabFramePainter aHelper(*this); + aHelper.PaintLines(rRenderContext, rRect); + } + + SwLayoutFrame::PaintSwFrame( rRenderContext, rRect ); + } + // #i6467# - no light grey rectangle for page preview + else if ( gProp.pSGlobalShell->GetWin() && !gProp.pSGlobalShell->IsPreview() ) + { + // #i6467# - intersect output rectangle with table frame + SwRect aTabRect( getFramePrintArea() ); + aTabRect.Pos() += getFrameArea().Pos(); + SwRect aTabOutRect( rRect ); + aTabOutRect.Intersection( aTabRect ); + SwViewOption::DrawRect( &rRenderContext, aTabOutRect, COL_LIGHTGRAY ); + } + const_cast<SwTabFrame*>(this)->ResetComplete(); +} + +/** + * Paint border shadow + * + * @param[in] rRect aligned rect to clip the result + * @param[in,out] rOutRect full painting area as input + * painting area reduced by shadow space for border and background as output + * @param[in] rShadow includes shadow attributes + * @param[in] bDrawFullShadowRectangle paint full rect of shadow + * @param[in] bTop paint top part of the shadow + * @param[in] bBottom paint bottom part of the shadow + * @param[in] bLeft paint left part of the shadow + * @param[in] bRight paint right part of the shadow +**/ +static void lcl_PaintShadow( const SwRect& rRect, SwRect& rOutRect, + const SvxShadowItem& rShadow, const bool bDrawFullShadowRectangle, + const bool bTop, const bool bBottom, + const bool bLeft, const bool bRight, + SwPaintProperties const & properties) +{ + const tools::Long nWidth = ::lcl_AlignWidth ( rShadow.GetWidth(), properties ); + const tools::Long nHeight = ::lcl_AlignHeight( rShadow.GetWidth(), properties ); + + SwRects aRegion; + SwRect aOut( rOutRect ); + + switch ( rShadow.GetLocation() ) + { + case SvxShadowLocation::BottomRight: + { + if ( bDrawFullShadowRectangle ) + { + // draw full shadow rectangle + aOut.Top( rOutRect.Top() + nHeight ); + aOut.Left( rOutRect.Left() + nWidth ); + aRegion.push_back( aOut ); + } + else + { + if( bBottom ) + { + aOut.Top( rOutRect.Bottom() - nHeight ); + if( bLeft ) + aOut.Left( rOutRect.Left() + nWidth ); + aRegion.push_back( aOut ); + } + if( bRight ) + { + aOut.Left( rOutRect.Right() - nWidth ); + if( bTop ) + aOut.Top( rOutRect.Top() + nHeight ); + else + aOut.Top( rOutRect.Top() ); + if( bBottom ) + aOut.Bottom( rOutRect.Bottom() - nHeight ); + aRegion.push_back( aOut ); + } + } + + if( bRight ) + rOutRect.AddRight(- nWidth ); + if( bBottom ) + rOutRect.AddBottom(- nHeight ); + } + break; + case SvxShadowLocation::TopLeft: + { + if ( bDrawFullShadowRectangle ) + { + // draw full shadow rectangle + aOut.Bottom( rOutRect.Bottom() - nHeight ); + aOut.Right( rOutRect.Right() - nWidth ); + aRegion.push_back( aOut ); + } + else + { + if( bTop ) + { + aOut.Bottom( rOutRect.Top() + nHeight ); + if( bRight ) + aOut.Right( rOutRect.Right() - nWidth ); + aRegion.push_back( aOut ); + } + if( bLeft ) + { + aOut.Right( rOutRect.Left() + nWidth ); + if( bBottom ) + aOut.Bottom( rOutRect.Bottom() - nHeight ); + else + aOut.Bottom( rOutRect.Bottom() ); + if( bTop ) + aOut.Top( rOutRect.Top() + nHeight ); + aRegion.push_back( aOut ); + } + } + + if( bLeft ) + rOutRect.AddLeft( nWidth ); + if( bTop ) + rOutRect.AddTop( nHeight ); + } + break; + case SvxShadowLocation::TopRight: + { + if ( bDrawFullShadowRectangle ) + { + // draw full shadow rectangle + aOut.Bottom( rOutRect.Bottom() - nHeight); + aOut.Left( rOutRect.Left() + nWidth ); + aRegion.push_back( aOut ); + } + else + { + if( bTop ) + { + aOut.Bottom( rOutRect.Top() + nHeight ); + if( bLeft ) + aOut.Left( rOutRect.Left() + nWidth ); + aRegion.push_back( aOut ); + } + if( bRight ) + { + aOut.Left( rOutRect.Right() - nWidth ); + if( bBottom ) + aOut.Bottom( rOutRect.Bottom() - nHeight ); + else + aOut.Bottom( rOutRect.Bottom() ); + if( bTop ) + aOut.Top( rOutRect.Top() + nHeight ); + aRegion.push_back( aOut ); + } + } + + if( bRight ) + rOutRect.AddRight( - nWidth ); + if( bTop ) + rOutRect.AddTop( nHeight ); + } + break; + case SvxShadowLocation::BottomLeft: + { + if ( bDrawFullShadowRectangle ) + { + // draw full shadow rectangle + aOut.Top( rOutRect.Top() + nHeight ); + aOut.Right( rOutRect.Right() - nWidth ); + aRegion.push_back( aOut ); + } + else + { + if( bBottom ) + { + aOut.Top( rOutRect.Bottom()- nHeight ); + if( bRight ) + aOut.Right( rOutRect.Right() - nWidth ); + aRegion.push_back( aOut ); + } + if( bLeft ) + { + aOut.Right( rOutRect.Left() + nWidth ); + if( bTop ) + aOut.Top( rOutRect.Top() + nHeight ); + else + aOut.Top( rOutRect.Top() ); + if( bBottom ) + aOut.Bottom( rOutRect.Bottom() - nHeight ); + aRegion.push_back( aOut ); + } + } + + if( bLeft ) + rOutRect.AddLeft( nWidth ); + if( bBottom ) + rOutRect.AddBottom( - nHeight ); + } + break; + default: + assert(false); + break; + } + + vcl::RenderContext *pOut = properties.pSGlobalShell->GetOut(); + + DrawModeFlags nOldDrawMode = pOut->GetDrawMode(); + Color aShadowColor( rShadow.GetColor().GetRGBColor() ); + if( !aRegion.empty() && properties.pSGlobalShell->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + // In high contrast mode, the output device has already set the + // DrawModeFlags::SettingsFill flag. This causes the SetFillColor function + // to ignore the setting of a new color. Therefore we have to reset + // the drawing mode + pOut->SetDrawMode( DrawModeFlags::Default ); + aShadowColor = SwViewOption::GetFontColor(); + } + + if ( pOut->GetFillColor() != aShadowColor ) + pOut->SetFillColor( aShadowColor ); + + pOut->SetLineColor(); + + pOut->SetDrawMode( nOldDrawMode ); + + for (const SwRect & rOut : aRegion) + { + aOut = rOut; + if ( rRect.Overlaps( aOut ) && aOut.Height() > 0 && aOut.Width() > 0 ) + { + aOut.Intersection_( rRect ); + pOut->DrawRect( aOut.SVRect() ); + } + } +} + +/** + * Paints a shadow if the format requests so. + * + * The shadow is always painted on the outer edge of the OutRect. + * If needed, the OutRect is shrunk so the painting of the border can be + * done on it. + * + * @note: draw full shadow rectangle for frames with transparent drawn backgrounds (OD 23.08.2002 #99657#) + */ +void SwFrame::PaintShadow( const SwRect& rRect, SwRect& rOutRect, + const SwBorderAttrs &rAttrs ) const +{ + SvxShadowItem rShadow = rAttrs.GetShadow(); + + const bool bCnt = IsContentFrame(); + const bool bTop = !bCnt || rAttrs.GetTopLine ( *(this) ); + const bool bBottom = !bCnt || rAttrs.GetBottomLine( *(this) ); + + if( IsVertical() ) + { + switch( rShadow.GetLocation() ) + { + case SvxShadowLocation::BottomRight: rShadow.SetLocation(SvxShadowLocation::BottomLeft); break; + case SvxShadowLocation::TopLeft: rShadow.SetLocation(SvxShadowLocation::TopRight); break; + case SvxShadowLocation::TopRight: rShadow.SetLocation(SvxShadowLocation::BottomRight); break; + case SvxShadowLocation::BottomLeft: rShadow.SetLocation(SvxShadowLocation::TopLeft); break; + default: break; + } + } + + // determine, if full shadow rectangle have to be drawn or only two shadow rectangles beside the frame. + // draw full shadow rectangle, if frame background is drawn transparent. + // Status Quo: + // SwLayoutFrame can have transparent drawn backgrounds. Thus, + // "asked" their frame format. + const bool bDrawFullShadowRectangle = + ( IsLayoutFrame() && + static_cast<const SwLayoutFrame*>(this)->GetFormat()->IsBackgroundTransparent() + ); + + SwRectFnSet aRectFnSet(this); + ::lcl_ExtendLeftAndRight( rOutRect, *(this), rAttrs, aRectFnSet.FnRect() ); + + lcl_PaintShadow(rRect, rOutRect, rShadow, bDrawFullShadowRectangle, bTop, bBottom, true, true, gProp); +} + +void SwFrame::PaintBorderLine( const SwRect& rRect, + const SwRect& rOutRect, + const SwPageFrame * pPage, + const Color *pColor, + const SvxBorderLineStyle nStyle ) const +{ + if ( !rOutRect.Overlaps( rRect ) ) + return; + + SwRect aOut( rOutRect ); + aOut.Intersection_( rRect ); + + const SwTabFrame *pTab = IsCellFrame() ? FindTabFrame() : nullptr; + SubColFlags nSubCol = ( IsCellFrame() || IsRowFrame() ) + ? SubColFlags::Tab + : ( IsInSct() + ? SubColFlags::Sect + : ( IsInFly() ? SubColFlags::Fly : SubColFlags::Page ) ); + if( pColor && gProp.pSGlobalShell->GetWin() && + Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + pColor = &SwViewOption::GetFontColor(); + } + + if (pPage->GetSortedObjs() && + pPage->GetFormat()->GetDoc()->getIDocumentSettingAccess().get(DocumentSettingId::SUBTRACT_FLYS)) + { + SwRegionRects aRegion( aOut, 4 ); + basegfx::utils::B2DClipState aClipState; + ::lcl_SubtractFlys( this, pPage, aOut, aRegion, aClipState, gProp ); + for ( size_t i = 0; i < aRegion.size(); ++i ) + gProp.pSLines->AddLineRect( aRegion[i], pColor, nStyle, pTab, nSubCol, gProp ); + } + else + gProp.pSLines->AddLineRect( aOut, pColor, nStyle, pTab, nSubCol, gProp ); +} + +namespace drawinglayer::primitive2d +{ + namespace { + + class SwBorderRectanglePrimitive2D : public BufferedDecompositionPrimitive2D + { + private: + /// the transformation defining the geometry of this BorderRectangle + basegfx::B2DHomMatrix maB2DHomMatrix; + + /// the four styles to be used + svx::frame::Style maStyleTop; + svx::frame::Style maStyleRight; + svx::frame::Style maStyleBottom; + svx::frame::Style maStyleLeft; + + protected: + /// local decomposition. + virtual void create2DDecomposition( + Primitive2DContainer& rContainer, + const geometry::ViewInformation2D& rViewInformation) const override; + + public: + /// constructor + SwBorderRectanglePrimitive2D( + const basegfx::B2DHomMatrix& rB2DHomMatrix, + const svx::frame::Style& rStyleTop, + const svx::frame::Style& rStyleRight, + const svx::frame::Style& rStyleBottom, + const svx::frame::Style& rStyleLeft); + + /// data read access + const basegfx::B2DHomMatrix& getB2DHomMatrix() const { return maB2DHomMatrix; } + const svx::frame::Style& getStyleTop() const { return maStyleTop; } + const svx::frame::Style& getStyleRight() const { return maStyleRight; } + const svx::frame::Style& getStyleBottom() const { return maStyleBottom; } + const svx::frame::Style& getStyleLeft() const { return maStyleLeft; } + + /// compare operator + virtual bool operator==(const BasePrimitive2D& rPrimitive) const override; + + /// get range + virtual basegfx::B2DRange getB2DRange(const geometry::ViewInformation2D& rViewInformation) const override; + + /// provide unique ID + virtual sal_uInt32 getPrimitive2DID() const override; + }; + + } + + void SwBorderRectanglePrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, + const geometry::ViewInformation2D& /*rViewInformation*/) const + { + basegfx::B2DPoint aTopLeft(getB2DHomMatrix() * basegfx::B2DPoint(0.0, 0.0)); + basegfx::B2DPoint aTopRight(getB2DHomMatrix() * basegfx::B2DPoint(1.0, 0.0)); + basegfx::B2DPoint aBottomLeft(getB2DHomMatrix() * basegfx::B2DPoint(0.0, 1.0)); + basegfx::B2DPoint aBottomRight(getB2DHomMatrix() * basegfx::B2DPoint(1.0, 1.0)); + + // prepare SdrFrameBorderDataVector + std::shared_ptr<drawinglayer::primitive2d::SdrFrameBorderDataVector> aData( + std::make_shared<drawinglayer::primitive2d::SdrFrameBorderDataVector>()); + + if(getStyleTop().IsUsed()) + { + // move top left/right inwards half border width + basegfx::B2DVector aDown(getB2DHomMatrix() * basegfx::B2DVector(0.0, 1.0)); + aDown.setLength(getStyleTop().GetWidth() * 0.5); + aTopLeft += aDown; + aTopRight += aDown; + } + + if(getStyleBottom().IsUsed()) + { + // move bottom left/right inwards half border width + basegfx::B2DVector aUp(getB2DHomMatrix() * basegfx::B2DVector(0.0, -1.0)); + aUp.setLength(getStyleBottom().GetWidth() * 0.5); + aBottomLeft += aUp; + aBottomRight += aUp; + } + + if(getStyleLeft().IsUsed()) + { + // move left top/bottom inwards half border width + basegfx::B2DVector aRight(getB2DHomMatrix() * basegfx::B2DVector(1.0, 0.0)); + aRight.setLength(getStyleLeft().GetWidth() * 0.5); + aTopLeft += aRight; + aBottomLeft += aRight; + } + + if(getStyleRight().IsUsed()) + { + // move right top/bottom inwards half border width + basegfx::B2DVector aLeft(getB2DHomMatrix() * basegfx::B2DVector(-1.0, 0.0)); + aLeft.setLength(getStyleRight().GetWidth() * 0.5); + aTopRight += aLeft; + aBottomRight += aLeft; + } + + // go round-robin, from TopLeft to TopRight, down, left and back up. That + // way, the borders will not need to be mirrored in any way + if(getStyleTop().IsUsed()) + { + // create BorderPrimitive(s) for top border + const basegfx::B2DVector aVector(aTopRight - aTopLeft); + aData->emplace_back( + aTopLeft, + aVector, + getStyleTop(), + nullptr); + drawinglayer::primitive2d::SdrFrameBorderData& rInstance(aData->back()); + + if(getStyleLeft().IsUsed()) + { + rInstance.addSdrConnectStyleData(true, getStyleLeft(), basegfx::B2DVector(aBottomLeft - aTopLeft), false); + } + + if(getStyleRight().IsUsed()) + { + rInstance.addSdrConnectStyleData(false, getStyleRight(), basegfx::B2DVector(aBottomRight - aTopRight), false); + } + } + + if(getStyleRight().IsUsed()) + { + // create BorderPrimitive(s) for right border + const basegfx::B2DVector aVector(aBottomRight - aTopRight); + aData->emplace_back( + aTopRight, + aVector, + getStyleRight(), + nullptr); + drawinglayer::primitive2d::SdrFrameBorderData& rInstance(aData->back()); + + if(getStyleTop().IsUsed()) + { + rInstance.addSdrConnectStyleData(true, getStyleTop(), basegfx::B2DVector(aTopLeft - aTopRight), false); + } + + if(getStyleBottom().IsUsed()) + { + rInstance.addSdrConnectStyleData(false, getStyleBottom(), basegfx::B2DVector(aBottomLeft - aBottomRight), false); + } + } + + if(getStyleBottom().IsUsed()) + { + // create BorderPrimitive(s) for bottom border + const basegfx::B2DVector aVector(aBottomLeft - aBottomRight); + aData->emplace_back( + aBottomRight, + aVector, + getStyleBottom(), + nullptr); + drawinglayer::primitive2d::SdrFrameBorderData& rInstance(aData->back()); + + if(getStyleRight().IsUsed()) + { + rInstance.addSdrConnectStyleData(true, getStyleRight(), basegfx::B2DVector(aTopRight - aBottomRight), false); + } + + if(getStyleLeft().IsUsed()) + { + rInstance.addSdrConnectStyleData(false, getStyleLeft(), basegfx::B2DVector(aTopLeft - aBottomLeft), false); + } + } + + if(getStyleLeft().IsUsed()) + { + // create BorderPrimitive(s) for left border + const basegfx::B2DVector aVector(aTopLeft - aBottomLeft); + aData->emplace_back( + aBottomLeft, + aVector, + getStyleLeft(), + nullptr); + drawinglayer::primitive2d::SdrFrameBorderData& rInstance(aData->back()); + + if(getStyleBottom().IsUsed()) + { + rInstance.addSdrConnectStyleData(true, getStyleBottom(), basegfx::B2DVector(aBottomRight - aBottomLeft), false); + } + + if(getStyleTop().IsUsed()) + { + rInstance.addSdrConnectStyleData(false, getStyleTop(), basegfx::B2DVector(aTopRight - aTopLeft), false); + } + } + + // create instance of SdrFrameBorderPrimitive2D if + // SdrFrameBorderDataVector is used + if(!aData->empty()) + { + rContainer.append( + drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::SdrFrameBorderPrimitive2D( + aData, + true))); // force visualization to minimal one discrete unit (pixel) + } + } + + SwBorderRectanglePrimitive2D::SwBorderRectanglePrimitive2D( + const basegfx::B2DHomMatrix& rB2DHomMatrix, + const svx::frame::Style& rStyleTop, + const svx::frame::Style& rStyleRight, + const svx::frame::Style& rStyleBottom, + const svx::frame::Style& rStyleLeft) + : maB2DHomMatrix(rB2DHomMatrix), + maStyleTop(rStyleTop), + maStyleRight(rStyleRight), + maStyleBottom(rStyleBottom), + maStyleLeft(rStyleLeft) + { + } + + bool SwBorderRectanglePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BasePrimitive2D::operator==(rPrimitive)) + { + const SwBorderRectanglePrimitive2D& rCompare = static_cast<const SwBorderRectanglePrimitive2D&>(rPrimitive); + + return (getB2DHomMatrix() == rCompare.getB2DHomMatrix() && + getStyleTop() == rCompare.getStyleTop() && + getStyleRight() == rCompare.getStyleRight() && + getStyleBottom() == rCompare.getStyleBottom() && + getStyleLeft() == rCompare.getStyleLeft()); + } + + return false; + } + + basegfx::B2DRange SwBorderRectanglePrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + + aRetval.transform(getB2DHomMatrix()); + return aRetval; + } + + // provide unique ID + sal_uInt32 SwBorderRectanglePrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_SWBORDERRECTANGLERIMITIVE; + } + +} // end of namespace drawinglayer::primitive2d + +namespace { + +editeng::SvxBorderLine const * get_ptr(std::optional<editeng::SvxBorderLine> const & opt) { + return opt ? &*opt : nullptr; +} + +} + +void PaintCharacterBorder( + const SwFont& rFont, + const SwRect& rPaintArea, + const bool bVerticalLayout, + const bool bVerticalLayoutLRBT, + const bool bJoinWithPrev, + const bool bJoinWithNext ) +{ + SwRect aAlignedRect(rPaintArea); + SwAlignRect(aAlignedRect, gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut()); + + bool bTop = true; + bool bBottom = true; + bool bLeft = true; + bool bRight = true; + + switch (rFont.GetOrientation(bVerticalLayout, bVerticalLayoutLRBT).get()) + { + case 0 : + bLeft = !bJoinWithPrev; + bRight = !bJoinWithNext; + break; + case 900 : + bBottom = !bJoinWithPrev; + bTop = !bJoinWithNext; + break; + case 1800 : + bRight = !bJoinWithPrev; + bLeft = !bJoinWithNext; + break; + case 2700 : + bTop = !bJoinWithPrev; + bBottom = !bJoinWithNext; + break; + } + + // Paint shadow (reduce painting rect) + { + const SvxShadowItem aShadow( + 0, &rFont.GetShadowColor(), rFont.GetShadowWidth(), + rFont.GetAbsShadowLocation(bVerticalLayout, bVerticalLayoutLRBT)); + + if( aShadow.GetLocation() != SvxShadowLocation::NONE ) + { + lcl_PaintShadow( rPaintArea, aAlignedRect, aShadow, + false, bTop, bBottom, bLeft, bRight, gProp); + } + } + + const basegfx::B2DHomMatrix aBorderTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aAlignedRect.Width(), aAlignedRect.Height(), + aAlignedRect.Left(), aAlignedRect.Top())); + const svx::frame::Style aStyleTop( + bTop ? get_ptr(rFont.GetAbsTopBorder(bVerticalLayout, bVerticalLayoutLRBT)) : nullptr, + 1.0); + const svx::frame::Style aStyleRight( + bRight ? get_ptr(rFont.GetAbsRightBorder(bVerticalLayout, bVerticalLayoutLRBT)) : nullptr, + 1.0); + const svx::frame::Style aStyleBottom( + bBottom ? get_ptr(rFont.GetAbsBottomBorder(bVerticalLayout, bVerticalLayoutLRBT)) + : nullptr, + 1.0); + const svx::frame::Style aStyleLeft( + bLeft ? get_ptr(rFont.GetAbsLeftBorder(bVerticalLayout, bVerticalLayoutLRBT)) : nullptr, + 1.0); + drawinglayer::primitive2d::Primitive2DContainer aBorderLineTarget; + + aBorderLineTarget.append( + drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::SwBorderRectanglePrimitive2D( + aBorderTransform, + aStyleTop, + aStyleRight, + aStyleBottom, + aStyleLeft))); + gProp.pBLines->AddBorderLines(std::move(aBorderLineTarget)); +} + +/// #i15844# +static const SwFrame* lcl_HasNextCell( const SwFrame& rFrame ) +{ + OSL_ENSURE( rFrame.IsCellFrame(), + "lcl_HasNextCell( const SwFrame& rFrame ) should be called with SwCellFrame" ); + + const SwFrame* pTmpFrame = &rFrame; + do + { + if ( pTmpFrame->GetNext() ) + return pTmpFrame->GetNext(); + + pTmpFrame = pTmpFrame->GetUpper()->GetUpper(); + } + while ( pTmpFrame->IsCellFrame() ); + + return nullptr; +} + +/** + * Determine cell frame, from which the border attributes + * for paint of top/bottom border has to be used. + * + * OD 21.02.2003 #b4779636#, #107692# + * + * @param _pCellFrame + * input parameter - constant pointer to cell frame for which the cell frame + * for the border attributes has to be determined. + * + * @param _rCellBorderAttrs + * input parameter - constant reference to the border attributes of cell frame + * <_pCellFrame>. + * + * @param _bTop + * input parameter - boolean, that controls, if cell frame for top border or + * for bottom border has to be determined. + * + * @return constant pointer to cell frame, for which the border attributes has + * to be used + */ +static const SwFrame* lcl_GetCellFrameForBorderAttrs( const SwFrame* _pCellFrame, + const SwBorderAttrs& _rCellBorderAttrs, + const bool _bTop ) +{ + OSL_ENSURE( _pCellFrame, "No cell frame available, dying soon" ); + + // determine, if cell frame is at bottom/top border of a table frame and + // the table frame has/is a follow. + const SwFrame* pTmpFrame = _pCellFrame; + bool bCellAtBorder = true; + bool bCellAtLeftBorder = !_pCellFrame->GetPrev(); + bool bCellAtRightBorder = !_pCellFrame->GetNext(); + while( !pTmpFrame->IsRowFrame() || !pTmpFrame->GetUpper()->IsTabFrame() ) + { + pTmpFrame = pTmpFrame->GetUpper(); + if ( pTmpFrame->IsRowFrame() && + (_bTop ? pTmpFrame->GetPrev() : pTmpFrame->GetNext()) + ) + { + bCellAtBorder = false; + } + if ( pTmpFrame->IsCellFrame() ) + { + if ( pTmpFrame->GetPrev() ) + { + bCellAtLeftBorder = false; + } + if ( pTmpFrame->GetNext() ) + { + bCellAtRightBorder = false; + } + } + } + OSL_ENSURE( pTmpFrame && pTmpFrame->IsRowFrame(), "No RowFrame available" ); + + const SwLayoutFrame* pParentRowFrame = static_cast<const SwLayoutFrame*>(pTmpFrame); + const SwTabFrame* pParentTabFrame = + static_cast<const SwTabFrame*>(pParentRowFrame->GetUpper()); + + const bool bCellNeedsAttribute = bCellAtBorder && + ( _bTop ? + // bCellInFirstRowWithMaster + ( !pParentRowFrame->GetPrev() && + pParentTabFrame->IsFollow() && + 0 == pParentTabFrame->GetTable()->GetRowsToRepeat() ) : + // bCellInLastRowWithFollow + ( !pParentRowFrame->GetNext() && + pParentTabFrame->GetFollow() ) + ); + + const SwFrame* pRet = _pCellFrame; + if ( bCellNeedsAttribute ) + { + // determine, if cell frame has no borders inside the table. + const SwFrame* pNextCell = nullptr; + bool bNoBordersInside = false; + + if ( bCellAtLeftBorder && ( nullptr != ( pNextCell = lcl_HasNextCell( *_pCellFrame ) ) ) ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pNextCell ); + const SwBorderAttrs &rBorderAttrs = *aAccess.Get(); + const SvxBoxItem& rBorderBox = rBorderAttrs.GetBox(); + bCellAtRightBorder = !lcl_HasNextCell( *pNextCell ); + bNoBordersInside = + ( !rBorderBox.GetTop() || !pParentRowFrame->GetPrev() ) && + !rBorderBox.GetLeft() && + ( !rBorderBox.GetRight() || bCellAtRightBorder ) && + ( !rBorderBox.GetBottom() || !pParentRowFrame->GetNext() ); + } + else + { + const SvxBoxItem& rBorderBox = _rCellBorderAttrs.GetBox(); + bNoBordersInside = + ( !rBorderBox.GetTop() || !pParentRowFrame->GetPrev() ) && + ( !rBorderBox.GetLeft() || bCellAtLeftBorder ) && + ( !rBorderBox.GetRight() || bCellAtRightBorder ) && + ( !rBorderBox.GetBottom() || !pParentRowFrame->GetNext() ); + } + + if ( bNoBordersInside ) + { + if ( _bTop && !_rCellBorderAttrs.GetBox().GetTop() ) + { + //-hack + // Cell frame has no top border and no border inside the table, but + // it is at the top border of a table frame, which is a follow. + // Thus, use border attributes of cell frame in first row of complete table. + // First, determine first table frame of complete table. + SwTabFrame* pMasterTabFrame = pParentTabFrame->FindMaster( true ); + // determine first row of complete table. + const SwFrame* pFirstRow = pMasterTabFrame->GetLower(); + // return first cell in first row + SwFrame* pLowerCell = const_cast<SwFrame*>(pFirstRow->GetLower()); + while ( !pLowerCell->IsCellFrame() || + ( pLowerCell->GetLower() && pLowerCell->GetLower()->IsRowFrame() ) + ) + { + pLowerCell = pLowerCell->GetLower(); + } + OSL_ENSURE( pLowerCell && pLowerCell->IsCellFrame(), "No CellFrame available" ); + pRet = pLowerCell; + } + else if ( !_bTop && !_rCellBorderAttrs.GetBox().GetBottom() ) + { + //-hack + // Cell frame has no bottom border and no border inside the table, + // but it is at the bottom border of a table frame, which has a follow. + // Thus, use border attributes of cell frame in last row of complete table. + // First, determine last table frame of complete table. + SwTabFrame* pLastTabFrame = const_cast<SwTabFrame*>(pParentTabFrame->GetFollow()); + while ( pLastTabFrame->GetFollow() ) + { + pLastTabFrame = pLastTabFrame->GetFollow(); + } + // determine last row of complete table. + SwFrame* pLastRow = pLastTabFrame->GetLastLower(); + // return first bottom border cell in last row + SwFrame* pLowerCell = pLastRow->GetLower(); + while ( !pLowerCell->IsCellFrame() || + ( pLowerCell->GetLower() && pLowerCell->GetLower()->IsRowFrame() ) + ) + { + if ( pLowerCell->IsRowFrame() ) + { + while ( pLowerCell->GetNext() ) + { + pLowerCell = pLowerCell->GetNext(); + } + } + pLowerCell = pLowerCell->GetLower(); + } + OSL_ENSURE( pLowerCell && pLowerCell->IsCellFrame(), "No CellFrame available" ); + pRet = pLowerCell; + } + } + } + + return pRet; +} + +std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> SwFrame::CreateProcessor2D( ) const +{ + basegfx::B2DRange aViewRange; + + SdrPage *pDrawPage = getRootFrame()->GetCurrShell()->Imp()->GetPageView()->GetPage(); + const drawinglayer::geometry::ViewInformation2D aNewViewInfos( + basegfx::B2DHomMatrix( ), + getRootFrame()->GetCurrShell()->GetOut()->GetViewTransformation(), + aViewRange, + GetXDrawPageForSdrPage( pDrawPage ), + 0.0); + + return drawinglayer::processor2d::createBaseProcessor2DFromOutputDevice( + *getRootFrame()->GetCurrShell()->GetOut(), + aNewViewInfos ); +} + +void SwFrame::ProcessPrimitives( const drawinglayer::primitive2d::Primitive2DContainer& rSequence ) const +{ + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor2D = CreateProcessor2D(); + if ( pProcessor2D ) + { + pProcessor2D->process( rSequence ); + } +} + +/// Paints shadows and borders +void SwFrame::PaintSwFrameShadowAndBorder( + const SwRect& rRect, + const SwPageFrame* /*pPage*/, + const SwBorderAttrs& rAttrs) const +{ + // There's nothing (Row,Body,Footnote,Root,Column,NoText) need to do here + if (GetType() & (SwFrameType::NoTxt|SwFrameType::Row|SwFrameType::Body|SwFrameType::Ftn|SwFrameType::Column|SwFrameType::Root)) + return; + + if (IsCellFrame() && !gProp.pSGlobalShell->GetViewOptions()->IsTable()) + return; + + // #i29550# + if ( IsTabFrame() || IsCellFrame() || IsRowFrame() ) + { + const SwTabFrame* pTabFrame = FindTabFrame(); + if ( pTabFrame->IsCollapsingBorders() ) + return; + + if ( pTabFrame->GetTable()->IsNewModel() && ( !IsCellFrame() || IsCoveredCell() ) ) + return; + } + + const bool bLine = rAttrs.IsLine(); + const bool bShadow = rAttrs.GetShadow().GetLocation() != SvxShadowLocation::NONE; + + // - flag to control, + //-hack has to be used. + const bool bb4779636HackActive = true; + + const SwFrame* pCellFrameForBottomBorderAttrs = nullptr; + const SwFrame* pCellFrameForTopBorderAttrs = nullptr; + bool bFoundCellForTopOrBorderAttrs = false; + if ( bb4779636HackActive && IsCellFrame() ) + { + pCellFrameForBottomBorderAttrs = lcl_GetCellFrameForBorderAttrs( this, rAttrs, false ); + if ( pCellFrameForBottomBorderAttrs != this ) + bFoundCellForTopOrBorderAttrs = true; + pCellFrameForTopBorderAttrs = lcl_GetCellFrameForBorderAttrs( this, rAttrs, true ); + if ( pCellFrameForTopBorderAttrs != this ) + bFoundCellForTopOrBorderAttrs = true; + } + + // - add condition <bFoundCellForTopOrBorderAttrs> + //-hack + if ( !(bLine || bShadow || bFoundCellForTopOrBorderAttrs) ) + return; + + //If the rectangle is completely inside the PrtArea, no border needs to + //be painted. + //For the PrtArea the aligned value needs to be used, otherwise it could + //happen, that some parts won't be processed. + SwRect aRect( getFramePrintArea() ); + aRect += getFrameArea().Pos(); + ::SwAlignRect( aRect, gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut() ); + // new local boolean variable in order to + // suspend border paint under special cases - see below. + // NOTE: This is a fix for the implementation of feature #99657#. + bool bDrawOnlyShadowForTransparentFrame = false; + if ( aRect.Contains( rRect ) ) + { + // paint shadow, if background is transparent. + // Because of introduced transparent background for fly frame #99657#, + // the shadow have to be drawn if the background is transparent, + // in spite the fact that the paint rectangle <rRect> lies fully + // in the printing area. + // NOTE to chosen solution: + // On transparent background, continue processing, but suspend + // drawing of border by setting <bDrawOnlyShadowForTransparentFrame> + // to true. + if ( IsLayoutFrame() && + static_cast<const SwLayoutFrame*>(this)->GetFormat()->IsBackgroundTransparent() ) + { + bDrawOnlyShadowForTransparentFrame = true; + } + else + { + return; + } + } + + ::lcl_CalcBorderRect( aRect, this, rAttrs, true, gProp ); + rAttrs.SetGetCacheLine( true ); + + if(bShadow) + { + PaintShadow(rRect, aRect, rAttrs); + } + + // suspend drawing of border + // add condition < NOT bDrawOnlyShadowForTransparentFrame > - see above + // - add condition <bFoundCellForTopOrBorderAttrs> + //-hack. + if((bLine || bFoundCellForTopOrBorderAttrs) && !bDrawOnlyShadowForTransparentFrame) + { + // define SvxBorderLine(s) to use + const SvxBoxItem& rBox(rAttrs.GetBox()); + const SvxBorderLine* pLeftBorder(rBox.GetLeft()); + const SvxBorderLine* pRightBorder(rBox.GetRight()); + const SvxBorderLine* pTopBorder(rBox.GetTop()); + const SvxBorderLine* pBottomBorder(rBox.GetBottom()); + + // if R2L, exchange Right/Left + const bool bR2L(IsCellFrame() && IsRightToLeft()); + + if(bR2L) + { + std::swap(pLeftBorder, pRightBorder); + } + + // if ContentFrame and joined Prev/Next, reset top/bottom as needed + if(IsContentFrame()) + { + const SwFrame* pDirRefFrame(IsCellFrame() ? FindTabFrame() : this); + const SwRectFnSet aRectFnSet(pDirRefFrame); + const SwRectFn& _rRectFn(aRectFnSet.FnRect()); + + if(rAttrs.JoinedWithPrev(*this)) + { + // tdf#115296 re-add adaptation of vert distance to close the evtl. + // existing gap to previous frame + const SwFrame* pPrevFrame(GetPrev()); + (aRect.*_rRectFn->fnSetTop)( (pPrevFrame->*_rRectFn->fnGetPrtBottom)() ); + + // ...and disable top border paint/creation + pTopBorder = nullptr; + } + + if(rAttrs.JoinedWithNext(*this)) + { + // tdf#115296 re-add adaptation of vert distance to close the evtl. + // existing gap to next frame + const SwFrame* pNextFrame(GetNext()); + (aRect.*_rRectFn->fnSetBottom)( (pNextFrame->*_rRectFn->fnGetPrtTop)() ); + + // ...and disable bottom border paint/creation + pBottomBorder = nullptr; + } + } + + // necessary to replace TopBorder? + if((!IsContentFrame() || rAttrs.GetTopLine(*this)) && IsCellFrame() && pCellFrameForTopBorderAttrs != this) + { + SwBorderAttrAccess aAccess(SwFrame::GetCache(), pCellFrameForTopBorderAttrs); + pTopBorder = aAccess.Get()->GetBox().GetTop(); + } + + // necessary to replace BottomBorder? + if((!IsContentFrame() || rAttrs.GetBottomLine(*this)) && IsCellFrame() && pCellFrameForBottomBorderAttrs != this) + { + SwBorderAttrAccess aAccess(SwFrame::GetCache(), pCellFrameForBottomBorderAttrs); + pBottomBorder = aAccess.Get()->GetBox().GetBottom(); + } + + bool bWordBorder = false; + SwViewShell* pShell = getRootFrame()->GetCurrShell(); + if (pShell) + { + const IDocumentSettingAccess& rIDSA = pShell->GetDoc()->getIDocumentSettingAccess(); + bWordBorder = rIDSA.get(DocumentSettingId::TABLE_ROW_KEEP); + } + bool bInWordTableCell = IsContentFrame() && GetUpper()->IsCellFrame() && bWordBorder; + if (bInWordTableCell) + { + // Compat mode: don't paint bottom border if we know the bottom of the content was cut + // off. + auto pContentFrame = static_cast<const SwContentFrame*>(this); + if (pContentFrame->IsUndersized()) + { + pBottomBorder = nullptr; + } + } + + if(nullptr != pLeftBorder || nullptr != pRightBorder || nullptr != pTopBorder || nullptr != pBottomBorder) + { + // now we have all SvxBorderLine(s) sorted out, create geometry + const basegfx::B2DHomMatrix aBorderTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aRect.Width(), aRect.Height(), + aRect.Left(), aRect.Top())); + const svx::frame::Style aStyleTop(pTopBorder, 1.0); + svx::frame::Style aStyleRight(pRightBorder, 1.0); + + // Right/bottom page borders are always mirrored in Word. + if (IsPageFrame() && bWordBorder) + { + aStyleRight.MirrorSelf(); + } + + svx::frame::Style aStyleBottom(pBottomBorder, 1.0); + + if (IsPageFrame() && bWordBorder) + { + aStyleBottom.MirrorSelf(); + } + + const svx::frame::Style aStyleLeft(pLeftBorder, 1.0); + drawinglayer::primitive2d::Primitive2DContainer aBorderLineTarget; + + drawinglayer::primitive2d::Primitive2DReference aRetval( + new drawinglayer::primitive2d::SwBorderRectanglePrimitive2D( + aBorderTransform, + aStyleTop, + aStyleRight, + aStyleBottom, + aStyleLeft)); + + if (bInWordTableCell) + { + // Compat mode: cut off the borders which are outside of our own area. + const SwRect& rClip = getFrameArea(); + basegfx::B2DRectangle aClip(rClip.Left(), rClip.Top(), rClip.Right(), + rClip.Bottom()); + const basegfx::B2DPolyPolygon aPolyPolygon( + basegfx::utils::createPolygonFromRect(aClip)); + const drawinglayer::primitive2d::Primitive2DReference xClipped( + new drawinglayer::primitive2d::MaskPrimitive2D(aPolyPolygon, { aRetval })); + aRetval = xClipped; + } + + aBorderLineTarget.append(aRetval); + gProp.pBLines->AddBorderLines(std::move(aBorderLineTarget)); + } + } + + rAttrs.SetGetCacheLine( false ); +} + +/** + * Special implementation because of the footnote line + * + * Currently only the top frame needs to be taken into account + * Other lines and shadows are set aside + */ +void SwFootnoteContFrame::PaintSwFrameShadowAndBorder( + const SwRect& rRect, + const SwPageFrame* pPage, + const SwBorderAttrs&) const +{ + //If the rectangle is completely inside the PrtArea, no border needs to + //be painted. + SwRect aRect( getFramePrintArea() ); + aRect.Pos() += getFrameArea().Pos(); + if ( !aRect.Contains( rRect ) ) + PaintLine( rRect, pPage ); +} + +/// Paint footnote lines. +void SwFootnoteContFrame::PaintLine( const SwRect& rRect, + const SwPageFrame *pPage ) const +{ + //The length of the line is derived from the percentual indication on the + //PageDesc. The position is also stated on the PageDesc. + //The pen can directly be taken from the PageDesc. + + if ( !pPage ) + pPage = FindPageFrame(); + const SwPageFootnoteInfo &rInf = pPage->GetPageDesc()->GetFootnoteInfo(); + + SwRectFnSet aRectFnSet(this); + SwTwips nPrtWidth = aRectFnSet.GetWidth(getFramePrintArea()); + Fraction aFract( nPrtWidth, 1 ); + aFract *= rInf.GetWidth(); + const SwTwips nWidth = static_cast<tools::Long>(aFract); + + SwTwips nX = aRectFnSet.GetPrtLeft(*this); + switch ( rInf.GetAdj() ) + { + case css::text::HorizontalAdjust_CENTER: + nX += nPrtWidth/2 - nWidth/2; break; + case css::text::HorizontalAdjust_RIGHT: + nX += nPrtWidth - nWidth; break; + case css::text::HorizontalAdjust_LEFT: + /* do nothing */; break; + default: + SAL_WARN("sw.core", "New adjustment for footnote lines?"); + assert(false); + } + SwTwips nLineWidth = rInf.GetLineWidth(); + const SwRect aLineRect = aRectFnSet.IsVert() ? + SwRect( Point(getFrameArea().Left()+getFrameArea().Width()-rInf.GetTopDist()-nLineWidth, + nX), Size( nLineWidth, nWidth ) ) + : SwRect( Point( nX, getFrameArea().Pos().Y() + rInf.GetTopDist() ), + Size( nWidth, rInf.GetLineWidth())); + if ( aLineRect.HasArea() && rInf.GetLineStyle() != SvxBorderLineStyle::NONE) + PaintBorderLine( rRect, aLineRect , pPage, &rInf.GetLineColor(), + rInf.GetLineStyle() ); +} + +/// Paints the separator line for inside columns +void SwLayoutFrame::PaintColLines( const SwRect &rRect, const SwFormatCol &rFormatCol, + const SwPageFrame *pPage ) const +{ + const SwFrame *pCol = Lower(); + if ( !pCol || !pCol->IsColumnFrame() ) + return; + + SwRectFn fnRect = pCol->IsVertical() ? ( pCol->IsVertLR() ? (pCol->IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ) : fnRectHori; + + SwRect aLineRect = getFramePrintArea(); + aLineRect += getFrameArea().Pos(); + + SwTwips nTop = ((aLineRect.*fnRect->fnGetHeight)()*rFormatCol.GetLineHeight()) + / 100 - (aLineRect.*fnRect->fnGetHeight)(); + SwTwips nBottom = 0; + + switch ( rFormatCol.GetLineAdj() ) + { + case COLADJ_CENTER: + nBottom = nTop / 2; nTop -= nBottom; break; + case COLADJ_TOP: + nBottom = nTop; nTop = 0; break; + case COLADJ_BOTTOM: + break; + default: + OSL_ENSURE( false, "New adjustment for column lines?" ); + } + + if( nTop ) + (aLineRect.*fnRect->fnSubTop)( nTop ); + if( nBottom ) + (aLineRect.*fnRect->fnAddBottom)( nBottom ); + + SwTwips nPenHalf = rFormatCol.GetLineWidth(); + (aLineRect.*fnRect->fnSetWidth)( nPenHalf ); + nPenHalf /= 2; + + //We need to be a bit generous here, to not lose something. + SwRect aRect( rRect ); + (aRect.*fnRect->fnSubLeft)( nPenHalf + gProp.nSPixelSzW ); + (aRect.*fnRect->fnAddRight)( nPenHalf + gProp.nSPixelSzW ); + SwRectGet fnGetX = IsRightToLeft() ? fnRect->fnGetLeft : fnRect->fnGetRight; + while ( pCol->GetNext() ) + { + (aLineRect.*fnRect->fnSetPosX) + ( (pCol->getFrameArea().*fnGetX)() - nPenHalf ); + if ( aRect.Overlaps( aLineRect ) ) + PaintBorderLine( aRect, aLineRect , pPage, &rFormatCol.GetLineColor(), + rFormatCol.GetLineStyle() ); + pCol = pCol->GetNext(); + } +} + +void SwPageFrame::PaintGrid( OutputDevice const * pOut, SwRect const &rRect ) const +{ + if( !m_bHasGrid || gProp.pSRetoucheFly || gProp.pSRetoucheFly2 ) + return; + SwTextGridItem const*const pGrid(GetGridItem(this)); + if( !(pGrid && ( OUTDEV_PRINTER != pOut->GetOutDevType() ? + pGrid->GetDisplayGrid() : pGrid->GetPrintGrid() )) ) + return; + + const SwLayoutFrame* pBody = FindBodyCont(); + if( !pBody ) + return; + + SwRect aGrid( pBody->getFramePrintArea() ); + aGrid += pBody->getFrameArea().Pos(); + + SwRect aInter( aGrid ); + aInter.Intersection( rRect ); + if( !aInter.HasArea() ) + return; + + bool bGrid = pGrid->GetRubyTextBelow(); + bool bCell = GRID_LINES_CHARS == pGrid->GetGridType(); + tools::Long nGrid = pGrid->GetBaseHeight(); + const SwDoc* pDoc = GetFormat()->GetDoc(); + tools::Long nGridWidth = GetGridWidth(*pGrid, *pDoc); + tools::Long nRuby = pGrid->GetRubyHeight(); + tools::Long nSum = nGrid + nRuby; + const Color *pCol = &pGrid->GetColor(); + + SwTwips nRight = aInter.Left() + aInter.Width(); + SwTwips nBottom = aInter.Top() + aInter.Height(); + if( IsVertical() ) + { + SwTwips nOrig = aGrid.Left() + aGrid.Width(); + SwTwips nY = nOrig + nSum * + ( ( nOrig - aInter.Left() ) / nSum ); + SwRect aTmp( Point( nY, aInter.Top() ), + Size( 1, aInter.Height() ) ); + SwTwips nX = aGrid.Top() + nGrid * + ( ( aInter.Top() - aGrid.Top() )/ nGrid ); + if( nX < aInter.Top() ) + nX += nGrid; + SwTwips nGridBottom = aGrid.Top() + aGrid.Height(); + bool bLeft = aGrid.Top() >= aInter.Top(); + bool bRight = nGridBottom <= nBottom; + bool bBorder = bLeft || bRight; + while( nY > nRight ) + { + aTmp.Pos().setX( nY ); + if( bGrid ) + { + nY -= nGrid; + SwTwips nPosY = std::max( SwTwips(aInter.Left()), nY ); + SwTwips nHeight = std::min(nRight, SwTwips(aTmp.Pos().X()))-nPosY; + if( nHeight > 0 ) + { + if( bCell ) + { + SwRect aVert( Point( nPosY, nX ), + Size( nHeight, 1 ) ); + while( aVert.Top() <= nBottom ) + { + PaintBorderLine(rRect,aVert,this,pCol); + aVert.Pos().AdjustY(nGrid ); + } + } + else if( bBorder ) + { + SwRect aVert( Point( nPosY, aGrid.Top() ), + Size( nHeight, 1 ) ); + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setY( nGridBottom ); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + else + { + nY -= nRuby; + if( bBorder ) + { + SwTwips nPos = std::max( SwTwips(aInter.Left()), nY ); + SwTwips nW = std::min(nRight, SwTwips(aTmp.Pos().X())) - nPos; + SwRect aVert( Point( nPos, aGrid.Top() ), + Size( nW, 1 ) ); + if( nW > 0 ) + { + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setY( nGridBottom ); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + bGrid = !bGrid; + } + while( nY >= aInter.Left() ) + { + aTmp.Pos().setX( nY ); + PaintBorderLine( rRect, aTmp, this, pCol); + if( bGrid ) + { + nY -= nGrid; + SwTwips nHeight = aTmp.Pos().X() + - std::max(SwTwips(aInter.Left()), nY ); + if( nHeight > 0 ) + { + if( bCell ) + { + SwRect aVert( Point(aTmp.Pos().X()-nHeight, + nX ), Size( nHeight, 1 ) ); + while( aVert.Top() <= nBottom ) + { + PaintBorderLine(rRect,aVert,this,pCol); + aVert.Pos().AdjustY(nGrid ); + } + } + else if( bBorder ) + { + SwRect aVert( Point(aTmp.Pos().X()-nHeight, + aGrid.Top() ), Size( nHeight, 1 ) ); + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setY( nGridBottom ); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + else + { + nY -= nRuby; + if( bBorder ) + { + SwTwips nPos = std::max( SwTwips(aInter.Left()), nY ); + SwTwips nW = std::min(nRight, SwTwips(aTmp.Pos().X())) - nPos; + SwRect aVert( Point( nPos, aGrid.Top() ), + Size( nW, 1 ) ); + if( nW > 0 ) + { + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setY( nGridBottom ); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + bGrid = !bGrid; + } + } + else + { + SwTwips nOrig = aGrid.Top(); + SwTwips nY = nOrig + nSum *( (aInter.Top()-nOrig)/nSum ); + SwRect aTmp( Point( aInter.Left(), nY ), + Size( aInter.Width(), 1 ) ); + //for textgrid refactor + SwTwips nX = aGrid.Left() + nGridWidth * + ( ( aInter.Left() - aGrid.Left() )/ nGridWidth ); + if( nX < aInter.Left() ) + nX += nGridWidth; + SwTwips nGridRight = aGrid.Left() + aGrid.Width(); + bool bLeft = aGrid.Left() >= aInter.Left(); + bool bRight = nGridRight <= nRight; + bool bBorder = bLeft || bRight; + while( nY < aInter.Top() ) + { + aTmp.Pos().setY(nY); + if( bGrid ) + { + nY += nGrid; + SwTwips nPosY = std::max( aInter.Top(), aTmp.Pos().getY() ); + SwTwips nHeight = std::min(nBottom, nY ) - nPosY; + if( nHeight ) + { + if( bCell ) + { + SwRect aVert( Point( nX, nPosY ), + Size( 1, nHeight ) ); + while( aVert.Left() <= nRight ) + { + PaintBorderLine(rRect,aVert,this,pCol); + aVert.Pos().AdjustX(nGridWidth ); //for textgrid refactor + } + } + else if ( bBorder ) + { + SwRect aVert( Point( aGrid.Left(), nPosY ), + Size( 1, nHeight ) ); + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setX( nGridRight ); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + else + { + nY += nRuby; + if( bBorder ) + { + SwTwips nPos = std::max(aInter.Top(),aTmp.Pos().getY()); + SwTwips nH = std::min( nBottom, nY ) - nPos; + SwRect aVert( Point( aGrid.Left(), nPos ), + Size( 1, nH ) ); + if( nH > 0 ) + { + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setX(nGridRight); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + bGrid = !bGrid; + } + while( nY <= nBottom ) + { + aTmp.Pos().setY(nY); + PaintBorderLine( rRect, aTmp, this, pCol); + if( bGrid ) + { + nY += nGrid; + SwTwips nHeight = std::min(nBottom, nY) - aTmp.Pos().getY(); + if( nHeight ) + { + if( bCell ) + { + SwRect aVert( Point( nX, aTmp.Pos().getY() ), + Size( 1, nHeight ) ); + while( aVert.Left() <= nRight ) + { + PaintBorderLine( rRect, aVert, this, pCol); + aVert.Pos().setX(aVert.Pos().getX() + nGridWidth); //for textgrid refactor + } + } + else if( bBorder ) + { + SwRect aVert( Point( aGrid.Left(), + aTmp.Pos().getY() ), Size( 1, nHeight ) ); + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setX(nGridRight); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + else + { + nY += nRuby; + if( bBorder ) + { + SwTwips nPos = std::max(aInter.Top(),aTmp.Pos().Y()); + SwTwips nH = std::min( nBottom, nY ) - nPos; + SwRect aVert( Point( aGrid.Left(), nPos ), + Size( 1, nH ) ); + if( nH > 0 ) + { + if( bLeft ) + PaintBorderLine(rRect,aVert,this,pCol); + if( bRight ) + { + aVert.Pos().setX(nGridRight); + PaintBorderLine(rRect,aVert,this,pCol); + } + } + } + } + bGrid = !bGrid; + } + } +} + +/** + * Paint margin area of a page + * + * OD 20.11.2002 for #104598#: + * implement paint of margin area; margin area will be painted for a + * view shell with a window and if the document is not in online layout. + * + * @param _rOutputRect + * input parameter - constant instance reference of the rectangle, for + * which an output has to be generated. + * + * @param _pViewShell + * input parameter - instance of the view shell, on which the output + * has to be generated. + */ +void SwPageFrame::PaintMarginArea( const SwRect& _rOutputRect, + SwViewShell const * _pViewShell ) const +{ + if ( !_pViewShell->GetWin() || _pViewShell->GetViewOptions()->getBrowseMode() ) + return; + + // Simplified paint with DrawingLayer FillStyle + SwRect aPgRect = getFrameArea(); + aPgRect.Intersection_( _rOutputRect ); + + if(!aPgRect.IsEmpty()) + { + OutputDevice *pOut = _pViewShell->GetOut(); + + if(pOut->GetFillColor() != aGlobalRetoucheColor) + { + pOut->SetFillColor(aGlobalRetoucheColor); + } + + pOut->DrawRect(aPgRect.SVRect()); + } +} + +const sal_Int8 SwPageFrame::snShadowPxWidth = 9; + +bool SwPageFrame::IsRightShadowNeeded() const +{ + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bIsLTR = getRootFrame()->IsLeftToRightViewLayout(); + + // We paint the right shadow if we're not in book mode + // or if we've no sibling or are the last page of the "row" + return !pSh || (!pSh->GetViewOptions()->IsViewLayoutBookMode()) || !GetNext() + || (this == Lower()) || (bIsLTR && OnRightPage()) + || (!bIsLTR && !OnRightPage()); + +} + +bool SwPageFrame::IsLeftShadowNeeded() const +{ + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bIsLTR = getRootFrame()->IsLeftToRightViewLayout(); + + // We paint the left shadow if we're not in book mode + // or if we've no sibling or are the last page of the "row" + return !pSh || (!pSh->GetViewOptions()->IsViewLayoutBookMode()) || !GetPrev() + || (bIsLTR && !OnRightPage()) + || (!bIsLTR && OnRightPage()); +} + +/** + * Determine rectangle for bottom page shadow + * for #i9719# + */ +/*static*/ void SwPageFrame::GetHorizontalShadowRect( const SwRect& _rPageRect, + const SwViewShell* _pViewShell, + OutputDevice const * pRenderContext, + SwRect& _orHorizontalShadowRect, + bool bPaintLeftShadow, + bool bPaintRightShadow, + bool bRightSidebar ) +{ + const SwPostItMgr *pMgr = _pViewShell->GetPostItMgr(); + SwRect aAlignedPageRect( _rPageRect ); + ::SwAlignRect( aAlignedPageRect, _pViewShell, pRenderContext ); + SwRect aPagePxRect(pRenderContext->LogicToPixel( aAlignedPageRect.SVRect() )); + + tools::Long lShadowAdjustment = snShadowPxWidth - 1; // TODO: extract this + + _orHorizontalShadowRect.Chg( + Point( aPagePxRect.Left() + (bPaintLeftShadow ? lShadowAdjustment : 0), 0 ), + Size( aPagePxRect.Width() - ( (bPaintLeftShadow ? lShadowAdjustment : 0) + (bPaintRightShadow ? lShadowAdjustment : 0) ), + snShadowPxWidth ) ); + + if(pMgr && pMgr->ShowNotes() && pMgr->HasNotes()) + { + // Notes are displayed, we've to extend borders + SwTwips aSidebarTotalWidth = pMgr->GetSidebarWidth(true) + pMgr->GetSidebarBorderWidth(true); + if(bRightSidebar) + _orHorizontalShadowRect.AddRight( aSidebarTotalWidth ); + else + _orHorizontalShadowRect.AddLeft( - aSidebarTotalWidth ); + } +} + +namespace { + +enum PaintArea {LEFT, RIGHT, TOP, BOTTOM}; + +} + +#define BORDER_TILE_SIZE 512 + +/// Wrapper around pOut->DrawBitmapEx. +static void lcl_paintBitmapExToRect(vcl::RenderContext *pOut, const Point& aPoint, const Size& aSize, const BitmapEx& rBitmapEx, PaintArea eArea) +{ + if(!comphelper::LibreOfficeKit::isActive()) + { + // The problem is that if we get called multiple times and the color is + // partly transparent, then the result will get darker and darker. To avoid + // this, always paint the background color before doing the real paint. + tools::Rectangle aRect(aPoint, aSize); + + if (!aRect.IsEmpty()) + { + switch (eArea) + { + case LEFT: aRect.SetLeft( aRect.Right() - 1 ); break; + case RIGHT: aRect.SetRight( aRect.Left() + 1 ); break; + case TOP: aRect.SetTop( aRect.Bottom() - 1 ); break; + case BOTTOM: aRect.SetBottom( aRect.Top() + 1 ); break; + } + } + + pOut->SetFillColor(SwViewOption::GetAppBackgroundColor()); + pOut->SetLineColor(); + pOut->DrawRect(pOut->PixelToLogic(aRect)); + } + + // Tiled render if necessary + tools::Rectangle aComplete(aPoint, aSize); + Size aTileSize(BORDER_TILE_SIZE, BORDER_TILE_SIZE); + + tools::Long iterX = eArea != RIGHT && eArea != LEFT ? BORDER_TILE_SIZE : 0; + tools::Long iterY = eArea == RIGHT || eArea == LEFT ? BORDER_TILE_SIZE : 0; + + for (tools::Rectangle aTile(aPoint, aTileSize); true; aTile.Move(iterX, iterY)) + { + tools::Rectangle aRender = aComplete.GetIntersection(aTile); + if (aRender.IsEmpty()) + break; + pOut->DrawBitmapEx(pOut->PixelToLogic(aRender.TopLeft()), + pOut->PixelToLogic(aRender.GetSize()), + Point(0, 0), aRender.GetSize(), + rBitmapEx); + } + +} + +/** + * Paint page border and shadow + * + * for #i9719# + * implement paint of page border and shadow +*/ +/*static*/ void SwPageFrame::PaintBorderAndShadow( const SwRect& _rPageRect, + const SwViewShell* _pViewShell, + bool bPaintLeftShadow, + bool bPaintRightShadow, + bool bRightSidebar ) +{ + // No shadow in prefs + if (!SwViewOption::IsShadow()) + return; + + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *_pViewShell->GetOut() ); + + static vcl::DeleteOnDeinit<drawinglayer::primitive2d::DiscreteShadow> shadowMaskObj( + vcl::bitmap::loadFromName(BMP_PAGE_SHADOW_MASK, + ImageLoadFlags::IgnoreDarkTheme | ImageLoadFlags::IgnoreScalingFactor)); + + drawinglayer::primitive2d::DiscreteShadow& shadowMask = *shadowMaskObj.get(); + static vcl::DeleteOnDeinit< BitmapEx > aPageTopRightShadowObj {}; + static vcl::DeleteOnDeinit< BitmapEx > aPageBottomRightShadowObj {}; + static vcl::DeleteOnDeinit< BitmapEx > aPageBottomLeftShadowObj {}; + static vcl::DeleteOnDeinit< BitmapEx > aPageBottomShadowBaseObj {}; + static vcl::DeleteOnDeinit< BitmapEx > aPageRightShadowBaseObj {}; + static vcl::DeleteOnDeinit< BitmapEx > aPageTopShadowBaseObj {}; + static vcl::DeleteOnDeinit< BitmapEx > aPageTopLeftShadowObj {}; + static vcl::DeleteOnDeinit< BitmapEx > aPageLeftShadowBaseObj {}; + BitmapEx& aPageTopRightShadow = *aPageTopRightShadowObj.get(); + BitmapEx& aPageBottomRightShadow = *aPageBottomRightShadowObj.get(); + BitmapEx& aPageBottomLeftShadow = *aPageBottomLeftShadowObj.get(); + BitmapEx& aPageBottomShadow = *aPageBottomShadowBaseObj.get(); + BitmapEx& aPageRightShadow = *aPageRightShadowBaseObj.get(); + BitmapEx& aPageTopShadow = *aPageTopShadowBaseObj.get(); + BitmapEx& aPageTopLeftShadow = *aPageTopLeftShadowObj.get(); + BitmapEx& aPageLeftShadow = *aPageLeftShadowBaseObj.get(); + static Color aShadowColor( COL_AUTO ); + + SwRect aAlignedPageRect( _rPageRect ); + ::SwAlignRect( aAlignedPageRect, _pViewShell, _pViewShell->GetOut() ); + SwRect aPagePxRect(_pViewShell->GetOut()->LogicToPixel( aAlignedPageRect.SVRect() )); + + if (aShadowColor != SwViewOption::GetShadowColor()) + { + aShadowColor = SwViewOption::GetShadowColor(); + + AlphaMask aMask( shadowMask.getBottomRight().GetBitmap() ); + Bitmap aFilledSquare(aMask.GetSizePixel(), vcl::PixelFormat::N24_BPP); + aFilledSquare.Erase( aShadowColor ); + aPageBottomRightShadow = BitmapEx( aFilledSquare, aMask ); + + aMask = AlphaMask( shadowMask.getBottomLeft().GetBitmap() ); + aFilledSquare = Bitmap(aMask.GetSizePixel(), vcl::PixelFormat::N24_BPP); + aFilledSquare.Erase( aShadowColor ); + aPageBottomLeftShadow = BitmapEx( aFilledSquare, aMask ); + + aMask = AlphaMask( shadowMask.getBottom().GetBitmap() ); + aFilledSquare = Bitmap(aMask.GetSizePixel(), vcl::PixelFormat::N24_BPP); + aFilledSquare.Erase( aShadowColor ); + aPageBottomShadow = BitmapEx( aFilledSquare, aMask ); + + aMask = AlphaMask( shadowMask.getTop().GetBitmap() ); + aFilledSquare = Bitmap(aMask.GetSizePixel(), vcl::PixelFormat::N24_BPP); + aFilledSquare.Erase( aShadowColor ); + aPageTopShadow = BitmapEx( aFilledSquare, aMask ); + + aMask = AlphaMask( shadowMask.getTopRight().GetBitmap() ); + aFilledSquare = Bitmap(aMask.GetSizePixel(), vcl::PixelFormat::N24_BPP); + aFilledSquare.Erase( aShadowColor ); + aPageTopRightShadow = BitmapEx( aFilledSquare, aMask ); + + aMask = AlphaMask( shadowMask.getRight().GetBitmap() ); + aFilledSquare = Bitmap(aMask.GetSizePixel(), vcl::PixelFormat::N24_BPP); + aFilledSquare.Erase( aShadowColor ); + aPageRightShadow = BitmapEx( aFilledSquare, aMask ); + + aMask = AlphaMask( shadowMask.getTopLeft().GetBitmap() ); + aFilledSquare = Bitmap(aMask.GetSizePixel(), vcl::PixelFormat::N24_BPP); + aFilledSquare.Erase( aShadowColor ); + aPageTopLeftShadow = BitmapEx( aFilledSquare, aMask ); + + aMask = AlphaMask( shadowMask.getLeft().GetBitmap() ); + aFilledSquare = Bitmap(aMask.GetSizePixel(), vcl::PixelFormat::N24_BPP); + aFilledSquare.Erase( aShadowColor ); + aPageLeftShadow = BitmapEx( aFilledSquare, aMask ); + } + + SwRect aPaintRect; + OutputDevice *pOut = _pViewShell->GetOut(); + + SwPageFrame::GetHorizontalShadowRect( _rPageRect, _pViewShell, pOut, aPaintRect, bPaintLeftShadow, bPaintRightShadow, bRightSidebar ); + + // Right shadow & corners + if ( bPaintRightShadow ) + { + pOut->DrawBitmapEx( pOut->PixelToLogic( Point( aPaintRect.Right(), aPagePxRect.Bottom() + 1 - (aPageBottomRightShadow.GetSizePixel().Height() - snShadowPxWidth) ) ), + aPageBottomRightShadow ); + pOut->DrawBitmapEx( pOut->PixelToLogic( Point( aPaintRect.Right(), aPagePxRect.Top() - snShadowPxWidth ) ), + aPageTopRightShadow ); + + if (aPagePxRect.Height() > 2 * snShadowPxWidth) + { + const tools::Long nWidth = aPageRightShadow.GetSizePixel().Width(); + const tools::Long nHeight = aPagePxRect.Height() - 2 * (snShadowPxWidth - 1); + if (aPageRightShadow.GetSizePixel().Height() < BORDER_TILE_SIZE) + aPageRightShadow.Scale(Size(nWidth, BORDER_TILE_SIZE), BmpScaleFlag::Fast); + + lcl_paintBitmapExToRect(pOut, + Point(aPaintRect.Right() + snShadowPxWidth, aPagePxRect.Top() + snShadowPxWidth - 1), + Size(nWidth, nHeight), + aPageRightShadow, RIGHT); + } + } + + // Left shadows and corners + if(bPaintLeftShadow) + { + const tools::Long lLeft = aPaintRect.Left() - aPageBottomLeftShadow.GetSizePixel().Width(); + pOut->DrawBitmapEx( pOut->PixelToLogic( Point( lLeft, + aPagePxRect.Bottom() + 1 + snShadowPxWidth - aPageBottomLeftShadow.GetSizePixel().Height() ) ), aPageBottomLeftShadow ); + pOut->DrawBitmapEx( pOut->PixelToLogic( Point( lLeft, aPagePxRect.Top() - snShadowPxWidth ) ), aPageTopLeftShadow ); + if (aPagePxRect.Height() > 2 * snShadowPxWidth) + { + const tools::Long nWidth = aPageLeftShadow.GetSizePixel().Width(); + const tools::Long nHeight = aPagePxRect.Height() - 2 * (snShadowPxWidth - 1); + if (aPageLeftShadow.GetSizePixel().Height() < BORDER_TILE_SIZE) + aPageLeftShadow.Scale(Size(nWidth, BORDER_TILE_SIZE), BmpScaleFlag::Fast); + + lcl_paintBitmapExToRect(pOut, + Point(lLeft, aPagePxRect.Top() + snShadowPxWidth - 1), + Size(nWidth, nHeight), + aPageLeftShadow, LEFT); + } + } + + // Bottom shadow + const tools::Long nBottomHeight = aPageBottomShadow.GetSizePixel().Height(); + if (aPageBottomShadow.GetSizePixel().Width() < BORDER_TILE_SIZE) + aPageBottomShadow.Scale(Size(BORDER_TILE_SIZE, nBottomHeight), BmpScaleFlag::Fast); + + lcl_paintBitmapExToRect(pOut, + Point(aPaintRect.Left(), aPagePxRect.Bottom() + 2), + Size(aPaintRect.Width(), nBottomHeight), + aPageBottomShadow, BOTTOM); + + // Top shadow + const tools::Long nTopHeight = aPageTopShadow.GetSizePixel().Height(); + if (aPageTopShadow.GetSizePixel().Width() < BORDER_TILE_SIZE) + aPageTopShadow.Scale(Size(BORDER_TILE_SIZE, nTopHeight), BmpScaleFlag::Fast); + + lcl_paintBitmapExToRect(pOut, + Point(aPaintRect.Left(), aPagePxRect.Top() - snShadowPxWidth), + Size(aPaintRect.Width(), nTopHeight), + aPageTopShadow, TOP); +} + +/** + * mod #i6193# paint sidebar for notes + * IMPORTANT: if you change the rects here, also change SwPostItMgr::ScrollbarHit + */ +/*static*/void SwPageFrame::PaintNotesSidebar(const SwRect& _rPageRect, SwViewShell* _pViewShell, sal_uInt16 nPageNum, bool bRight) +{ + //TODO: cut out scrollbar area and arrows out of sidepane rect, otherwise it could flicker when pressing arrow buttons + if (!_pViewShell ) + return; + + SwRect aPageRect( _rPageRect ); + SwAlignRect( aPageRect, _pViewShell, _pViewShell->GetOut() ); + + const SwPostItMgr *pMgr = _pViewShell->GetPostItMgr(); + if (!(pMgr && pMgr->ShowNotes() && pMgr->HasNotes())) // do not show anything in print preview + return; + + sal_Int32 nScrollerHeight = pMgr->GetSidebarScrollerHeight(); + const tools::Rectangle &aVisRect = _pViewShell->VisArea().SVRect(); + //draw border and sidepane + _pViewShell->GetOut()->SetLineColor(); + if (!bRight) + { + _pViewShell->GetOut()->SetFillColor(SwViewOption::GetObjectBoundariesColor()); + _pViewShell->GetOut()->DrawRect(tools::Rectangle(Point(aPageRect.Left()-pMgr->GetSidebarBorderWidth(),aPageRect.Top()),Size(pMgr->GetSidebarBorderWidth(),aPageRect.Height()))) ; + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + _pViewShell->GetOut()->SetFillColor(COL_BLACK); + else + _pViewShell->GetOut()->SetFillColor(SwViewOption::GetSectionBoundColor()); + _pViewShell->GetOut()->DrawRect(tools::Rectangle(Point(aPageRect.Left()-pMgr->GetSidebarWidth()-pMgr->GetSidebarBorderWidth(),aPageRect.Top()),Size(pMgr->GetSidebarWidth(),aPageRect.Height()))) ; + } + else + { + _pViewShell->GetOut()->SetFillColor(SwViewOption::GetObjectBoundariesColor()); + SwRect aSidebarBorder(aPageRect.TopRight(),Size(pMgr->GetSidebarBorderWidth(),aPageRect.Height())); + _pViewShell->GetOut()->DrawRect(aSidebarBorder.SVRect()); + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + _pViewShell->GetOut()->SetFillColor(COL_BLACK); + else + _pViewShell->GetOut()->SetFillColor(SwViewOption::GetSectionBoundColor()); + SwRect aSidebar(Point(aPageRect.Right()+pMgr->GetSidebarBorderWidth(),aPageRect.Top()),Size(pMgr->GetSidebarWidth(),aPageRect.Height())); + _pViewShell->GetOut()->DrawRect(aSidebar.SVRect()); + } + if (!pMgr->ShowScrollbar(nPageNum)) + return; + + // draw scrollbar area and arrows + Point aPointBottom; + Point aPointTop; + aPointBottom = !bRight ? Point(aPageRect.Left() - pMgr->GetSidebarWidth() - pMgr->GetSidebarBorderWidth() + _pViewShell->GetOut()->PixelToLogic(Size(2,0)).Width(),aPageRect.Bottom()- _pViewShell->GetOut()->PixelToLogic(Size(0,2+pMgr->GetSidebarScrollerHeight())).Height()) : + Point(aPageRect.Right() + pMgr->GetSidebarBorderWidth() + _pViewShell->GetOut()->PixelToLogic(Size(2,0)).Width(),aPageRect.Bottom()- _pViewShell->GetOut()->PixelToLogic(Size(0,2+pMgr->GetSidebarScrollerHeight())).Height()); + aPointTop = !bRight ? Point(aPageRect.Left() - pMgr->GetSidebarWidth() + _pViewShell->GetOut()->PixelToLogic(Size(2,0)).Width(),aPageRect.Top() + _pViewShell->GetOut()->PixelToLogic(Size(0,2)).Height()) : + Point(aPageRect.Right() + pMgr->GetSidebarBorderWidth() + _pViewShell->GetOut()->PixelToLogic(Size(2,0)).Width(),aPageRect.Top() + _pViewShell->GetOut()->PixelToLogic(Size(0,2)).Height()); + Size aSize(pMgr->GetSidebarWidth() - _pViewShell->GetOut()->PixelToLogic(Size(4,0)).Width(), _pViewShell->GetOut()->PixelToLogic(Size(0,nScrollerHeight)).Height()) ; + tools::Rectangle aRectBottom(aPointBottom,aSize); + tools::Rectangle aRectTop(aPointTop,aSize); + + if (aRectBottom.Overlaps(aVisRect)) + { + + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + _pViewShell->GetOut()->SetLineColor(COL_WHITE); + _pViewShell->GetOut()->SetFillColor(COL_BLACK); + } + else + { + _pViewShell->GetOut()->SetLineColor(COL_BLACK); + _pViewShell->GetOut()->SetFillColor(COL_LIGHTGRAY); + } + _pViewShell->GetOut()->DrawRect(aRectBottom); + _pViewShell->GetOut()->DrawLine(aPointBottom + Point(pMgr->GetSidebarWidth()/3,0), aPointBottom + Point(pMgr->GetSidebarWidth()/3 , _pViewShell->GetOut()->PixelToLogic(Size(0,nScrollerHeight)).Height())); + + _pViewShell->GetOut()->SetLineColor(); + Point aMiddleFirst(aPointBottom + Point(pMgr->GetSidebarWidth()/6,_pViewShell->GetOut()->PixelToLogic(Size(0,nScrollerHeight)).Height()/2)); + Point aMiddleSecond(aPointBottom + Point(pMgr->GetSidebarWidth()/3*2,_pViewShell->GetOut()->PixelToLogic(Size(0,nScrollerHeight)).Height()/2)); + PaintNotesSidebarArrows(aMiddleFirst,aMiddleSecond,_pViewShell,pMgr->GetArrowColor(KEY_PAGEUP,nPageNum), pMgr->GetArrowColor(KEY_PAGEDOWN,nPageNum)); + } + if (!aRectTop.Overlaps(aVisRect)) + return; + + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + _pViewShell->GetOut()->SetLineColor(COL_WHITE); + _pViewShell->GetOut()->SetFillColor(COL_BLACK); + } + else + { + _pViewShell->GetOut()->SetLineColor(COL_BLACK); + _pViewShell->GetOut()->SetFillColor(COL_LIGHTGRAY); + } + _pViewShell->GetOut()->DrawRect(aRectTop); + _pViewShell->GetOut()->DrawLine(aPointTop + Point(pMgr->GetSidebarWidth()/3*2,0), aPointTop + Point(pMgr->GetSidebarWidth()/3*2 , _pViewShell->GetOut()->PixelToLogic(Size(0,nScrollerHeight)).Height())); + + _pViewShell->GetOut()->SetLineColor(); + Point aMiddleFirst(aPointTop + Point(pMgr->GetSidebarWidth()/3,_pViewShell->GetOut()->PixelToLogic(Size(0,nScrollerHeight)).Height()/2)); + Point aMiddleSecond(aPointTop + Point(pMgr->GetSidebarWidth()/6*5,_pViewShell->GetOut()->PixelToLogic(Size(0,nScrollerHeight)).Height()/2)); + PaintNotesSidebarArrows(aMiddleFirst,aMiddleSecond,_pViewShell, pMgr->GetArrowColor(KEY_PAGEUP,nPageNum), pMgr->GetArrowColor(KEY_PAGEDOWN,nPageNum)); +} + +/*static*/ void SwPageFrame::PaintNotesSidebarArrows(const Point &aMiddleFirst, const Point &aMiddleSecond, SwViewShell const * _pViewShell, const Color& rColorUp, const Color& rColorDown) +{ + tools::Polygon aTriangleUp(3); + tools::Polygon aTriangleDown(3); + + aTriangleUp.SetPoint(aMiddleFirst + Point(0,_pViewShell->GetOut()->PixelToLogic(Size(0,-3)).Height()),0); + aTriangleUp.SetPoint(aMiddleFirst + Point(_pViewShell->GetOut()->PixelToLogic(Size(-3,0)).Width(),_pViewShell->GetOut()->PixelToLogic(Size(0,3)).Height()),1); + aTriangleUp.SetPoint(aMiddleFirst + Point(_pViewShell->GetOut()->PixelToLogic(Size(3,0)).Width(),_pViewShell->GetOut()->PixelToLogic(Size(0,3)).Height()),2); + + aTriangleDown.SetPoint(aMiddleSecond + Point(_pViewShell->GetOut()->PixelToLogic(Size(-3,0)).Width(),_pViewShell->GetOut()->PixelToLogic(Size(0,-3)).Height()),0); + aTriangleDown.SetPoint(aMiddleSecond + Point(_pViewShell->GetOut()->PixelToLogic(Size(+3,0)).Width(),_pViewShell->GetOut()->PixelToLogic(Size(0,-3)).Height()),1); + aTriangleDown.SetPoint(aMiddleSecond + Point(0,_pViewShell->GetOut()->PixelToLogic(Size(0,3)).Height()),2); + + _pViewShell->GetOut()->SetFillColor(rColorUp); + _pViewShell->GetOut()->DrawPolygon(aTriangleUp); + _pViewShell->GetOut()->SetFillColor(rColorDown); + _pViewShell->GetOut()->DrawPolygon(aTriangleDown); +} + +/** + * Get bound rectangle of border and shadow for repaints + * + * for #i9719# + */ +/*static*/ void SwPageFrame::GetBorderAndShadowBoundRect( const SwRect& _rPageRect, + const SwViewShell* _pViewShell, + OutputDevice const * pRenderContext, + SwRect& _orBorderAndShadowBoundRect, + bool bLeftShadow, + bool bRightShadow, + bool bRightSidebar + ) +{ + SwRect aAlignedPageRect( _rPageRect ); + ::SwAlignRect( aAlignedPageRect, _pViewShell, pRenderContext ); + SwRect aPagePxRect(pRenderContext->LogicToPixel( aAlignedPageRect.SVRect() )); + aPagePxRect.AddBottom( snShadowPxWidth + 1 ); + aPagePxRect.AddTop( - snShadowPxWidth - 1 ); + + SwRect aTmpRect; + + // Always ask for full shadow since we want a bounding rect + // including at least the page frame + SwPageFrame::GetHorizontalShadowRect( _rPageRect, _pViewShell, pRenderContext, aTmpRect, false, false, bRightSidebar ); + + if(bLeftShadow) aPagePxRect.Left( aTmpRect.Left() - snShadowPxWidth - 1); + if(bRightShadow) aPagePxRect.Right( aTmpRect.Right() + snShadowPxWidth + 1); + + _orBorderAndShadowBoundRect = SwRect(pRenderContext->PixelToLogic( aPagePxRect.SVRect() )); +} + +SwRect SwPageFrame::GetBoundRect(OutputDevice const * pOutputDevice) const +{ + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + SwRect aPageRect( getFrameArea() ); + SwRect aResult; + + if(!pSh) { + return SwRect( Point(0, 0), Size(0, 0) ); + } + + SwPageFrame::GetBorderAndShadowBoundRect( aPageRect, pSh, pOutputDevice, aResult, + IsLeftShadowNeeded(), IsRightShadowNeeded(), SidebarPosition() == sw::sidebarwindows::SidebarPosition::RIGHT ); + return aResult; +} + +/*static*/ SwTwips SwPageFrame::GetSidebarBorderWidth( const SwViewShell* _pViewShell ) +{ + const SwPostItMgr* pPostItMgr = _pViewShell ? _pViewShell->GetPostItMgr() : nullptr; + const SwTwips nRet = pPostItMgr && pPostItMgr->HasNotes() && pPostItMgr->ShowNotes() ? pPostItMgr->GetSidebarWidth() + pPostItMgr->GetSidebarBorderWidth() : 0; + return nRet; +} + +void SwFrame::PaintBaBo( const SwRect& rRect, const SwPageFrame *pPage, + const bool bOnlyTextBackground ) const +{ + if ( !pPage ) + pPage = FindPageFrame(); + + OutputDevice *pOut = gProp.pSGlobalShell->GetOut(); + + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pOut ); + + pOut->Push( vcl::PushFlags::FILLCOLOR|vcl::PushFlags::LINECOLOR ); + pOut->SetLineColor(); + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + + // take care of page margin area + // Note: code move from <SwFrame::PaintSwFrameBackground(..)> to new method + // <SwPageFrame::Paintmargin(..)>. + if ( IsPageFrame() && !bOnlyTextBackground) + { + static_cast<const SwPageFrame*>(this)->PaintMarginArea( rRect, gProp.pSGlobalShell ); + } + + // paint background + { + PaintSwFrameBackground( rRect, pPage, rAttrs, false, true/*bLowerBorder*/, bOnlyTextBackground ); + } + + // paint border before painting background + // paint grid for page frame and paint border + if (!bOnlyTextBackground) + { + SwRect aRect( rRect ); + + if( IsPageFrame() ) + { + static_cast<const SwPageFrame*>(this)->PaintGrid( pOut, aRect ); + } + + PaintSwFrameShadowAndBorder(aRect, pPage, rAttrs); + } + + pOut->Pop(); +} + +static bool lcl_compareFillAttributes(const drawinglayer::attribute::SdrAllFillAttributesHelperPtr& pA, const drawinglayer::attribute::SdrAllFillAttributesHelperPtr& pB) +{ + if (pA == pB) + return true; + if (!pA || !pB) + return false; + return pA->getFillAttribute() == pB->getFillAttribute(); +} + +/// Do not paint background for fly frames without a background brush by +/// calling <PaintBaBo> at the page or at the fly frame its anchored +void SwFrame::PaintSwFrameBackground( const SwRect &rRect, const SwPageFrame *pPage, + const SwBorderAttrs & rAttrs, + const bool bLowerMode, + const bool bLowerBorder, + const bool bOnlyTextBackground ) const +{ + // #i1837# - no paint of table background, if corresponding option is *not* set. + if( IsTabFrame() && + !gProp.pSGlobalShell->GetViewOptions()->IsTable() ) + { + return; + } + + // nothing to do for covered table cells: + if( IsCellFrame() && IsCoveredCell() ) + return; + + SwViewShell *pSh = gProp.pSGlobalShell; + + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pSh->GetOut() ); + + const SvxBrushItem* pItem; + // temporary background brush for a fly frame without a background brush + std::unique_ptr<SvxBrushItem> pTmpBackBrush; + std::optional<Color> pCol; + SwRect aOrigBackRect; + const bool bPageFrame = IsPageFrame(); + bool bLowMode = true; + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes; + + bool bBack = GetBackgroundBrush( aFillAttributes, pItem, pCol, aOrigBackRect, bLowerMode, /*bConsiderTextBox=*/false ); + + // show track changes of table row + if( IsRowFrame() && !getRootFrame()->IsHideRedlines() ) + { + RedlineType eType = static_cast<const SwRowFrame*>(this)->GetTabLine()->GetRedlineType(); + if ( RedlineType::Delete == eType || RedlineType::Insert == eType ) + { + pCol = RedlineType::Delete == eType ? COL_AUTHOR_TABLE_DEL : COL_AUTHOR_TABLE_INS; + bBack = true; + } + } + else if ( bBack && IsCellFrame() && !getRootFrame()->IsHideRedlines() && + // skip cell background to show the row colored according to its tracked change + RedlineType::None != static_cast<const SwRowFrame*>(GetUpper())->GetTabLine()->GetRedlineType() ) + { + return; + } + + //- Output if a separate background is used. + bool bNoFlyBackground = !gProp.bSFlyMetafile && !bBack && IsFlyFrame(); + if ( bNoFlyBackground ) + { + // Fly frame has no background. + // Try to find background brush at parents, if previous call of + // <GetBackgroundBrush> disabled this option with the parameter <bLowerMode> + if ( bLowerMode ) + { + bBack = GetBackgroundBrush( aFillAttributes, pItem, pCol, aOrigBackRect, false, /*bConsiderTextBox=*/false ); + } + // If still no background found for the fly frame, initialize the + // background brush <pItem> with global retouche color and set <bBack> + // to true, that fly frame will paint its background using this color. + if ( !bBack ) + { + // #i6467# - on print output, pdf output and in embedded mode not editing color COL_WHITE is used + // instead of the global retouche color. + if ( pSh->GetOut()->GetOutDevType() == OUTDEV_PRINTER || + pSh->GetViewOptions()->IsPDFExport() || + ( pSh->GetDoc()->GetDocShell()->GetCreateMode() == SfxObjectCreateMode::EMBEDDED && + !pSh->GetDoc()->GetDocShell()->IsInPlaceActive() + ) + ) + { + pTmpBackBrush.reset(new SvxBrushItem( COL_WHITE, RES_BACKGROUND )); + + //UUU + aFillAttributes = std::make_shared<drawinglayer::attribute::SdrAllFillAttributesHelper>(COL_WHITE); + } + else + { + pTmpBackBrush.reset(new SvxBrushItem( aGlobalRetoucheColor, RES_BACKGROUND)); + + //UUU + aFillAttributes = std::make_shared<drawinglayer::attribute::SdrAllFillAttributesHelper>(aGlobalRetoucheColor); + } + + pItem = pTmpBackBrush.get(); + bBack = true; + } + } + + SwRect aPaintRect( getFrameArea() ); + if( IsTextFrame() || IsSctFrame() ) + aPaintRect = UnionFrame( true ); + + // bOnlyTextBackground means background that's on top of background shapes, + // this includes both text and cell frames. + if ( (!bOnlyTextBackground || IsTextFrame() || IsCellFrame()) && aPaintRect.Overlaps( rRect ) ) + { + if ( bBack || bPageFrame || !bLowerMode ) + { + const bool bBrowse = pSh->GetViewOptions()->getBrowseMode(); + SwRect aRect; + if ( (bPageFrame && bBrowse) || + (IsTextFrame() && getFramePrintArea().SSize() == getFrameArea().SSize()) ) + { + aRect = getFrameArea(); + ::SwAlignRect( aRect, gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut() ); + } + else + { + if (bPageFrame && GetAttrSet()->GetItem<SfxBoolItem>(RES_BACKGROUND_FULL_SIZE)->GetValue()) + { + aRect = getFrameArea(); + ::SwAlignRect(aRect, gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut()); + } + else + { + ::lcl_CalcBorderRect( aRect, this, rAttrs, false, gProp); + } + + if ( (IsTextFrame() || IsTabFrame()) && GetPrev() ) + { + if ( GetPrev()->GetAttrSet()->GetBackground() == GetAttrSet()->GetBackground() && + lcl_compareFillAttributes(GetPrev()->getSdrAllFillAttributesHelper(), getSdrAllFillAttributesHelper())) + { + aRect.Top( getFrameArea().Top() ); + } + } + } + aRect.Intersection( rRect ); + + OutputDevice *pOut = pSh->GetOut(); + + if ( aRect.HasArea() ) + { + std::unique_ptr<SvxBrushItem> pNewItem; + + if( pCol ) + { + pNewItem.reset(new SvxBrushItem( *pCol, RES_BACKGROUND )); + pItem = pNewItem.get(); + aFillAttributes = std::make_shared<drawinglayer::attribute::SdrAllFillAttributesHelper>(*pCol); + } + + SwRegionRects aRegion( aRect ); + basegfx::B2DPolygon aB2DPolygon{tools::Polygon(aRect.SVRect()).getB2DPolygon()}; + basegfx::utils::B2DClipState aClipState{basegfx::B2DPolyPolygon(aB2DPolygon)}; + if (pPage->GetSortedObjs() && + pSh->GetDoc()->getIDocumentSettingAccess().get(DocumentSettingId::SUBTRACT_FLYS)) + { + ::lcl_SubtractFlys( this, pPage, aRect, aRegion, aClipState, gProp ); + } + + // Determine, if background transparency + // have to be considered for drawing. + // Status Quo: background transparency have to be + // considered for fly frames + const bool bConsiderBackgroundTransparency = IsFlyFrame(); + bool bDone(false); + + // #i125189# We are also done when the new DrawingLayer FillAttributes are used + // or the FillStyle is set (different from drawing::FillStyle_NONE) + if (aFillAttributes) + { + if(aFillAttributes->isUsed()) + { + // check if really something is painted + bDone = DrawFillAttributes(aFillAttributes, aOrigBackRect, aRegion, aClipState, *pOut); + } + + if(!bDone) + { + // if not, still a FillStyle could be set but the transparency is at 100%, + // thus need to check the model data itself for FillStyle (do not rely on + // SdrAllFillAttributesHelper since it already contains optimized information, + // e.g. transparency leads to no fill) + const drawing::FillStyle eFillStyle(GetAttrSet()->Get(XATTR_FILLSTYLE).GetValue()); + + if(drawing::FillStyle_NONE != eFillStyle) + { + bDone = true; + } + } + } + + if(!bDone) + { + for (size_t i = 0; i < aRegion.size(); ++i) + { + if (1 < aRegion.size()) + { + ::SwAlignRect( aRegion[i], gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut() ); + if( !aRegion[i].HasArea() ) + continue; + } + // add 6th parameter to indicate, if background transparency have to be considered + // Set missing 5th parameter to the default value GRFNUM_NO + // - see declaration in /core/inc/frmtool.hxx. + ::DrawGraphic( + pItem, + *pOut, + aOrigBackRect, + aRegion[i], + GRFNUM_NO, + bConsiderBackgroundTransparency ); + } + } + } + } + else + bLowMode = bLowerMode; + } + + // delete temporary background brush. + pTmpBackBrush.reset(); + + //Now process lower and his neighbour. + //We end this as soon as a Frame leaves the chain and therefore is not a lower + //of me anymore + const SwFrame *pFrame = GetLower(); + if ( !pFrame ) + return; + + SwRect aFrameRect; + SwRect aRect( GetPaintArea() ); + aRect.Intersection_( rRect ); + SwRect aBorderRect( aRect ); + SwShortCut aShortCut( *pFrame, aBorderRect ); + do + { if ( gProp.pSProgress ) + SfxProgress::Reschedule(); + + aFrameRect = pFrame->GetPaintArea(); + if ( aFrameRect.Overlaps( aBorderRect ) ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFrame ); + const SwBorderAttrs &rTmpAttrs = *aAccess.Get(); + if ( ( pFrame->IsLayoutFrame() && bLowerBorder ) || aFrameRect.Overlaps( aRect ) ) + { + pFrame->PaintSwFrameBackground( aRect, pPage, rTmpAttrs, bLowMode, + bLowerBorder, bOnlyTextBackground ); + } + + if ( bLowerBorder ) + { + pFrame->PaintSwFrameShadowAndBorder( aBorderRect, pPage, rTmpAttrs ); + } + } + pFrame = pFrame->GetNext(); + } while ( pFrame && pFrame->GetUpper() == this && + !aShortCut.Stop( aFrameRect ) ); +} + +/// Refreshes all subsidiary lines of a page. +void SwPageFrame::RefreshSubsidiary( const SwRect &rRect ) const +{ + if ( !(isSubsidiaryLinesEnabled() || isTableBoundariesEnabled() + || isSubsidiaryLinesForSectionsEnabled() || isSubsidiaryLinesFlysEnabled()) ) + return; + + if ( !rRect.HasArea() ) + return; + + //During paint using the root, the array is controlled from there. + //Otherwise we'll handle it for our self. + bool bDelSubs = false; + if ( !gProp.pSSubsLines ) + { + gProp.pSSubsLines.reset(new SwSubsRects); + // create container for special subsidiary lines + gProp.pSSpecSubsLines.reset(new SwSubsRects); + bDelSubs = true; + } + + RefreshLaySubsidiary( this, rRect ); + + if ( bDelSubs ) + { + // paint special subsidiary lines and delete its container + gProp.pSSpecSubsLines->PaintSubsidiary( gProp.pSGlobalShell->GetOut(), nullptr, gProp ); + gProp.pSSpecSubsLines.reset(); + + gProp.pSSubsLines->PaintSubsidiary(gProp.pSGlobalShell->GetOut(), gProp.pSLines.get(), gProp); + gProp.pSSubsLines.reset(); + } +} + +void SwLayoutFrame::RefreshLaySubsidiary( const SwPageFrame *pPage, + const SwRect &rRect ) const +{ + const bool bSubsOpt = isSubsidiaryLinesEnabled(); + if ( bSubsOpt ) + PaintSubsidiaryLines( pPage, rRect ); + + const SwFrame *pLow = Lower(); + if( !pLow ) + return; + SwShortCut aShortCut( *pLow, rRect ); + while( pLow && !aShortCut.Stop( pLow->getFrameArea() ) ) + { + if ( pLow->getFrameArea().Overlaps( rRect ) && pLow->getFrameArea().HasArea() ) + { + if ( pLow->IsLayoutFrame() ) + static_cast<const SwLayoutFrame*>(pLow)->RefreshLaySubsidiary( pPage, rRect); + else if ( pLow->GetDrawObjs() ) + { + const SwSortedObjs& rObjs = *(pLow->GetDrawObjs()); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if ( pPage->GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( + pAnchoredObj->GetDrawObj()->GetLayer() ) ) + if (auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + if ( pFly->IsFlyInContentFrame() && pFly->getFrameArea().Overlaps( rRect ) ) + { + if ( !pFly->Lower() || !pFly->Lower()->IsNoTextFrame() || + !static_cast<const SwNoTextFrame*>(pFly->Lower())->HasAnimation()) + pFly->RefreshLaySubsidiary( pPage, rRect ); + } + } + } + } + } + pLow = pLow->GetNext(); + } +} + +/** + * Subsidiary lines to paint the PrtAreas + * Only the LayoutFrames which directly contain Content + * Paints the desired line and pays attention to not overpaint any flys + */ +static void lcl_RefreshLine( const SwLayoutFrame *pLay, + const SwPageFrame *pPage, + const Point &rP1, + const Point &rP2, + const SubColFlags nSubColor, + SwLineRects* pSubsLines ) +{ + //In which direction do we loop? Can only be horizontal or vertical. + OSL_ENSURE( ((rP1.X() == rP2.X()) || (rP1.Y() == rP2.Y())), + "Sloped subsidiary lines are not allowed." ); + + const bool bHori = rP1.Y() == rP2.Y(); + + // use pointers to member function in order to unify flow + typedef tools::Long (Point::*pmfPtGet)() const; + typedef void (Point::*pmfPtSet)(tools::Long); + const pmfPtGet pDirPtX = &Point::X; + const pmfPtGet pDirPtY = &Point::Y; + const pmfPtGet pDirPt = bHori ? pDirPtX : pDirPtY; + const pmfPtSet pDirPtSetX = &Point::setX; + const pmfPtSet pDirPtSetY = &Point::setY; + const pmfPtSet pDirPtSet = bHori ? pDirPtSetX : pDirPtSetY; + + Point aP1( rP1 ); + Point aP2( rP2 ); + + while ( (aP1.*pDirPt)() < (aP2.*pDirPt)() ) + { + //If the starting point lies in a fly, it is directly set behind the + //fly. + //The end point moves to the start if the end point lies in a fly or we + //have a fly between starting point and end point. + // In this way, every position is output one by one. + + //If I'm a fly I'll only avoid those flys which are places 'above' me; + //this means those who are behind me in the array. + //Even if I'm inside a fly or inside a fly inside a fly a.s.o I won't + //avoid any of those flys. + SwOrderIter aIter( pPage ); + const SwFlyFrame *pMyFly = pLay->FindFlyFrame(); + if ( pMyFly ) + { + aIter.Current( pMyFly->GetVirtDrawObj() ); + while ( nullptr != (pMyFly = pMyFly->GetAnchorFrame()->FindFlyFrame()) ) + { + if ( aIter()->GetOrdNum() > pMyFly->GetVirtDrawObj()->GetOrdNum() ) + aIter.Current( pMyFly->GetVirtDrawObj() ); + } + } + else + aIter.Bottom(); + + while ( aIter() ) + { + const SwVirtFlyDrawObj *pObj = static_cast<const SwVirtFlyDrawObj*>(aIter()); + const SwFlyFrame *pFly = pObj ? pObj->GetFlyFrame() : nullptr; + + //I certainly won't avoid myself, even if I'm placed _inside_ the + //fly I won't avoid it. + if ( !pFly || (pFly == pLay || pFly->IsAnLower( pLay )) ) + { + aIter.Next(); + continue; + } + + // do *not* consider fly frames with a transparent background. + // do *not* consider fly frame, which belongs to an invisible layer + if ( pFly->IsBackgroundTransparent() || + !pFly->GetFormat()->GetDoc()->getIDocumentDrawModelAccess().IsVisibleLayerId( pObj->GetLayer() ) ) + { + aIter.Next(); + continue; + } + + //Is the Obj placed on the line + const tools::Long nP1OthPt = !bHori ? rP1.X() : rP1.Y(); + const tools::Rectangle &rBound = pObj->GetCurrentBoundRect(); + const Point aDrPt( rBound.TopLeft() ); + const tools::Long nDrOthPt = !bHori ? aDrPt.X() : aDrPt.Y(); + const Size aDrSz( rBound.GetSize() ); + const tools::Long nDrOthSz = !bHori ? aDrSz.Width() : aDrSz.Height(); + + if ( nP1OthPt >= nDrOthPt && nP1OthPt <= nDrOthPt + nDrOthSz ) + { + const tools::Long nDrDirPt = bHori ? aDrPt.X() : aDrPt.Y(); + const tools::Long nDrDirSz = bHori ? aDrSz.Width() : aDrSz.Height(); + + if ( (aP1.*pDirPt)() >= nDrDirPt && (aP1.*pDirPt)() <= nDrDirPt + nDrDirSz ) + (aP1.*pDirPtSet)( nDrDirPt + nDrDirSz ); + + if ( (aP2.*pDirPt)() >= nDrDirPt && (aP1.*pDirPt)() < (nDrDirPt - 1) ) + (aP2.*pDirPtSet)( nDrDirPt - 1 ); + } + aIter.Next(); + } + + if ( (aP1.*pDirPt)() < (aP2.*pDirPt)() ) + { + SwRect aRect( aP1, aP2 ); + // use parameter <pSubsLines> instead of global variable <gProp.pSSubsLines>. + pSubsLines->AddLineRect( aRect, nullptr, SvxBorderLineStyle::SOLID, + nullptr, nSubColor, gProp ); + } + aP1 = aP2; + (aP1.*pDirPtSet)( (aP1.*pDirPt)() + 1 ); + aP2 = rP2; + } +} + +static drawinglayer::primitive2d::Primitive2DContainer lcl_CreatePageAreaDelimiterPrimitives( + const SwRect& rRect ) +{ + drawinglayer::primitive2d::Primitive2DContainer aSeq( 4 ); + + basegfx::BColor aLineColor = SwViewOption::GetDocBoundariesColor().getBColor(); + double nLineLength = 200.0; // in Twips + + Point aPoints[] = { rRect.TopLeft(), rRect.TopRight(), rRect.BottomRight(), rRect.BottomLeft() }; + double const aXOffDirs[] = { -1.0, 1.0, 1.0, -1.0 }; + double const aYOffDirs[] = { -1.0, -1.0, 1.0, 1.0 }; + + // Actually loop over the corners to create the two lines + for ( int i = 0; i < 4; i++ ) + { + basegfx::B2DVector aHorizVector( aXOffDirs[i], 0.0 ); + basegfx::B2DVector aVertVector( 0.0, aYOffDirs[i] ); + + basegfx::B2DPoint aBPoint( aPoints[i].getX(), aPoints[i].getY() ); + + basegfx::B2DPolygon aPolygon; + aPolygon.append( aBPoint + aHorizVector * nLineLength ); + aPolygon.append( aBPoint ); + aPolygon.append( aBPoint + aVertVector * nLineLength ); + + aSeq[i] = new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + aPolygon, aLineColor ); + } + + return aSeq; +} + +static drawinglayer::primitive2d::Primitive2DContainer lcl_CreateRectangleDelimiterPrimitives ( + const SwRect& rRect ) +{ + drawinglayer::primitive2d::Primitive2DContainer aSeq( 1 ); + basegfx::BColor aLineColor = SwViewOption::GetDocBoundariesColor().getBColor(); + + basegfx::B2DPolygon aPolygon; + aPolygon.append( basegfx::B2DPoint( rRect.Left(), rRect.Top() ) ); + aPolygon.append( basegfx::B2DPoint( rRect.Right(), rRect.Top() ) ); + aPolygon.append( basegfx::B2DPoint( rRect.Right(), rRect.Bottom() ) ); + aPolygon.append( basegfx::B2DPoint( rRect.Left(), rRect.Bottom() ) ); + aPolygon.setClosed( true ); + + aSeq[0] = new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + aPolygon, aLineColor ); + + return aSeq; +} + +static drawinglayer::primitive2d::Primitive2DContainer lcl_CreateColumnAreaDelimiterPrimitives( + const SwRect& rRect ) +{ + drawinglayer::primitive2d::Primitive2DContainer aSeq( 4 ); + + basegfx::BColor aLineColor = SwViewOption::GetDocBoundariesColor().getBColor(); + double nLineLength = 100.0; // in Twips + + Point aPoints[] = { rRect.TopLeft(), rRect.TopRight(), rRect.BottomRight(), rRect.BottomLeft() }; + double const aXOffDirs[] = { 1.0, -1.0, -1.0, 1.0 }; + double const aYOffDirs[] = { 1.0, 1.0, -1.0, -1.0 }; + + // Actually loop over the corners to create the two lines + for ( int i = 0; i < 4; i++ ) + { + basegfx::B2DVector aHorizVector( aXOffDirs[i], 0.0 ); + basegfx::B2DVector aVertVector( 0.0, aYOffDirs[i] ); + + basegfx::B2DPoint aBPoint( aPoints[i].getX(), aPoints[i].getY() ); + + basegfx::B2DPolygon aPolygon; + aPolygon.append( aBPoint + aHorizVector * nLineLength ); + aPolygon.append( aBPoint ); + aPolygon.append( aBPoint + aVertVector * nLineLength ); + + aSeq[i] = new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + aPolygon, aLineColor ); + } + + return aSeq; +} + +void SwPageFrame::PaintSubsidiaryLines( const SwPageFrame *, + const SwRect & ) const +{ + if ( gProp.pSGlobalShell->IsHeaderFooterEdit() ) + return; + + const SwFrame* pLay = Lower(); + const SwFrame* pFootnoteCont = nullptr; + const SwFrame* pPageBody = nullptr; + while ( pLay && !( pFootnoteCont && pPageBody ) ) + { + if ( pLay->IsFootnoteContFrame( ) ) + pFootnoteCont = pLay; + if ( pLay->IsBodyFrame() ) + pPageBody = pLay; + pLay = pLay->GetNext(); + } + + SwRect aArea( pPageBody->getFrameArea() ); + if ( pFootnoteCont ) + aArea.AddBottom( pFootnoteCont->getFrameArea().Bottom() - aArea.Bottom() ); + + if ( !gProp.pSGlobalShell->GetViewOptions()->IsViewMetaChars( ) ) + ProcessPrimitives( lcl_CreatePageAreaDelimiterPrimitives( aArea ) ); + else + ProcessPrimitives( lcl_CreateRectangleDelimiterPrimitives( aArea ) ); +} + +void SwColumnFrame::PaintSubsidiaryLines( const SwPageFrame *, + const SwRect & ) const +{ + const SwFrame* pLay = Lower(); + const SwFrame* pFootnoteCont = nullptr; + const SwFrame* pColBody = nullptr; + while ( pLay && !( pFootnoteCont && pColBody ) ) + { + if ( pLay->IsFootnoteContFrame( ) ) + pFootnoteCont = pLay; + if ( pLay->IsBodyFrame() ) + pColBody = pLay; + pLay = pLay->GetNext(); + } + + SwRect aArea( pColBody->getFrameArea() ); + + // #i3662# - enlarge top of column body frame's printing area + // in sections to top of section frame. + const bool bColInSection = GetUpper()->IsSctFrame(); + if ( bColInSection ) + { + if ( IsVertical() ) + aArea.Right( GetUpper()->getFrameArea().Right() ); + else + aArea.Top( GetUpper()->getFrameArea().Top() ); + } + + if ( pFootnoteCont ) + aArea.AddBottom( pFootnoteCont->getFrameArea().Bottom() - aArea.Bottom() ); + + ::SwAlignRect( aArea, gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut() ); + + if ( !gProp.pSGlobalShell->GetViewOptions()->IsViewMetaChars( ) ) + ProcessPrimitives( lcl_CreateColumnAreaDelimiterPrimitives( aArea ) ); + else + ProcessPrimitives( lcl_CreateRectangleDelimiterPrimitives( aArea ) ); +} + +void SwSectionFrame::PaintSubsidiaryLines( const SwPageFrame * pPage, + const SwRect & rRect ) const +{ + const bool bNoLowerColumn = !Lower() || !Lower()->IsColumnFrame(); + if ( bNoLowerColumn ) + { + SwLayoutFrame::PaintSubsidiaryLines( pPage, rRect ); + } +} + +/** + * The SwBodyFrame doesn't print any subsidiary line: it's bounds are painted + * either by the parent page or the parent column frame. + */ +void SwBodyFrame::PaintSubsidiaryLines( const SwPageFrame *, + const SwRect & ) const +{ +} + +void SwHeadFootFrame::PaintSubsidiaryLines( const SwPageFrame *, const SwRect & ) const +{ + if ( gProp.pSGlobalShell->IsHeaderFooterEdit() ) + { + SwRect aArea( getFramePrintArea() ); + aArea.Pos() += getFrameArea().Pos(); + if ( !gProp.pSGlobalShell->GetViewOptions()->IsViewMetaChars( ) ) + ProcessPrimitives( lcl_CreatePageAreaDelimiterPrimitives( aArea ) ); + else + ProcessPrimitives( lcl_CreateRectangleDelimiterPrimitives( aArea ) ); + } +} + +/** + * This method is overridden in order to have no subsidiary lines + * around the footnotes. + */ +void SwFootnoteFrame::PaintSubsidiaryLines( const SwPageFrame *, + const SwRect & ) const +{ +} + +/** + * This method is overridden in order to have no subsidiary lines + * around the footnotes containers. + */ +void SwFootnoteContFrame::PaintSubsidiaryLines( const SwPageFrame *, + const SwRect & ) const +{ +} + +void SwLayoutFrame::PaintSubsidiaryLines( const SwPageFrame *pPage, + const SwRect &rRect ) const +{ + bool bNewTableModel = false; + + // #i29550# + if ( IsTabFrame() || IsCellFrame() || IsRowFrame() ) + { + const SwTabFrame* pTabFrame = FindTabFrame(); + if ( pTabFrame->IsCollapsingBorders() ) + return; + + bNewTableModel = pTabFrame->GetTable()->IsNewModel(); + // in the new table model, we have an early return for all cell-related + // frames, except from non-covered table cells + if ( bNewTableModel ) + if ( IsTabFrame() || + IsRowFrame() || + ( IsCellFrame() && IsCoveredCell() ) ) + return; + } + + const bool bFlys = pPage->GetSortedObjs() != nullptr; + + const bool bCell = IsCellFrame(); + // #i3662# - use frame area for cells for section use also frame area + const bool bUseFrameArea = bCell || IsSctFrame(); + SwRect aOriginal( bUseFrameArea ? getFrameArea() : getFramePrintArea() ); + if ( !bUseFrameArea ) + aOriginal.Pos() += getFrameArea().Pos(); + + ::SwAlignRect( aOriginal, gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut() ); + + if ( !aOriginal.Overlaps( rRect ) ) + return; + + SwRect aOut( aOriginal ); + aOut.Intersection_( rRect ); + + const SwTwips nRight = aOut.Right(); + const SwTwips nBottom= aOut.Bottom(); + + const Point aRT( nRight, aOut.Top() ); + const Point aRB( nRight, nBottom ); + const Point aLB( aOut.Left(), nBottom ); + + SubColFlags nSubColor = ( bCell || IsRowFrame() ) + ? SubColFlags::Tab + : ( IsInSct() + ? SubColFlags::Sect + : ( IsInFly() ? SubColFlags::Fly : SubColFlags::Page ) ); + + // collect body, header, footer, footnote and section + // sub-lines in <pSpecSubsLine> array. + const bool bSpecialSublines = IsBodyFrame() || IsHeaderFrame() || IsFooterFrame() || + IsFootnoteFrame() || IsSctFrame(); + SwLineRects *const pUsedSubsLines = bSpecialSublines + ? gProp.pSSpecSubsLines.get() : gProp.pSSubsLines.get(); + + // NOTE: for cell frames only left and right (horizontal layout) respectively + // top and bottom (vertical layout) lines painted. + // NOTE2: this does not hold for the new table model!!! We paint the top border + // of each non-covered table cell. + const bool bVert = IsVertical(); + if ( bFlys ) + { + // add control for drawing left and right lines + if ( !bCell || bNewTableModel || !bVert ) + { + if ( aOriginal.Left() == aOut.Left() ) + ::lcl_RefreshLine( this, pPage, aOut.Pos(), aLB, nSubColor, pUsedSubsLines ); + // in vertical layout set page/column break at right + if ( aOriginal.Right() == nRight ) + ::lcl_RefreshLine( this, pPage, aRT, aRB, nSubColor, pUsedSubsLines ); + } + // adjust control for drawing top and bottom lines + if ( !bCell || bNewTableModel || bVert ) + { + if ( aOriginal.Top() == aOut.Top() ) + // in horizontal layout set page/column break at top + ::lcl_RefreshLine( this, pPage, aOut.Pos(), aRT, nSubColor, pUsedSubsLines ); + if ( aOriginal.Bottom() == nBottom ) + ::lcl_RefreshLine( this, pPage, aLB, aRB, nSubColor, + pUsedSubsLines ); + } + } + else + { + // add control for drawing left and right lines + if ( !bCell || bNewTableModel || !bVert ) + { + if ( aOriginal.Left() == aOut.Left() ) + { + const SwRect aRect( aOut.Pos(), aLB ); + pUsedSubsLines->AddLineRect( aRect, nullptr, + SvxBorderLineStyle::SOLID, nullptr, nSubColor, gProp ); + } + // in vertical layout set page/column break at right + if ( aOriginal.Right() == nRight ) + { + const SwRect aRect( aRT, aRB ); + pUsedSubsLines->AddLineRect( aRect, nullptr, + SvxBorderLineStyle::SOLID, nullptr, nSubColor, gProp ); + } + } + // adjust control for drawing top and bottom lines + if ( !bCell || bNewTableModel || bVert ) + { + if ( aOriginal.Top() == aOut.Top() ) + { + // in horizontal layout set page/column break at top + const SwRect aRect( aOut.Pos(), aRT ); + pUsedSubsLines->AddLineRect( aRect, nullptr, + SvxBorderLineStyle::SOLID, nullptr, nSubColor, gProp ); + } + if ( aOriginal.Bottom() == nBottom ) + { + const SwRect aRect( aLB, aRB ); + pUsedSubsLines->AddLineRect( aRect, nullptr, + SvxBorderLineStyle::SOLID, nullptr, nSubColor, gProp ); + } + } + } +} + +/** + * Refreshes all extra data (line breaks a.s.o) of the page. Basically only those objects + * are considered which horizontally overlap the Rect. + */ +void SwPageFrame::RefreshExtraData( const SwRect &rRect ) const +{ + const SwLineNumberInfo &rInfo = GetFormat()->GetDoc()->GetLineNumberInfo(); + bool bLineInFly = (rInfo.IsPaintLineNumbers() && rInfo.IsCountInFlys()) + || static_cast<sal_Int16>(SW_MOD()->GetRedlineMarkPos()) != text::HoriOrientation::NONE; + + SwRect aRect( rRect ); + ::SwAlignRect( aRect, gProp.pSGlobalShell, gProp.pSGlobalShell->GetOut() ); + if ( !aRect.HasArea() ) + return; + + SwLayoutFrame::RefreshExtraData( aRect ); + + if ( bLineInFly && GetSortedObjs() ) + for (SwAnchoredObject* pAnchoredObj : *GetSortedObjs()) + { + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + if ( pFly->getFrameArea().Top() <= aRect.Bottom() && + pFly->getFrameArea().Bottom() >= aRect.Top() ) + pFly->RefreshExtraData( aRect ); + } + } +} + +void SwLayoutFrame::RefreshExtraData( const SwRect &rRect ) const +{ + + const SwLineNumberInfo &rInfo = GetFormat()->GetDoc()->GetLineNumberInfo(); + bool bLineInBody = rInfo.IsPaintLineNumbers(), + bLineInFly = bLineInBody && rInfo.IsCountInFlys(), + bRedLine = static_cast<sal_Int16>(SW_MOD()->GetRedlineMarkPos())!=text::HoriOrientation::NONE; + + const SwContentFrame *pCnt = ContainsContent(); + while ( pCnt && IsAnLower( pCnt ) ) + { + if ( pCnt->IsTextFrame() && ( bRedLine || + ( !pCnt->IsInTab() && + ((bLineInBody && pCnt->IsInDocBody()) || + (bLineInFly && pCnt->IsInFly())) ) ) && + pCnt->getFrameArea().Top() <= rRect.Bottom() && + pCnt->getFrameArea().Bottom() >= rRect.Top() ) + { + static_cast<const SwTextFrame*>(pCnt)->PaintExtraData( rRect ); + } + if ( bLineInFly && pCnt->GetDrawObjs() ) + for (SwAnchoredObject* pAnchoredObj : *pCnt->GetDrawObjs()) + { + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + if ( pFly->IsFlyInContentFrame() && + pFly->getFrameArea().Top() <= rRect.Bottom() && + pFly->getFrameArea().Bottom() >= rRect.Top() ) + pFly->RefreshExtraData( rRect ); + } + } + pCnt = pCnt->GetNextContentFrame(); + } +} + +/** + * For #102450# + * Determine the color, that is respectively will be drawn as background + * for the page frame. + * Using existing method SwFrame::GetBackgroundBrush to determine the color + * that is set at the page frame respectively is parent. If none is found + * return the global retouche color + * + * @return Color + */ +Color SwPageFrame::GetDrawBackgroundColor() const +{ + const SvxBrushItem* pBrushItem; + std::optional<Color> xDummyColor; + SwRect aDummyRect; + drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes; + + if ( GetBackgroundBrush( aFillAttributes, pBrushItem, xDummyColor, aDummyRect, true, /*bConsiderTextBox=*/false) ) + { + if(aFillAttributes && aFillAttributes->isUsed()) + { + // let SdrAllFillAttributesHelper do the average color calculation + return Color(aFillAttributes->getAverageColor(aGlobalRetoucheColor.getBColor())); + } + else if(pBrushItem) + { + OUString referer; + SwViewShell * sh1 = getRootFrame()->GetCurrShell(); + if (sh1 != nullptr) { + SfxObjectShell * sh2 = sh1->GetDoc()->GetPersist(); + if (sh2 != nullptr && sh2->HasName()) { + referer = sh2->GetMedium()->GetName(); + } + } + const Graphic* pGraphic = pBrushItem->GetGraphic(referer); + + if(pGraphic) + { + // #29105# when a graphic is set, it may be possible to calculate a single + // color which looks good in all places of the graphic. Since it is + // planned to have text edit on the overlay one day and the fallback + // to aGlobalRetoucheColor returns something useful, just use that + // for now. + } + else + { + // not a graphic, use (hopefully) initialized color + return pBrushItem->GetColor(); + } + } + } + + return aGlobalRetoucheColor; +} + +/// create/return font used to paint the "empty page" string +const vcl::Font& SwPageFrame::GetEmptyPageFont() +{ + static vcl::Font aEmptyPgFont = []() + { + vcl::Font tmp; + tmp.SetFontSize( Size( 0, 80 * 20 )); // == 80 pt + tmp.SetWeight( WEIGHT_BOLD ); + tmp.SetStyleName(OUString()); + tmp.SetFamilyName("Helvetica"); + tmp.SetFamily( FAMILY_SWISS ); + tmp.SetTransparent( true ); + tmp.SetColor( COL_GRAY ); + return tmp; + }(); + + return aEmptyPgFont; +} + +/** + * Retouch for a section + * + * Retouch will only be done, if the Frame is the last one in his chain. + * The whole area of the upper which is located below the Frame will be + * cleared using PaintSwFrameBackground. + */ +void SwFrame::Retouch( const SwPageFrame * pPage, const SwRect &rRect ) const +{ + if ( gProp.bSFlyMetafile ) + return; + + OSL_ENSURE( GetUpper(), "Retouche try without Upper." ); + OSL_ENSURE( getRootFrame()->GetCurrShell() && gProp.pSGlobalShell->GetWin(), "Retouche on a printer?" ); + + SwRect aRetouche( GetUpper()->GetPaintArea() ); + aRetouche.Top( getFrameArea().Top() + getFrameArea().Height() ); + aRetouche.Intersection( gProp.pSGlobalShell->VisArea() ); + + if ( aRetouche.HasArea() ) + { + //Omit the passed Rect. To do this, we unfortunately need a region to + //cut out. + SwRegionRects aRegion( aRetouche ); + aRegion -= rRect; + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + + // #i16816# tagged pdf support + SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pSh->GetOut() ); + + for ( size_t i = 0; i < aRegion.size(); ++i ) + { + const SwRect &rRetouche = aRegion[i]; + + GetUpper()->PaintBaBo( rRetouche, pPage ); + + //Hell and Heaven need to be refreshed too. + //To avoid recursion my retouch flag needs to be reset first! + ResetRetouche(); + if ( rRetouche.HasArea() ) + { + const Color aPageBackgrdColor(pPage->GetDrawBackgroundColor()); + const IDocumentDrawModelAccess& rIDDMA = pSh->getIDocumentDrawModelAccess(); + // --> OD #i76669# + SwViewObjectContactRedirector aSwRedirector( *pSh ); + // <-- + + pSh->Imp()->PaintLayer( rIDDMA.GetHellId(), nullptr, + *pPage, rRetouche, &aPageBackgrdColor, + pPage->IsRightToLeft(), + &aSwRedirector ); + pSh->Imp()->PaintLayer( rIDDMA.GetHeavenId(), nullptr, + *pPage, rRetouche, &aPageBackgrdColor, + pPage->IsRightToLeft(), + &aSwRedirector ); + } + + SetRetouche(); + + //Because we leave all paint areas, we need to refresh the + //subsidiary lines. + pPage->RefreshSubsidiary( rRetouche ); + } + } + if ( SwViewShell::IsLstEndAction() ) + ResetRetouche(); +} + +/** + * Determine the background brush for the frame: + * the background brush is taken from it-self or from its parent (anchor/upper). + * Normally, the background brush is taken, which has no transparent color or + * which has a background graphic. But there are some special cases: + * (1) No background brush is taken from a page frame, if view option "IsPageBack" + * isn't set. + * (2) Background brush from an index section is taken under special conditions. + * In this case parameter <rpCol> is set to the index shading color. + * (3) New (OD 20.08.2002) - Background brush is taken, if on background drawing + * of the frame transparency is considered and its color is not "no fill"/"auto fill" + * + * Old description in German: + * Returns the Backgroundbrush for the area of the Frame. + * The Brush is defined by the Frame or by an upper, the first Brush is + * used. If no Brush is defined for a Frame, false is returned. + * + * @param rpBrush + * output parameter - constant reference pointer the found background brush + * + * @param rpFillStyle + * output parameter - constant reference pointer the found background fill style + * + * @param rpFillGradient + * output parameter - constant reference pointer the found background fill gradient + * + * @param rpCol + * output parameter - constant reference pointer to the color of the index shading + * set under special conditions, if background brush is taken from an index section. + * + * @param rOrigRect + * in-/output parameter - reference to the rectangle the background brush is + * considered for - adjusted to the frame, from which the background brush is + * taken. + * + * @parem bLowerMode + * input parameter - boolean indicating, if background brush should *not* be + * taken from parent. + * + * @param bConsiderTextBox + * consider the TextBox of this fly frame (if there is any) when determining + * the background color, useful for automatic font color. + * + * @return true, if a background brush for the frame is found + */ +bool SwFrame::GetBackgroundBrush( + drawinglayer::attribute::SdrAllFillAttributesHelperPtr& rFillAttributes, + const SvxBrushItem* & rpBrush, + std::optional<Color>& rxCol, + SwRect &rOrigRect, + bool bLowerMode, + bool bConsiderTextBox ) const +{ + const SwFrame *pFrame = this; + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const SwViewOption *pOpt = pSh->GetViewOptions(); + rpBrush = nullptr; + rxCol.reset(); + do + { + if ( pFrame->IsPageFrame() && !pOpt->IsPageBack() ) + return false; + + if (pFrame->supportsFullDrawingLayerFillAttributeSet()) + { + bool bHandledTextBox = false; + if (pFrame->IsFlyFrame() && bConsiderTextBox) + { + const SwFlyFrame* pFlyFrame = static_cast<const SwFlyFrame*>(pFrame); + SwFrameFormat* pShape + = SwTextBoxHelper::getOtherTextBoxFormat(pFlyFrame->GetFormat(), RES_FLYFRMFMT); + if (pShape) + { + SdrObject* pObject = pShape->FindRealSdrObject(); + if (pObject) + { + // Work with the fill attributes of the shape of the fly frame. + rFillAttributes = + std::make_shared<drawinglayer::attribute::SdrAllFillAttributesHelper>( + pObject->GetMergedItemSet()); + bHandledTextBox = true; + } + } + } + + if (!bHandledTextBox) + rFillAttributes = pFrame->getSdrAllFillAttributesHelper(); + } + const SvxBrushItem &rBack = pFrame->GetAttrSet()->GetBackground(); + + if( pFrame->IsSctFrame() ) + { + const SwSection* pSection = static_cast<const SwSectionFrame*>(pFrame)->GetSection(); + // Note: If frame <pFrame> is a section of the index and + // it its background color is "no fill"/"auto fill" and + // it has no background graphic and + // we are not in the page preview and + // we are not in read-only mode and + // option "index shadings" is set and + // the output is not the printer + // then set <rpCol> to the color of the index shading + if( pSection && ( SectionType::ToxHeader == pSection->GetType() || + SectionType::ToxContent == pSection->GetType() ) && + (rBack.GetColor() == COL_TRANSPARENT) && + rBack.GetGraphicPos() == GPOS_NONE && + !pOpt->IsPagePreview() && + !pOpt->IsReadonly() && + // #114856# Form view + !pOpt->IsFormView() && + SwViewOption::IsIndexShadings() && + !pOpt->IsPDFExport() && + pSh->GetOut()->GetOutDevType() != OUTDEV_PRINTER ) + { + rxCol = SwViewOption::GetIndexShadingsColor(); + } + } + + // determine, if background draw of frame <pFrame> considers transparency + // Status Quo: background transparency have to be + // considered for fly frames + const bool bConsiderBackgroundTransparency = pFrame->IsFlyFrame(); + + // #i125189# Do not base the decision for using the parent's fill style for this + // frame when the new DrawingLayer FillAttributes are used on the SdrAllFillAttributesHelper + // information. There the data is already optimized to no fill in the case that the + // transparence is at 100% while no fill is the criteria for derivation + bool bNewDrawingLayerFillStyleIsUsedAndNotNoFill(false); + + if(rFillAttributes) + { + // the new DrawingLayer FillStyle is used + if(rFillAttributes->isUsed()) + { + // it's not drawing::FillStyle_NONE + bNewDrawingLayerFillStyleIsUsedAndNotNoFill = true; + } + else + { + // maybe optimized already when 100% transparency is used somewhere, need to test + // XFillStyleItem directly from the model data + const drawing::FillStyle eFillStyle(pFrame->GetAttrSet()->Get(XATTR_FILLSTYLE).GetValue()); + + if(drawing::FillStyle_NONE != eFillStyle) + { + bNewDrawingLayerFillStyleIsUsedAndNotNoFill = true; + } + } + } + + // add condition: + // If <bConsiderBackgroundTransparency> is set - see above -, + // return brush of frame <pFrame>, if its color is *not* "no fill"/"auto fill" + if ( + // #i125189# Done when the new DrawingLayer FillAttributes are used and + // not drawing::FillStyle_NONE (see above) + bNewDrawingLayerFillStyleIsUsedAndNotNoFill || + + // done when SvxBrushItem is used + rBack.GetColor().GetAlpha() == 255 || rBack.GetGraphicPos() != GPOS_NONE || + + // done when direct color is forced + rxCol || + + // done when consider BG transparency and color is not completely transparent + (bConsiderBackgroundTransparency && (rBack.GetColor() != COL_TRANSPARENT)) + ) + { + rpBrush = &rBack; + if ( pFrame->IsPageFrame() && pSh->GetViewOptions()->getBrowseMode() ) + { + rOrigRect = pFrame->getFrameArea(); + ::SwAlignRect(rOrigRect, pSh, pSh->GetOut()); + } + else + { + if (pFrame->IsPageFrame() + && pFrame->GetAttrSet()->GetItem<SfxBoolItem>(RES_BACKGROUND_FULL_SIZE)->GetValue()) + { + rOrigRect = pFrame->getFrameArea(); + } + else if (pFrame->getFrameArea().SSize() != pFrame->getFramePrintArea().SSize()) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFrame ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + ::lcl_CalcBorderRect( rOrigRect, pFrame, rAttrs, false, gProp ); + } + else + { + rOrigRect = pFrame->getFramePrintArea(); + rOrigRect += pFrame->getFrameArea().Pos(); + } + } + + return true; + } + + if ( bLowerMode ) + { + // Do not try to get background brush from parent (anchor/upper) + return false; + } + + // get parent frame - anchor or upper - for next loop + if ( pFrame->IsFlyFrame() ) + { + pFrame = static_cast<const SwFlyFrame*>(pFrame)->GetAnchorFrame(); + } + else + { + pFrame = pFrame->GetUpper(); + } + } while ( pFrame ); + + return false; +} + +void SetOutDevAndWin( SwViewShell *pSh, OutputDevice *pO, + vcl::Window *pW, sal_uInt16 nZoom ) +{ + pSh->mpOut = pO; + pSh->mpWin = pW; + pSh->mpOpt->SetZoom( nZoom ); +} + +Graphic SwFrameFormat::MakeGraphic( ImageMap*, const sal_uInt32 /*nMaximumQuadraticPixels*/, const std::optional<Size>& /*rTargetDPI*/ ) +{ + return Graphic(); +} + +Graphic SwFlyFrameFormat::MakeGraphic( ImageMap* pMap, const sal_uInt32 /*nMaximumQuadraticPixels*/, const std::optional<Size>& /*rTargetDPI*/ ) +{ + Graphic aRet; + //search any Fly! + SwIterator<SwFrame,SwFormat> aIter( *this ); + SwFrame *pFirst = aIter.First(); + SwViewShell *const pSh = + pFirst ? pFirst->getRootFrame()->GetCurrShell() : nullptr; + if (nullptr != pSh) + { + SwViewShell *pOldGlobal = gProp.pSGlobalShell; + gProp.pSGlobalShell = pSh; + + bool bNoteURL = pMap && + SfxItemState::SET != GetAttrSet().GetItemState( RES_URL ); + if( bNoteURL ) + { + OSL_ENSURE( !pNoteURL, "MakeGraphic: pNoteURL already used? " ); + pNoteURL = new SwNoteURL; + } + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pFirst); + + OutputDevice *pOld = pSh->GetOut(); + ScopedVclPtrInstance< VirtualDevice > pDev( *pOld ); + pDev->EnableOutput( false ); + + GDIMetaFile aMet; + MapMode aMap( pOld->GetMapMode().GetMapUnit() ); + pDev->SetMapMode( aMap ); + aMet.SetPrefMapMode( aMap ); + + ::SwCalcPixStatics( pSh->GetOut() ); + aMet.SetPrefSize( pFly->getFrameArea().SSize() ); + + aMet.Record( pDev.get() ); + pDev->SetLineColor(); + pDev->SetFillColor(); + pDev->SetFont( pOld->GetFont() ); + + //Enlarge the rectangle if needed, so the border is painted too. + SwRect aOut( pFly->getFrameArea() ); + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFly ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + if ( rAttrs.CalcRightLine() ) + aOut.AddWidth(2*gProp.nSPixelSzW ); + if ( rAttrs.CalcBottomLine() ) + aOut.AddHeight(2*gProp.nSPixelSzH ); + + // #i92711# start Pre/PostPaint encapsulation before pOut is changed to the buffering VDev + const vcl::Region aRepaintRegion(aOut.SVRect()); + pSh->DLPrePaint2(aRepaintRegion); + + vcl::Window *pWin = pSh->GetWin(); + sal_uInt16 nZoom = pSh->GetViewOptions()->GetZoom(); + ::SetOutDevAndWin( pSh, pDev, nullptr, 100 ); + gProp.bSFlyMetafile = true; + gProp.pSFlyMetafileOut = pWin->GetOutDev(); + + SwViewShellImp *pImp = pSh->Imp(); + gProp.pSFlyOnlyDraw = pFly; + gProp.pSLines.reset(new SwLineRects); + + // determine page, fly frame is on + const SwPageFrame* pFlyPage = pFly->FindPageFrame(); + const Color aPageBackgrdColor(pFlyPage->GetDrawBackgroundColor()); + const IDocumentDrawModelAccess& rIDDMA = pSh->getIDocumentDrawModelAccess(); + // --> OD #i76669# + SwViewObjectContactRedirector aSwRedirector( *pSh ); + // <-- + pImp->PaintLayer( rIDDMA.GetHellId(), nullptr, + *pFlyPage, aOut, &aPageBackgrdColor, + pFlyPage->IsRightToLeft(), + &aSwRedirector ); + gProp.pSLines->PaintLines( pDev, gProp ); + if ( pFly->IsFlyInContentFrame() ) + pFly->PaintSwFrame( *pDev, aOut ); + gProp.pSLines->PaintLines( pDev, gProp ); + pImp->PaintLayer( rIDDMA.GetHeavenId(), nullptr, + *pFlyPage, aOut, &aPageBackgrdColor, + pFlyPage->IsRightToLeft(), + &aSwRedirector ); + gProp.pSLines->PaintLines( pDev, gProp ); + gProp.pSLines.reset(); + gProp.pSFlyOnlyDraw = nullptr; + + gProp.pSFlyMetafileOut = nullptr; + gProp.bSFlyMetafile = false; + ::SetOutDevAndWin( pSh, pOld, pWin, nZoom ); + + // #i92711# end Pre/PostPaint encapsulation when pOut is back and content is painted + pSh->DLPostPaint2(true); + + aMet.Stop(); + aMet.Move( -pFly->getFrameArea().Left(), -pFly->getFrameArea().Top() ); + aRet = Graphic( aMet ); + + if( bNoteURL ) + { + OSL_ENSURE( pNoteURL, "MakeGraphic: Good Bye, NoteURL." ); + delete pNoteURL; + pNoteURL = nullptr; + } + gProp.pSGlobalShell = pOldGlobal; + } + return aRet; +} + +Graphic SwDrawFrameFormat::MakeGraphic( ImageMap*, const sal_uInt32 nMaximumQuadraticPixels, const std::optional<Size>& rTargetDPI ) +{ + Graphic aRet; + SwDrawModel* pMod = getIDocumentDrawModelAccess().GetDrawModel(); + if ( pMod ) + { + SdrObject *pObj = FindSdrObject(); + SdrView aView( *pMod ); + SdrPageView *pPgView = aView.ShowSdrPage(aView.GetModel()->GetPage(0)); + aView.MarkObj( pObj, pPgView ); + aRet = aView.GetMarkedObjBitmapEx(/*bNoVDevIfOneBmpMarked=*/false, nMaximumQuadraticPixels, rTargetDPI); + aView.HideSdrPage(); + } + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/sectfrm.cxx b/sw/source/core/layout/sectfrm.cxx new file mode 100644 index 000000000..eb667dd51 --- /dev/null +++ b/sw/source/core/layout/sectfrm.cxx @@ -0,0 +1,2941 @@ +/* -*- 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 <config_wasm_strip.h> + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <svl/itemiter.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <fmtclbl.hxx> +#include <sectfrm.hxx> +#include <cellfrm.hxx> +#include <section.hxx> +#include <IDocumentSettingAccess.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <txtfrm.hxx> +#include <fmtclds.hxx> +#include <colfrm.hxx> +#include <tabfrm.hxx> +#include <ftnfrm.hxx> +#include <layouter.hxx> +#include <dbg_lay.hxx> +#include <viewopt.hxx> +#include <viewimp.hxx> +#include <editeng/brushitem.hxx> +#include <fmtftntx.hxx> +#include <flyfrm.hxx> +#include <sortedobjs.hxx> +#include <hints.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> + +namespace +{ +/** + * Performs the correct type of position invalidation depending on if we're in + * CalcContent(). + */ +void InvalidateFramePos(SwFrame* pFrame, bool bInCalcContent) +{ + if (bInCalcContent) + pFrame->InvalidatePos_(); + else + pFrame->InvalidatePos(); +} +} + +SwSectionFrame::SwSectionFrame( SwSection &rSect, SwFrame* pSib ) + : SwLayoutFrame( rSect.GetFormat(), pSib ) + , SwFlowFrame( static_cast<SwFrame&>(*this) ) + , m_pSection( &rSect ) + , m_bFootnoteAtEnd(false) + , m_bEndnAtEnd(false) + , m_bContentLock(false) + , m_bOwnFootnoteNum(false) + , m_bFootnoteLock(false) +{ + StartListening(rSect.GetFormat()->GetNotifier()); + + mnFrameType = SwFrameType::Section; + + CalcFootnoteAtEndFlag(); + CalcEndAtEndFlag(); +} + +SwSectionFrame::SwSectionFrame( SwSectionFrame &rSect, bool bMaster ) : + SwLayoutFrame( rSect.GetFormat(), rSect.getRootFrame() ), + SwFlowFrame( static_cast<SwFrame&>(*this) ), + m_pSection( rSect.GetSection() ), + m_bFootnoteAtEnd( rSect.IsFootnoteAtEnd() ), + m_bEndnAtEnd( rSect.IsEndnAtEnd() ), + m_bContentLock( false ), + m_bOwnFootnoteNum( false ), + m_bFootnoteLock( false ) +{ + StartListening(rSect.GetFormat()->GetNotifier()); + + mnFrameType = SwFrameType::Section; + + PROTOCOL( this, PROT::Section, bMaster ? DbgAction::CreateMaster : DbgAction::CreateFollow, &rSect ) + + if( bMaster ) + { + SwSectionFrame* pMaster = rSect.IsFollow() ? rSect.FindMaster() : nullptr; + if (pMaster) + pMaster->SetFollow( this ); + SetFollow( &rSect ); + } + else + { + SetFollow( rSect.GetFollow() ); + rSect.SetFollow( this ); + if( !GetFollow() ) + rSect.SimpleFormat(); + if( !rSect.IsColLocked() ) + rSect.InvalidateSize(); + } +} + +// NOTE: call <SwSectionFrame::Init()> directly after creation of a new section +// frame and its insert in the layout. +void SwSectionFrame::Init() +{ + assert(GetUpper() && "SwSectionFrame::Init before insertion?!"); + SwRectFnSet aRectFnSet(this); + tools::Long nWidth = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetWidth( aFrm, nWidth ); + aRectFnSet.SetHeight( aFrm, 0 ); + } + + // #109700# LRSpace for sections + const SvxLRSpaceItem& rLRSpace = GetFormat()->GetLRSpace(); + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetLeft( aPrt, rLRSpace.GetLeft() ); + aRectFnSet.SetWidth( aPrt, nWidth - rLRSpace.GetLeft() - rLRSpace.GetRight() ); + aRectFnSet.SetHeight( aPrt, 0 ); + } + + const SwFormatCol &rCol = GetFormat()->GetCol(); + if( ( rCol.GetNumCols() > 1 || IsAnyNoteAtEnd() ) && !IsInFootnote() ) + { + const SwFormatCol *pOld = Lower() ? &rCol : new SwFormatCol; + ChgColumns( *pOld, rCol, IsAnyNoteAtEnd() ); + if( pOld != &rCol ) + delete pOld; + } +} + +void SwSectionFrame::DestroyImpl() +{ + if( GetFormat() && !GetFormat()->GetDoc()->IsInDtor() ) + { + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame ) + pRootFrame->RemoveFromList( this ); + if( IsFollow() ) + { + SwSectionFrame *pMaster = FindMaster(); + if( pMaster ) + { + PROTOCOL( this, PROT::Section, DbgAction::DelFollow, pMaster ) + pMaster->SetFollow( GetFollow() ); + // A Master always grabs the space until the lower edge of his + // Upper. If he doesn't have a Follow anymore, he can + // release it, which is why the Size of the Master is + // invalidated. + if( !GetFollow() ) + pMaster->InvalidateSize(); + } + } +#if defined DBG_UTIL + else if( HasFollow() ) + { + PROTOCOL( this, PROT::Section, DbgAction::DelMaster, GetFollow() ) + } +#endif + } + + SwLayoutFrame::DestroyImpl(); +} + +SwSectionFrame::~SwSectionFrame() +{ +} + +void SwSectionFrame::DelEmpty( bool bRemove ) +{ + if( IsColLocked() ) + { + OSL_ENSURE( !bRemove, "Don't delete locked SectionFrames" ); + return; + } + SwFrame* pUp = GetUpper(); + if( pUp ) + { + // #i27138# + // notify accessibility paragraphs objects about changed + // CONTENT_FLOWS_FROM/_TO relation. + // Relation CONTENT_FLOWS_FROM for current next paragraph will change + // and relation CONTENT_FLOWS_TO for current previous paragraph will change. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + { + SwViewShell* pViewShell( getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + auto pNext = FindNextCnt( true ); + auto pPrev = FindPrevCnt(); + pViewShell->InvalidateAccessibleParaFlowRelation( + pNext ? pNext->DynCastTextFrame() : nullptr, + pPrev ? pPrev->DynCastTextFrame() : nullptr ); + } + } +#endif + Cut_( bRemove ); + } + SwSectionFrame *pMaster = IsFollow() ? FindMaster() : nullptr; + if (pMaster) + { + pMaster->SetFollow( GetFollow() ); + // A Master always grabs the space until the lower edge of his + // Upper. If he doesn't have a Follow anymore, he can + // release it, which is why the Size of the Master is + // invalidated. + if( !GetFollow() && !pMaster->IsColLocked() ) + pMaster->InvalidateSize(); + } + SetFollow(nullptr); + if( !pUp ) + return; + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( 0 ); + } + + // If we are destroyed immediately anyway, we don't need + // to put us into the list + if( bRemove ) + { // If we already were half dead before this DelEmpty, + // we are likely in the list and have to remove us from + // it + if( !m_pSection && getRootFrame() ) + getRootFrame()->RemoveFromList( this ); + } + else if( getRootFrame() ) + { + getRootFrame()->InsertEmptySct( this ); + } + + m_pSection = nullptr; // like this a reanimation is virtually impossible though +} + +void SwSectionFrame::Cut() +{ + Cut_( true ); +} + +void SwSectionFrame::Cut_( bool bRemove ) +{ + OSL_ENSURE( GetUpper(), "Cut without Upper()." ); + + PROTOCOL( this, PROT::Cut, DbgAction::NONE, GetUpper() ) + + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + SwFrame *pFrame = GetNext(); + SwFrame* pPrepFrame = nullptr; + while( pFrame && pFrame->IsSctFrame() && !static_cast<SwSectionFrame*>(pFrame)->GetSection() ) + pFrame = pFrame->GetNext(); + if( pFrame ) + { // The former successor might have calculated a gap to the predecessor + // which is now obsolete since he becomes the first + pFrame->InvalidatePrt_(); + pFrame->InvalidatePos_(); + if( pFrame->IsSctFrame() ) + pFrame = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if ( pFrame && pFrame->IsContentFrame() ) + { + pFrame->InvalidatePage( pPage ); + if( IsInFootnote() && !GetIndPrev() ) + pPrepFrame = pFrame; + } + } + else + { + InvalidateNextPos(); + // Someone has to take over the retouching: predecessor or Upper + pFrame = GetPrev(); + if ( nullptr != pFrame ) + { + pFrame->SetRetouche(); + pFrame->Prepare( PrepareHint::WidowsOrphans ); + if ( pFrame->IsContentFrame() ) + pFrame->InvalidatePage( pPage ); + } + // If I am (was) the only FlowFrame in my Upper, then he has to take over + // the retouching. + // Furthermore a blank page could have emerged + else + { SwRootFrame *pRoot = static_cast<SwRootFrame*>(pPage->GetUpper()); + pRoot->SetSuperfluous(); + GetUpper()->SetCompletePaint(); + } + } + // First remove, then shrink Upper + SwLayoutFrame *pUp = GetUpper(); + if( bRemove ) + { + RemoveFromLayout(); + if( pUp && !pUp->Lower() && pUp->IsFootnoteFrame() && !pUp->IsColLocked() && + pUp->GetUpper() ) + { + pUp->Cut(); + SwFrame::DestroyFrame(pUp); + pUp = nullptr; + } + } + if( pPrepFrame ) + pPrepFrame->Prepare( PrepareHint::FootnoteInvalidation ); + if ( !pUp ) + return; + + SwRectFnSet aRectFnSet(this); + SwTwips nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if( nFrameHeight <= 0 ) + return; + + if( !bRemove ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, 0 ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, 0 ); + } + + pUp->Shrink( nFrameHeight ); +} + +void SwSectionFrame::Paste( SwFrame* pParent, SwFrame* pSibling ) +{ + OSL_ENSURE( pParent, "No parent for Paste()." ); + OSL_ENSURE( pParent->IsLayoutFrame(), "Parent is ContentFrame." ); + OSL_ENSURE( pParent != this, "I'm my own parent." ); + OSL_ENSURE( pSibling != this, "I'm my own neighbour." ); + OSL_ENSURE( !GetPrev() && !GetUpper(), + "I am still registered somewhere." ); + + PROTOCOL( this, PROT::Paste, DbgAction::NONE, GetUpper() ) + + // Add to the tree + SwSectionFrame* pSect = pParent->FindSctFrame(); + // Assure that parent is not inside a table frame, which is inside the found section frame. + if ( pSect ) + { + SwTabFrame* pTableFrame = pParent->FindTabFrame(); + if ( pTableFrame && + pSect->IsAnLower( pTableFrame ) ) + { + pSect = nullptr; + } + } + + SwRectFnSet aRectFnSet(pParent); + if( pSect && HasToBreak( pSect ) ) + { + if( pParent->IsColBodyFrame() ) // dealing with a single-column area + { + // If we are coincidentally at the end of a column, pSibling + // has to point to the first frame of the next column in order + // for the content of the next column to be moved correctly to the + // newly created pSect by the InsertGroup + SwColumnFrame *pCol = static_cast<SwColumnFrame*>(pParent->GetUpper()); + while( !pSibling && nullptr != ( pCol = static_cast<SwColumnFrame*>(pCol->GetNext()) ) ) + pSibling = static_cast<SwLayoutFrame*>(pCol->Lower())->Lower(); + if( pSibling ) + { + // Even worse: every following column content has to + // be attached to the pSibling-chain in order to be + // taken along + SwFrame *pTmp = pSibling; + while ( nullptr != ( pCol = static_cast<SwColumnFrame*>(pCol->GetNext()) ) ) + { + while ( pTmp->GetNext() ) + pTmp = pTmp->GetNext(); + SwFrame* pSave = ::SaveContent( pCol ); + if (pSave) + ::RestoreContent( pSave, pSibling->GetUpper(), pTmp ); + } + } + } + pParent = pSect; + pSect = new SwSectionFrame( *static_cast<SwSectionFrame*>(pParent)->GetSection(), pParent ); + // if pParent is decomposed into two parts, its Follow has to be attached + // to the new second part + pSect->SetFollow( static_cast<SwSectionFrame*>(pParent)->GetFollow() ); + static_cast<SwSectionFrame*>(pParent)->SetFollow( nullptr ); + if( pSect->GetFollow() ) + pParent->InvalidateSize_(); + + const bool bInserted = InsertGroupBefore( pParent, pSibling, pSect ); + if (bInserted) + { + pSect->Init(); + aRectFnSet.MakePos( *pSect, pSect->GetUpper(), pSect->GetPrev(), true); + } + if( !static_cast<SwLayoutFrame*>(pParent)->Lower() ) + { + SwSectionFrame::MoveContentAndDelete( static_cast<SwSectionFrame*>(pParent), false ); + pParent = this; + } + } + else + InsertGroupBefore( pParent, pSibling, nullptr ); + + InvalidateAll_(); + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + + if ( pSibling ) + { + pSibling->InvalidatePos_(); + pSibling->InvalidatePrt_(); + if ( pSibling->IsContentFrame() ) + pSibling->InvalidatePage( pPage ); + } + + SwTwips nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if( nFrameHeight ) + pParent->Grow( nFrameHeight ); + + if ( GetPrev() && !IsFollow() ) + { + GetPrev()->InvalidateSize(); + if ( GetPrev()->IsContentFrame() ) + GetPrev()->InvalidatePage( pPage ); + } +} + +/** +|* Here it's decided whether the this-SectionFrame should break up +|* the passed (Section)frm (or not). +|* Initially, all superior sections are broken up. Later on that could +|* be made configurable. +|*/ +bool SwSectionFrame::HasToBreak( const SwFrame* pFrame ) const +{ + if( !pFrame->IsSctFrame() ) + return false; + + const SwSectionFormat *pTmp = static_cast<const SwSectionFormat*>(GetFormat()); + + const SwFrameFormat *pOtherFormat = static_cast<const SwSectionFrame*>(pFrame)->GetFormat(); + do + { + pTmp = pTmp->GetParent(); + if( !pTmp ) + return false; + if( pTmp == pOtherFormat ) + return true; + } while( true ); // ( pTmp->GetSect().GetValue() ); +} + +/** +|* Merges two SectionFrames, in case it's about the same section. +|* This can be necessary when a (sub)section is deleted that had +|* divided another part into two. +|*/ +void SwSectionFrame::MergeNext( SwSectionFrame* pNxt ) +{ + if (pNxt->IsDeleteForbidden()) + return; + + if (pNxt->IsJoinLocked() || GetSection() != pNxt->GetSection()) + return; + + PROTOCOL( this, PROT::Section, DbgAction::Merge, pNxt ) + + SwFrame* pTmp = ::SaveContent( pNxt ); + if( pTmp ) + { + SwFrame* pLast = Lower(); + SwLayoutFrame* pLay = this; + if( pLast ) + { + while( pLast->GetNext() ) + pLast = pLast->GetNext(); + if( pLast->IsColumnFrame() ) + { // Columns now with BodyFrame + pLay = static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(pLast)->Lower()); + pLast = pLay->Lower(); + if( pLast ) + while( pLast->GetNext() ) + pLast = pLast->GetNext(); + } + } + ::RestoreContent( pTmp, pLay, pLast ); + } + SetFollow( pNxt->GetFollow() ); + pNxt->SetFollow( nullptr ); + pNxt->Cut(); + SwFrame::DestroyFrame(pNxt); + InvalidateSize(); +} + +/** +|* Divides a SectionFrame into two parts. The second one starts with the +|* passed frame. +|* This is required when inserting an inner section, because the MoveFwd +|* cannot have the desired effect within a frame or a table cell. +|*/ +bool SwSectionFrame::SplitSect( SwFrame* pFrame, bool bApres ) +{ + assert(pFrame && "SplitSect: Why?"); + SwFrame* pOther = bApres ? pFrame->FindNext() : pFrame->FindPrev(); + if( !pOther ) + return false; + SwSectionFrame* pSect = pOther->FindSctFrame(); + if( pSect != this ) + return false; + // Put the content aside + SwFrame* pSav = ::SaveContent( this, bApres ? pOther : pFrame ); + OSL_ENSURE( pSav, "SplitSect: What's on?" ); + if( pSav ) // be robust + { // Create a new SctFrame, not as a Follower/master + SwSectionFrame* pNew = new SwSectionFrame( *pSect->GetSection(), pSect ); + pNew->InsertBehind( pSect->GetUpper(), pSect ); + pNew->Init(); + SwRectFnSet aRectFnSet(this); + aRectFnSet.MakePos( *pNew, nullptr, pSect, true ); + // OD 25.03.2003 #108339# - restore content: + // determine layout frame for restoring content after the initialization + // of the section frame. In the section initialization the columns are + // created. + { + SwLayoutFrame* pLay = pNew; + // Search for last layout frame, e.g. for columned sections. + while( pLay->Lower() && pLay->Lower()->IsLayoutFrame() ) + pLay = static_cast<SwLayoutFrame*>(pLay->Lower()); + ::RestoreContent( pSav, pLay, nullptr ); + } + InvalidateSize_(); + if( HasFollow() ) + { + pNew->SetFollow( GetFollow() ); + SetFollow( nullptr ); + } + return true; + } + return false; +} + +/** +|* MoveContent is called for destroying a SectionFrames, due to +|* the cancellation or hiding of a section, to handle the content. +|* If the SectionFrame hasn't broken up another one, then the content +|* is moved to the Upper. Otherwise the content is moved to another +|* SectionFrame, which has to be potentially merged. +|*/ +// If a multi-column section is cancelled, the ContentFrames have to be +// invalidated +static void lcl_InvalidateInfFlags( SwFrame* pFrame, bool bInva ) +{ + while ( pFrame ) + { + pFrame->InvalidateInfFlags(); + if( bInva ) + { + pFrame->InvalidatePos_(); + pFrame->InvalidateSize_(); + pFrame->InvalidatePrt_(); + } + if( pFrame->IsLayoutFrame() ) + lcl_InvalidateInfFlags( static_cast<SwLayoutFrame*>(pFrame)->GetLower(), false ); + pFrame = pFrame->GetNext(); + } +} + +// Works like SwContentFrame::ImplGetNextContentFrame, but starts with a LayoutFrame +static SwContentFrame* lcl_GetNextContentFrame( const SwLayoutFrame* pLay, bool bFwd ) +{ + if ( bFwd ) + { + if ( pLay->GetNext() && pLay->GetNext()->IsContentFrame() ) + return const_cast<SwContentFrame*>(static_cast<const SwContentFrame*>(pLay->GetNext())); + } + else + { + if ( pLay->GetPrev() && pLay->GetPrev()->IsContentFrame() ) + return const_cast<SwContentFrame*>(static_cast<const SwContentFrame*>(pLay->GetPrev())); + } + + const SwFrame* pFrame = pLay; + SwContentFrame *pContentFrame = nullptr; + bool bGoingUp = true; + do { + const SwFrame *p = nullptr; + bool bGoingFwdOrBwd = false; + + bool bGoingDown = !bGoingUp && pFrame->IsLayoutFrame(); + if (bGoingDown) + { + p = static_cast<const SwLayoutFrame*>(pFrame)->Lower(); + bGoingDown = nullptr != p; + } + if ( !bGoingDown ) + { + p = pFrame->IsFlyFrame() ? + ( bFwd ? static_cast<const SwFlyFrame*>(pFrame)->GetNextLink() : static_cast<const SwFlyFrame*>(pFrame)->GetPrevLink() ) : + ( bFwd ? pFrame->GetNext() :pFrame->GetPrev() ); + bGoingFwdOrBwd = nullptr != p; + if ( !bGoingFwdOrBwd ) + { + p = pFrame->GetUpper(); + bGoingUp = nullptr != p; + if ( !bGoingUp ) + return nullptr; + } + } + + bGoingUp = !( bGoingFwdOrBwd || bGoingDown ); + assert(p); + if (!bFwd && bGoingDown) + while ( p->GetNext() ) + p = p->GetNext(); + + pFrame = p; + } while ( nullptr == (pContentFrame = (pFrame->IsContentFrame() ? const_cast<SwContentFrame*>(static_cast<const SwContentFrame*>(pFrame)) : nullptr) )); + + return pContentFrame; +} + +namespace +{ + SwLayoutFrame* FirstLeaf(SwSectionFrame* pLayFrame) + { + if (pLayFrame->Lower() && pLayFrame->Lower()->IsColumnFrame()) + return pLayFrame->GetNextLayoutLeaf(); + return pLayFrame; + } + + /// Checks if pFrame has a parent that can contain a split section frame. + bool CanContainSplitSection(const SwFrame* pFrame) + { + if (!pFrame->IsInTab()) + return true; + + // The frame is in a table, see if the table is in a section. + bool bRet = !pFrame->FindTabFrame()->IsInSct(); + + if (bRet) + { + // Don't try to split if the frame itself is a section frame with + // multiple columns. + if (pFrame->IsSctFrame()) + { + const SwFrame* pLower = pFrame->GetLower(); + if (pLower && pLower->IsColumnFrame()) + bRet = false; + } + } + + return bRet; + } +} + +void SwSectionFrame::MoveContentAndDelete( SwSectionFrame* pDel, bool bSave ) +{ + bool bSize = pDel->Lower() && pDel->Lower()->IsColumnFrame(); + SwFrame* pPrv = pDel->GetPrev(); + SwLayoutFrame* pUp = pDel->GetUpper(); + // OD 27.03.2003 #i12711# - initialize local pointer variables. + SwSectionFrame* pPrvSct = nullptr; + SwSectionFrame* pNxtSct = nullptr; + SwSectionFormat* pParent = static_cast<SwSectionFormat*>(pDel->GetFormat())->GetParent(); + if( pDel->IsInTab() && pParent ) + { + SwTabFrame *pTab = pDel->FindTabFrame(); + // If we are within a table, we can only have broken up sections that + // are inside as well, but not a section that contains the whole table. + if( pTab->IsInSct() && pParent == pTab->FindSctFrame()->GetFormat() ) + pParent = nullptr; + } + // If our Format has a parent, we have probably broken up another + // SectionFrame, which has to be checked. To do so we first acquire the + // succeeding and the preceding ContentFrame, let's see if they + // lay in the SectionFrames. + // OD 27.03.2003 #i12711# - check, if previous and next section belonging + // together and can be joined, *not* only if deleted section contains content. + if ( pParent ) + { + SwFrame* pPrvContent = lcl_GetNextContentFrame( pDel, false ); + pPrvSct = pPrvContent ? pPrvContent->FindSctFrame() : nullptr; + SwFrame* pNxtContent = lcl_GetNextContentFrame( pDel, true ); + pNxtSct = pNxtContent ? pNxtContent->FindSctFrame() : nullptr; + } + else + { + pParent = nullptr; + pPrvSct = pNxtSct = nullptr; + } + + // Now the content is put aside and the frame is destroyed + SwFrame *pSave = bSave ? ::SaveContent( pDel ) : nullptr; + bool bOldFootnote = true; + if( pSave && pUp->IsFootnoteFrame() ) + { + bOldFootnote = static_cast<SwFootnoteFrame*>(pUp)->IsColLocked(); + static_cast<SwFootnoteFrame*>(pUp)->ColLock(); + } + pDel->DelEmpty( true ); + SwFrame::DestroyFrame(pDel); + if( pParent ) + { // Search for the appropriate insert position + if( pNxtSct && pNxtSct->GetFormat() == pParent ) + { // Here we can insert ourselves at the beginning + pUp = FirstLeaf( pNxtSct ); + pPrv = nullptr; + if( pPrvSct && ( pPrvSct->GetFormat() != pParent ) ) + pPrvSct = nullptr; // In order that nothing is merged + } + else if( pPrvSct && pPrvSct->GetFormat() == pParent ) + { // Wonderful, here we can insert ourselves at the end + pUp = pPrvSct; + if( pUp->Lower() && pUp->Lower()->IsColumnFrame() ) + { + pUp = static_cast<SwLayoutFrame*>(pUp->GetLastLower()); + // The body of the last column + pUp = static_cast<SwLayoutFrame*>(pUp->Lower()); + } + // In order to perform the insertion after the last one + pPrv = pUp->GetLastLower(); + pPrvSct = nullptr; // Such that nothing is merged + } + else + { + if( pSave ) + { // Following situations: before and after the section-to-be + // deleted there is the section boundary of the enclosing + // section, or another (sibling) section connects subsequently, + // that derives from the same Parent. + // In that case, there's not (yet) a part of our parent available + // that can store the content, so we create it here. + pPrvSct = new SwSectionFrame( *pParent->GetSection(), pUp ); + pPrvSct->InsertBehind( pUp, pPrv ); + pPrvSct->Init(); + SwRectFnSet aRectFnSet(pUp); + aRectFnSet.MakePos( *pPrvSct, pUp, pPrv, true ); + pUp = FirstLeaf( pPrvSct ); + pPrv = nullptr; + } + pPrvSct = nullptr; // Such that nothing will be merged + } + } + // The content is going to be inserted... + if( pSave ) + { + lcl_InvalidateInfFlags( pSave, bSize ); + ::RestoreContent( pSave, pUp, pPrv ); + pUp->FindPageFrame()->InvalidateContent(); + if( !bOldFootnote ) + static_cast<SwFootnoteFrame*>(pUp)->ColUnlock(); + } + // Now two parts of the superior section could possibly be merged + if( pPrvSct && !pPrvSct->IsJoinLocked() ) + { + OSL_ENSURE( pNxtSct, "MoveContent: No Merge" ); + pPrvSct->MergeNext( pNxtSct ); + } +} + +void SwSectionFrame::MakeAll(vcl::RenderContext* pRenderContext) +{ + if ( IsJoinLocked() || IsColLocked() || StackHack::IsLocked() || StackHack::Count() > 50 ) + return; + if( !m_pSection ) // Via DelEmpty + { +#ifdef DBG_UTIL + OSL_ENSURE( getRootFrame()->IsInDelList( this ), "SectionFrame without Section" ); +#endif + if( !isFrameAreaPositionValid() ) + { + if( GetUpper() ) + { + SwRectFnSet aRectFnSet(GetUpper()); + aRectFnSet.MakePos( *this, GetUpper(), GetPrev(), false ); + } + + if (getFrameArea().Height() == 0) + { + // SwLayoutFrame::MakeAll() is not called for to-be-deleted + // section frames (which would invalidate the position of the + // next frame via the SwLayNotify dtor), so call it manually. + if (SwFrame* pNext = GetNext()) + pNext->InvalidatePos(); + } + } + + setFrameAreaPositionValid(true); + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + return; + } + LockJoin(); // I don't let myself to be destroyed on the way + + while( GetNext() && GetNext() == GetFollow() ) + { + const SwFrame* pFoll = GetFollow(); + MergeNext( static_cast<SwSectionFrame*>(GetNext()) ); + if( pFoll == GetFollow() ) + break; + } + + // OD 2004-03-15 #116561# - In online layout join the follows, if section + // can grow. + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + + // Split sections inside table cells: need to merge all follows of the + // section here, as later we won't attempt doing so. + bool bCanContainSplitSection = false; + if (IsInTab() && GetUpper()) + bCanContainSplitSection = CanContainSplitSection(GetUpper()); + + if( pSh && (pSh->GetViewOptions()->getBrowseMode() || bCanContainSplitSection) && + ( Grow( LONG_MAX, true ) > 0 ) ) + { + while( GetFollow() ) + { + const SwFrame* pFoll = GetFollow(); + MergeNext( GetFollow() ); + if( pFoll == GetFollow() ) + break; + } + } + + // A section with Follow uses all the space until the lower edge of the + // Upper. If it moves, its size can grow or decrease... + if( !isFrameAreaPositionValid() && ToMaximize( false ) ) + { + setFrameAreaSizeValid(false); + } + + SwLayoutFrame::MakeAll(getRootFrame()->GetCurrShell()->GetOut()); + + if (IsInTab()) + { + // In case the section is in a table, then calculate the lower right + // now. Just setting the valid size flag of the lower to false may not + // be enough, as lcl_RecalcRow() can call + // SwFrame::ValidateThisAndAllLowers(), and then we don't attempt + // calculating the proper position of the lower. + SwFrame* pLower = Lower(); + if (pLower && !pLower->isFrameAreaPositionValid()) + pLower->Calc(pRenderContext); + } + + UnlockJoin(); + if( m_pSection && IsSuperfluous() ) + DelEmpty( false ); +} + +bool SwSectionFrame::ShouldBwdMoved( SwLayoutFrame *, bool & ) +{ + OSL_FAIL( "Oops, where is my tinfoil hat?" ); + return false; +} + +const SwSectionFormat* SwSectionFrame::GetEndSectFormat_() const +{ + const SwSectionFormat *pFormat = m_pSection->GetFormat(); + while( !pFormat->GetEndAtTextEnd().IsAtEnd() ) + { + if( auto pNewFormat = dynamic_cast< const SwSectionFormat *>( pFormat->GetRegisteredIn()) ) + pFormat = pNewFormat; + else + return nullptr; + } + return pFormat; +} + +static void lcl_FindContentFrame( SwContentFrame* &rpContentFrame, SwFootnoteFrame* &rpFootnoteFrame, + SwFrame* pFrame, bool &rbChkFootnote ) +{ + if( !pFrame ) + return; + + while( pFrame->GetNext() ) + pFrame = pFrame->GetNext(); + while( !rpContentFrame && pFrame ) + { + if( pFrame->IsContentFrame() ) + rpContentFrame = static_cast<SwContentFrame*>(pFrame); + else if( pFrame->IsLayoutFrame() ) + { + if( pFrame->IsFootnoteFrame() ) + { + if( rbChkFootnote ) + { + rpFootnoteFrame = static_cast<SwFootnoteFrame*>(pFrame); + rbChkFootnote = rpFootnoteFrame->GetAttr()->GetFootnote().IsEndNote(); + } + } + else + lcl_FindContentFrame( rpContentFrame, rpFootnoteFrame, + static_cast<SwLayoutFrame*>(pFrame)->Lower(), rbChkFootnote ); + } + pFrame = pFrame->GetPrev(); + } +} + +SwContentFrame *SwSectionFrame::FindLastContent( SwFindMode nMode ) +{ + SwContentFrame *pRet = nullptr; + SwFootnoteFrame *pFootnoteFrame = nullptr; + SwSectionFrame *pSect = this; + if( nMode != SwFindMode::None ) + { + const SwSectionFormat *pFormat = IsEndnAtEnd() ? GetEndSectFormat() : + m_pSection->GetFormat(); + do { + while( pSect->HasFollow() ) + pSect = pSect->GetFollow(); + SwFrame* pTmp = pSect->FindNext(); + while( pTmp && pTmp->IsSctFrame() && + !static_cast<SwSectionFrame*>(pTmp)->GetSection() ) + pTmp = pTmp->FindNext(); + if( pTmp && pTmp->IsSctFrame() && + static_cast<SwSectionFrame*>(pTmp)->IsDescendantFrom( pFormat ) ) + pSect = static_cast<SwSectionFrame*>(pTmp); + else + break; + } while( true ); + } + bool bFootnoteFound = nMode == SwFindMode::EndNote; + do + { + lcl_FindContentFrame( pRet, pFootnoteFrame, pSect->Lower(), bFootnoteFound ); + if( pRet || !pSect->IsFollow() || nMode == SwFindMode::None || + ( SwFindMode::MyLast == nMode && this == pSect ) ) + break; + pSect = pSect->FindMaster(); + } while( pSect ); + if( ( nMode == SwFindMode::EndNote ) && pFootnoteFrame ) + pRet = pFootnoteFrame->ContainsContent(); + return pRet; +} + +bool SwSectionFrame::CalcMinDiff( SwTwips& rMinDiff ) const +{ + if( ToMaximize( true ) ) + { + SwRectFnSet aRectFnSet(this); + rMinDiff = aRectFnSet.GetPrtBottom(*GetUpper()); + rMinDiff = aRectFnSet.BottomDist( getFrameArea(), rMinDiff ); + return true; + } + return false; +} + +/** + * CollectEndnotes looks for endnotes in the sectionfrm and his follows, + * the endnotes will cut off the layout and put into the array. + * If the first endnote is not a master-SwFootnoteFrame, the whole sectionfrm + * contains only endnotes and it is not necessary to collect them. + */ +static SwFootnoteFrame* lcl_FindEndnote( SwSectionFrame* &rpSect, bool &rbEmpty, + SwLayouter *pLayouter ) +{ + // if rEmpty is set, the rpSect is already searched + SwSectionFrame* pSect = rbEmpty ? rpSect->GetFollow() : rpSect; + while( pSect ) + { + OSL_ENSURE( (pSect->Lower() && pSect->Lower()->IsColumnFrame()) || pSect->GetUpper()->IsFootnoteFrame(), + "InsertEndnotes: Where's my column?" ); + + // i73332: Columned section in endnote + SwColumnFrame* pCol = nullptr; + if(pSect->Lower() && pSect->Lower()->IsColumnFrame()) + pCol = static_cast<SwColumnFrame*>(pSect->Lower()); + + while( pCol ) // check all columns + { + SwFootnoteContFrame* pFootnoteCont = pCol->FindFootnoteCont(); + if( pFootnoteCont ) + { + SwFootnoteFrame* pRet = static_cast<SwFootnoteFrame*>(pFootnoteCont->Lower()); + while( pRet ) // look for endnotes + { + /* CollectEndNode can destroy pRet so we need to get the + next early + */ + SwFootnoteFrame* pRetNext = static_cast<SwFootnoteFrame*>(pRet->GetNext()); + if( pRet->GetAttr()->GetFootnote().IsEndNote() ) + { + if( pRet->GetMaster() ) + { + if( pLayouter ) + pLayouter->CollectEndnote( pRet ); + else + return nullptr; + } + else + return pRet; // Found + } + pRet = pRetNext; + } + } + pCol = static_cast<SwColumnFrame*>(pCol->GetNext()); + } + rpSect = pSect; + pSect = pLayouter ? pSect->GetFollow() : nullptr; + rbEmpty = true; + } + return nullptr; +} + +static void lcl_ColumnRefresh( SwSectionFrame* pSect, bool bFollow ) +{ + vcl::RenderContext* pRenderContext = pSect->getRootFrame()->GetCurrShell()->GetOut(); + while( pSect ) + { + bool bOldLock = pSect->IsColLocked(); + pSect->ColLock(); + if( pSect->Lower() && pSect->Lower()->IsColumnFrame() ) + { + SwColumnFrame *pCol = static_cast<SwColumnFrame*>(pSect->Lower()); + do + { pCol->InvalidateSize_(); + pCol->InvalidatePos_(); + static_cast<SwLayoutFrame*>(pCol)->Lower()->InvalidateSize_(); + pCol->Calc(pRenderContext); // calculation of column and + static_cast<SwLayoutFrame*>(pCol)->Lower()->Calc(pRenderContext); // body + pCol = static_cast<SwColumnFrame*>(pCol->GetNext()); + } while ( pCol ); + } + if( !bOldLock ) + pSect->ColUnlock(); + if( bFollow ) + pSect = pSect->GetFollow(); + else + pSect = nullptr; + } +} + +void SwSectionFrame::CollectEndnotes( SwLayouter* pLayouter ) +{ + OSL_ENSURE( IsColLocked(), "CollectEndnotes: You love the risk?" ); + // i73332: Section in footnode does not have columns! + OSL_ENSURE( (Lower() && Lower()->IsColumnFrame()) || GetUpper()->IsFootnoteFrame(), "Where's my column?" ); + + SwSectionFrame* pSect = this; + SwFootnoteFrame* pFootnote; + bool bEmpty = false; + // pSect is the last sectionfrm without endnotes or the this-pointer + // the first sectionfrm with endnotes may be destroyed, when the endnotes + // is cutted + while( nullptr != (pFootnote = lcl_FindEndnote( pSect, bEmpty, pLayouter )) ) + pLayouter->CollectEndnote( pFootnote ); + if( pLayouter->HasEndnotes() ) + lcl_ColumnRefresh( this, true ); +} + +/** Fits the size to the surroundings. +|* +|* Those that have a Follow or foot notes, have to extend until +|* the lower edge of a upper (bMaximize) +|* They must not extend above the Upper, as the case may be one can +|* try to grow its upper (bGrow) +|* If the size had to be changed, the content is calculated. +|* +|* @note: perform calculation of content, only if height has changed (OD 18.09.2002 #100522#) +|*/ +void SwSectionFrame::CheckClipping( bool bGrow, bool bMaximize ) +{ + SwRectFnSet aRectFnSet(this); + tools::Long nDiff; + SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*GetUpper()); + if( bGrow && ( !IsInFly() || !GetUpper()->IsColBodyFrame() || + !FindFlyFrame()->IsLocked() ) ) + { + nDiff = -aRectFnSet.BottomDist( getFrameArea(), nDeadLine ); + if( !bMaximize ) + nDiff += Undersize(); + if( nDiff > 0 ) + { + tools::Long nAdd = GetUpper()->Grow( nDiff ); + if( aRectFnSet.IsVert() ) + nDeadLine -= nAdd; + else + nDeadLine += nAdd; + } + } + nDiff = -aRectFnSet.BottomDist( getFrameArea(), nDeadLine ); + SetUndersized( !bMaximize && nDiff >= 0 ); + const bool bCalc = ( IsUndersized() || bMaximize ) && + ( nDiff || + aRectFnSet.GetTop(getFramePrintArea()) > aRectFnSet.GetHeight(getFrameArea()) ); + // OD 03.11.2003 #i19737# - introduce local variable <bExtraCalc> to indicate + // that a calculation has to be done beside the value of <bCalc>. + bool bExtraCalc = false; + if( !bCalc && !bGrow && IsAnyNoteAtEnd() && !IsInFootnote() ) + { + SwSectionFrame *pSect = this; + bool bEmpty = false; + SwLayoutFrame* pFootnote = IsEndnAtEnd() ? + lcl_FindEndnote( pSect, bEmpty, nullptr ) : nullptr; + if( pFootnote ) + { + pFootnote = pFootnote->FindFootnoteBossFrame(); + SwFrame* pTmp = FindLastContent( SwFindMode::LastCnt ); + // OD 08.11.2002 #104840# - use <SwLayoutFrame::IsBefore(..)> + if ( pTmp && pFootnote->IsBefore( pTmp->FindFootnoteBossFrame() ) ) + bExtraCalc = true; + } + else if( GetFollow() && !GetFollow()->ContainsAny() ) + bExtraCalc = true; + } + if ( !(bCalc || bExtraCalc) ) + return; + + nDiff = aRectFnSet.YDiff( nDeadLine, aRectFnSet.GetTop(getFrameArea()) ); + if( nDiff < 0 ) + nDeadLine = aRectFnSet.GetTop(getFrameArea()); + const Size aOldSz( getFramePrintArea().SSize() ); + tools::Long nTop = aRectFnSet.GetTopMargin(*this); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetBottom( aFrm, nDeadLine ); + } + + nDiff = aRectFnSet.GetHeight(getFrameArea()); + if( nTop > nDiff ) + nTop = nDiff; + aRectFnSet.SetYMargins( *this, nTop, 0 ); + + // OD 18.09.2002 #100522# + // Determine, if height has changed. + // Note: In vertical layout the height equals the width value. + bool bHeightChanged = aRectFnSet.IsVert() ? + (aOldSz.Width() != getFramePrintArea().Width()) : + (aOldSz.Height() != getFramePrintArea().Height()); + // Last but not least we have changed the height again, thus the inner + // layout (columns) is calculated and the content as well. + // OD 18.09.2002 #100522# + // calculate content, only if height has changed. + // OD 03.11.2003 #i19737# - restriction of content calculation too strong. + // If an endnote has an incorrect position or a follow section contains + // no content except footnotes/endnotes, the content has also been calculated. + if ( !(( bHeightChanged || bExtraCalc ) && Lower()) ) + return; + + if( Lower()->IsColumnFrame() ) + { + lcl_ColumnRefresh( this, false ); + ::CalcContent( this ); + } + else + { + ChgLowersProp( aOldSz ); + if( !bMaximize && !IsContentLocked() ) + ::CalcContent( this ); + } +} + +void SwSectionFrame::SimpleFormat() +{ + if ( IsJoinLocked() || IsColLocked() ) + return; + LockJoin(); + SwRectFnSet aRectFnSet(this); + if( GetPrev() || GetUpper() ) + { + // assure notifications on position changes. + const SwLayNotify aNotify( this ); + aRectFnSet.MakePos( *this, GetUpper(), GetPrev(), false ); + setFrameAreaPositionValid(true); + } + SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*GetUpper()); + // OD 22.10.2002 #97265# - call always method <lcl_ColumnRefresh(..)>, in + // order to get calculated lowers, not only if there space left in its upper. + if( aRectFnSet.BottomDist( getFrameArea(), nDeadLine ) >= 0 ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetBottom( aFrm, nDeadLine ); + } + + tools::Long nHeight = aRectFnSet.GetHeight(getFrameArea()); + tools::Long nTop = CalcUpperSpace(); + if( nTop > nHeight ) + nTop = nHeight; + aRectFnSet.SetYMargins( *this, nTop, 0 ); + } + lcl_ColumnRefresh( this, false ); + UnlockJoin(); +} + +namespace { + +// #i40147# - helper class to perform extra section format +// to position anchored objects and to keep the position of whose objects locked. +class ExtraFormatToPositionObjs +{ + private: + SwSectionFrame* mpSectFrame; + bool mbExtraFormatPerformed; + + public: + explicit ExtraFormatToPositionObjs( SwSectionFrame& _rSectFrame) + : mpSectFrame( &_rSectFrame ), + mbExtraFormatPerformed( false ) + {} + + ~ExtraFormatToPositionObjs() + { + if ( !mbExtraFormatPerformed ) + return; + + // release keep locked position of lower floating screen objects + SwPageFrame* pPageFrame = mpSectFrame->FindPageFrame(); + SwSortedObjs* pObjs = pPageFrame ? pPageFrame->GetSortedObjs() : nullptr; + if ( pObjs ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( mpSectFrame->IsAnLower( pAnchoredObj->GetAnchorFrame() ) ) + { + pAnchoredObj->SetKeepPosLocked( false ); + } + } + } + } + + // #i81555# + void InitObjs( SwFrame& rFrame ) + { + SwSortedObjs* pObjs = rFrame.GetDrawObjs(); + if ( pObjs ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + pAnchoredObj->UnlockPosition(); + pAnchoredObj->SetClearedEnvironment( false ); + } + } + if ( rFrame.IsLayoutFrame() ) + { + SwFrame* pLowerFrame = rFrame.GetLower(); + while ( pLowerFrame != nullptr ) + { + InitObjs( *pLowerFrame ); + + pLowerFrame = pLowerFrame->GetNext(); + } + } + } + + void FormatSectionToPositionObjs() + { + vcl::RenderContext* pRenderContext = mpSectFrame->getRootFrame()->GetCurrShell()->GetOut(); + // perform extra format for multi-columned section. + if ( !(mpSectFrame->Lower() && mpSectFrame->Lower()->IsColumnFrame() && + mpSectFrame->Lower()->GetNext()) ) + return; + + // grow section till bottom of printing area of upper frame + SwRectFnSet aRectFnSet(mpSectFrame); + SwTwips nTopMargin = aRectFnSet.GetTopMargin(*mpSectFrame); + Size aOldSectPrtSize( mpSectFrame->getFramePrintArea().SSize() ); + SwTwips nDiff = aRectFnSet.BottomDist( mpSectFrame->getFrameArea(), aRectFnSet.GetPrtBottom(*mpSectFrame->GetUpper()) ); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*mpSectFrame); + aRectFnSet.AddBottom( aFrm, nDiff ); + } + + aRectFnSet.SetYMargins( *mpSectFrame, nTopMargin, 0 ); + // #i59789# + // suppress formatting, if printing area of section is too narrow + if ( aRectFnSet.GetHeight(mpSectFrame->getFramePrintArea()) <= 0 ) + { + return; + } + mpSectFrame->ChgLowersProp( aOldSectPrtSize ); + + // format column frames and its body and footnote container + SwColumnFrame* pColFrame = static_cast<SwColumnFrame*>(mpSectFrame->Lower()); + while ( pColFrame ) + { + pColFrame->Calc(pRenderContext); + pColFrame->Lower()->Calc(pRenderContext); + if ( pColFrame->Lower()->GetNext() ) + { + pColFrame->Lower()->GetNext()->Calc(pRenderContext); + } + + pColFrame = static_cast<SwColumnFrame*>(pColFrame->GetNext()); + } + + // unlock position of lower floating screen objects for the extra format + // #i81555# + // Section frame can already have changed the page and its content + // can still be on the former page. + // Thus, initialize objects via lower-relationship + InitObjs( *mpSectFrame ); + + // format content - first with collecting its foot-/endnotes before content + // format, second without collecting its foot-/endnotes. + ::CalcContent( mpSectFrame ); + ::CalcContent( mpSectFrame, true ); + + // keep locked position of lower floating screen objects + SwPageFrame* pPageFrame = mpSectFrame->FindPageFrame(); + SwSortedObjs* pObjs = pPageFrame ? pPageFrame->GetSortedObjs() : nullptr; + if ( pObjs ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( mpSectFrame->IsAnLower( pAnchoredObj->GetAnchorFrame() ) ) + { + pAnchoredObj->SetKeepPosLocked( true ); + } + } + } + + mbExtraFormatPerformed = true; + + } +}; + +} + +/// "formats" the frame; Frame and PrtArea +void SwSectionFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs *pAttr ) +{ + if( !m_pSection ) // via DelEmpty + { +#ifdef DBG_UTIL + OSL_ENSURE( getRootFrame()->IsInDelList( this ), "SectionFrame without Section" ); +#endif + setFrameAreaPositionValid(true); + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + return; + } + + SwRectFnSet aRectFnSet(this); + + if ( !isFramePrintAreaValid() ) + { + PROTOCOL( this, PROT::PrintArea, DbgAction::NONE, nullptr ) + setFramePrintAreaValid(true); + SwTwips nUpper = CalcUpperSpace(); + + // #109700# LRSpace for sections + const SvxLRSpaceItem& rLRSpace = GetFormat()->GetLRSpace(); + aRectFnSet.SetXMargins( *this, rLRSpace.GetLeft(), rLRSpace.GetRight() ); + + if( nUpper != aRectFnSet.GetTopMargin(*this) ) + { + setFrameAreaSizeValid(false); + SwFrame* pOwn = ContainsAny(); + if( pOwn ) + pOwn->InvalidatePos_(); + } + aRectFnSet.SetYMargins( *this, nUpper, 0 ); + } + + if ( isFrameAreaSizeValid() ) + return; + + PROTOCOL_ENTER( this, PROT::Size, DbgAction::NONE, nullptr ) + const tools::Long nOldHeight = aRectFnSet.GetHeight(getFrameArea()); + bool bOldLock = IsColLocked(); + ColLock(); + + setFrameAreaSizeValid(true); + + // The size is only determined by the content, if the SectFrame does not have a + // Follow. Otherwise it fills (occupies) the Upper down to the lower edge. + // It is not responsible for the text flow, but the content is. + bool bMaximize = ToMaximize( false ); + + // OD 2004-05-17 #i28701# - If the wrapping style has to be considered + // on object positioning, an extra formatting has to be performed + // to determine the correct positions the floating screen objects. + // #i40147# + // use new helper class <ExtraFormatToPositionObjs>. + // This class additionally keep the locked position of the objects + // and releases this position lock keeping on destruction. + ExtraFormatToPositionObjs aExtraFormatToPosObjs( *this ); + if ( !bMaximize && + GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) && + !GetFormat()->GetBalancedColumns().GetValue() ) + { + aExtraFormatToPosObjs.FormatSectionToPositionObjs(); + } + + // Column widths have to be adjusted before calling CheckClipping. + // CheckClipping can cause the formatting of the lower frames + // which still have a width of 0. + const bool bHasColumns = Lower() && Lower()->IsColumnFrame(); + if ( bHasColumns && Lower()->GetNext() ) + AdjustColumns( nullptr, false ); + + if( GetUpper() ) + { + const tools::Long nWidth = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetWidth( aFrm, nWidth ); + } + + // #109700# LRSpace for sections + { + const SvxLRSpaceItem& rLRSpace = GetFormat()->GetLRSpace(); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetWidth( aPrt, nWidth - rLRSpace.GetLeft() - rLRSpace.GetRight() ); + } + + // OD 15.10.2002 #103517# - allow grow in online layout + // Thus, set <..IsBrowseMode()> as parameter <bGrow> on calling + // method <CheckClipping(..)>. + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + CheckClipping( pSh && pSh->GetViewOptions()->getBrowseMode(), bMaximize ); + bMaximize = ToMaximize( false ); + setFrameAreaSizeValid(true); + } + + // Check the width of the columns and adjust if necessary + if ( bHasColumns && ! Lower()->GetNext() && bMaximize ) + static_cast<SwColumnFrame*>(Lower())->Lower()->Calc(pRenderContext); + + if ( !bMaximize ) + { + SwTwips nRemaining = aRectFnSet.GetTopMargin(*this); + SwFrame *pFrame = m_pLower; + if( pFrame ) + { + if( pFrame->IsColumnFrame() && pFrame->GetNext() ) + { + // #i61435# + // suppress formatting, if upper frame has height <= 0 + if ( aRectFnSet.GetHeight(GetUpper()->getFrameArea()) > 0 ) + { + FormatWidthCols( *pAttr, nRemaining, MINLAY ); + } + // #126020# - adjust check for empty section + // #130797# - correct fix #126020# + while( HasFollow() && !GetFollow()->ContainsContent() && + !GetFollow()->ContainsAny( true ) ) + { + SwFrame* pOld = GetFollow(); + GetFollow()->DelEmpty( false ); + if( pOld == GetFollow() ) + break; + } + bMaximize = ToMaximize( false ); + nRemaining += aRectFnSet.GetHeight(pFrame->getFrameArea()); + } + else + { + if( pFrame->IsColumnFrame() ) + { + pFrame->Calc(pRenderContext); + pFrame = static_cast<SwColumnFrame*>(pFrame)->Lower(); + pFrame->Calc(pRenderContext); + pFrame = static_cast<SwLayoutFrame*>(pFrame)->Lower(); + CalcFootnoteContent(); + } + // If we are in a columned frame which calls a CalcContent + // in the FormatWidthCols, the content might need calculating + if( pFrame && !pFrame->isFrameAreaDefinitionValid() && IsInFly() && + FindFlyFrame()->IsColLocked() ) + ::CalcContent( this ); + nRemaining += InnerHeight(); + bMaximize = HasFollow(); + } + } + + SwTwips nDiff = aRectFnSet.GetHeight(getFrameArea()) - nRemaining; + if( nDiff < 0) + { + SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*GetUpper()); + { + tools::Long nBottom = aRectFnSet.GetBottom(getFrameArea()); + nBottom = aRectFnSet.YInc( nBottom, -nDiff ); + tools::Long nTmpDiff = aRectFnSet.YDiff( nBottom, nDeadLine ); + if( nTmpDiff > 0 ) + { + nTmpDiff = GetUpper()->Grow( nTmpDiff, true ); + nDeadLine = aRectFnSet.YInc( nDeadLine, nTmpDiff ); + nTmpDiff = aRectFnSet.YDiff( nBottom, nDeadLine ); + if( nTmpDiff > 0 ) + nDiff += nTmpDiff; + if( nDiff > 0 ) + nDiff = 0; + } + } + } + if( nDiff ) + { + tools::Long nTmp = nRemaining - aRectFnSet.GetHeight(getFrameArea()); + tools::Long nTop = aRectFnSet.GetTopMargin(*this); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nTmp ); + } + + aRectFnSet.SetYMargins( *this, nTop, 0 ); + InvalidateNextPos(); + + if (m_pLower && (!m_pLower->IsColumnFrame() || !m_pLower->GetNext())) + { + // If a single-column section just created the space that + // was requested by the "undersized" paragraphs, then they + // have to be invalidated and calculated, so they fully cover it + pFrame = m_pLower; + if( pFrame->IsColumnFrame() ) + { + pFrame->InvalidateSize_(); + pFrame->InvalidatePos_(); + pFrame->Calc(pRenderContext); + pFrame = static_cast<SwColumnFrame*>(pFrame)->Lower(); + pFrame->Calc(pRenderContext); + pFrame = static_cast<SwLayoutFrame*>(pFrame)->Lower(); + CalcFootnoteContent(); + } + bool bUnderSz = false; + while( pFrame ) + { + if( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsUndersized() ) + { + pFrame->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + bUnderSz = true; + } + pFrame = pFrame->GetNext(); + } + if( bUnderSz && !IsContentLocked() ) + ::CalcContent( this ); + } + } + } + + // Do not exceed the lower edge of the Upper. + // Do not extend below the lower edge with Sections with Follows + if ( GetUpper() ) + CheckClipping( true, bMaximize ); + if( !bOldLock ) + ColUnlock(); + tools::Long nDiff = nOldHeight - aRectFnSet.GetHeight(getFrameArea()); + + if( nDiff > 0 ) + { + if( !GetNext() ) + SetRetouche(); // Take over the retouching ourselves + if( GetUpper() && !GetUpper()->IsFooterFrame() ) + GetUpper()->Shrink( nDiff ); + } + + if( IsUndersized() ) + { + setFramePrintAreaValid(true); + } + +} + +/// Returns the next layout sheet where the frame can be moved in. +/// New pages are created only if specified by the parameter. +SwLayoutFrame *SwFrame::GetNextSctLeaf( MakePageType eMakePage ) +{ + // Attention: Nested sections are currently not supported + + PROTOCOL_ENTER( this, PROT::Leaf, DbgAction::NextSect, GetUpper()->FindSctFrame() ) + + // Shortcuts for "columned" sections, if we're not in the last column + // Can we slide to the next column of the section? + if( IsColBodyFrame() && GetUpper()->GetNext() ) + return static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(GetUpper()->GetNext())->Lower()); + if( GetUpper()->IsColBodyFrame() && GetUpper()->GetUpper()->GetNext() ) + return static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(GetUpper()->GetUpper()->GetNext())->Lower()); + // Inside a table-in-section, or sections of headers/footers, there can be only + // one column shift be made, one of the above shortcuts should have applied! + if( !CanContainSplitSection(GetUpper()) || FindFooterOrHeader() ) + return nullptr; + + SwSectionFrame *pSect = FindSctFrame(); + bool bWrongPage = false; + assert(pSect && "GetNextSctLeaf: Missing SectionFrame"); + + // Shortcut for sections with Follows. That's ok, + // if no columns or pages (except dummy pages) lie in between. + // In case of linked frames and in footnotes the shortcut would get + // even more costly + if( pSect->HasFollow() && pSect->IsInDocBody() && !pSect->IsInTab() ) + { + if( pSect->GetFollow() == pSect->GetNext() ) + { + SwPageFrame *pPg = pSect->GetFollow()->FindPageFrame(); + if( WrongPageDesc( pPg ) ) + bWrongPage = true; + else + return FirstLeaf( pSect->GetFollow() ); + } + else + { + SwFrame* pTmp; + if( !pSect->GetUpper()->IsColBodyFrame() || + nullptr == ( pTmp = pSect->GetUpper()->GetUpper()->GetNext() ) ) + pTmp = pSect->FindPageFrame()->GetNext(); + if( pTmp ) // is now the next column or page + { + SwFrame* pTmpX = pTmp; + if( pTmp->IsPageFrame() && static_cast<SwPageFrame*>(pTmp)->IsEmptyPage() ) + pTmp = pTmp->GetNext(); // skip dummy pages + SwFrame *pUp = pSect->GetFollow()->GetUpper(); + // pUp becomes the next column if the Follow lies in a column + // that is not a "not first" one, otherwise the page + if( !pUp->IsColBodyFrame() || + !( pUp = pUp->GetUpper() )->GetPrev() ) + pUp = pUp->FindPageFrame(); + // Now pUp and pTmp have to be the same page/column, otherwise + // pages or columns lie between Master and Follow + if( pUp == pTmp || pUp->GetNext() == pTmpX ) + { + SwPageFrame* pNxtPg = pUp->IsPageFrame() ? + static_cast<SwPageFrame*>(pUp) : pUp->FindPageFrame(); + if( WrongPageDesc( pNxtPg ) ) + bWrongPage = true; + else + return FirstLeaf( pSect->GetFollow() ); + } + } + } + } + +#ifndef NDEBUG + std::vector<SwFrame *> parents; + for (SwFrame * pTmp = GetUpper(); pTmp && !pTmp->IsPageFrame(); pTmp = pTmp->GetUpper()) + { + parents.push_back(pTmp); + } +#endif + + // Always end up in the same section: Body again inside Body etc. + const bool bBody = IsInDocBody(); + const bool bFootnotePage = FindPageFrame()->IsFootnotePage(); + + // The "pLayLeaf is in a table" case is rejected by default, so that it + // can't happen that we try to move a table to one of its own cells. + bool bLayLeafTableAllowed = false; + SwLayoutFrame *pLayLeaf; + + SwLayoutFrame* pCellLeaf = nullptr; + if (GetUpper()->IsInTab()) + { + if (IsTabFrame()) + { + return nullptr; // table in section in table: split disabled for now + } + // We are *in* a table (not an outermost SwTabFrame), see if there + // is a follow cell frame created already. + pCellLeaf = GetNextCellLeaf(); + if (!pCellLeaf) + { + SAL_WARN("sw.layout", "section is in table, but the table is not split"); + return nullptr; + } + } + + // A shortcut for TabFrames such that not all cells need to be visited + if( bWrongPage ) + pLayLeaf = nullptr; + else if( IsTabFrame() ) + { + SwFrame *const pTmpCnt = static_cast<SwTabFrame*>(this)->FindLastContentOrTable(); + pLayLeaf = pTmpCnt ? pTmpCnt->GetUpper() : nullptr; + } + else if (pCellLeaf && CanContainSplitSection(this)) + { + // This frame is in a table-not-in-section, its follow should be + // inserted under the follow of the frame's cell. + pLayLeaf = pCellLeaf; + if (pLayLeaf->FindTabFrame() == FindTabFrame()) + SAL_WARN("sw.layout", "my table frame and my follow's table frame is the same"); + // In this case pLayLeaf pointing to an in-table frame is OK. + bLayLeafTableAllowed = true; + } + else + { + pLayLeaf = GetNextLayoutLeaf(); + if( IsColumnFrame() ) + { + while( pLayLeaf && static_cast<SwColumnFrame*>(this)->IsAnLower( pLayLeaf ) ) + pLayLeaf = pLayLeaf->GetNextLayoutLeaf(); + } + } + + SwLayoutFrame *pOldLayLeaf = nullptr; // Such that in case of newly + // created pages, the search is + // not started over at the beginning + + while( true ) + { + if( pLayLeaf ) + { + // A layout leaf was found, let's see whether it can store me or + // another SectionFrame can be inserted here, or we have to continue + // searching + SwPageFrame* pNxtPg = pLayLeaf->FindPageFrame(); + if ( !bFootnotePage && pNxtPg->IsFootnotePage() ) + { // If I reached the end note pages it's over + pLayLeaf = nullptr; + continue; + } + // Once inBody always inBody, don't step into tables-in-sections and not into other sections + if ( (bBody && !pLayLeaf->IsInDocBody()) || + (IsInFootnote() != pLayLeaf->IsInFootnote() ) || + (pLayLeaf->IsInTab() && !bLayLeafTableAllowed) || + ( pLayLeaf->IsInSct() && ( !pSect->HasFollow() + || pSect->GetFollow() != pLayLeaf->FindSctFrame() ) ) ) + { + // Rejected - try again. + pOldLayLeaf = pLayLeaf; + pLayLeaf = pLayLeaf->GetNextLayoutLeaf(); + continue; + } + // Page desc is never wrong in case of sections-in-tables: in that + // case pLayLeaf points to our section's cell's follow, which is + // fine to be on the same page. New page creation is handled when + // creating / moving the cell frame. + if( WrongPageDesc( pNxtPg ) && !bLayLeafTableAllowed ) + { + if( bWrongPage ) + break; // there's a column between me and my right page + pLayLeaf = nullptr; + bWrongPage = true; + pOldLayLeaf = nullptr; + continue; + } + } + // There is no further LayoutFrame that fits, so a new page + // has to be created, although new pages are worthless within a frame + else if( !pSect->IsInFly() && + ( eMakePage == MAKEPAGE_APPEND || eMakePage == MAKEPAGE_INSERT ) ) + { + InsertPage(pOldLayLeaf ? pOldLayLeaf->FindPageFrame() : FindPageFrame(), + false ); + // and again the whole thing + if (pCellLeaf && CanContainSplitSection(this)) + // GetNextLayoutLeaf() would refer to the next cell in the same + // row, avoid that. pCellLeaf points to the correct cell in the + // follow table, and in the next round it'll be used, as we now + // have a next page. + pLayLeaf = pCellLeaf; + else + pLayLeaf = pOldLayLeaf ? pOldLayLeaf : GetNextLayoutLeaf(); + continue; + } + break; + } + + if( pLayLeaf ) + { + // We have found the suitable layout sheet. If there (in the sheet) is + // already a Follow of our section, we take its first layout sheet, + // otherwise it is time to create a section follow + SwSectionFrame* pNew = nullptr; + + // This can be omitted if existing Follows were cut short + SwFrame* pFirst = pLayLeaf->Lower(); + // Here SectionFrames that are to be deleted must be ignored + while( pFirst && pFirst->IsSctFrame() && !static_cast<SwSectionFrame*>(pFirst)->GetSection() ) + pFirst = pFirst->GetNext(); + if( pFirst && pFirst->IsSctFrame() && pSect->GetFollow() == pFirst ) + pNew = pSect->GetFollow(); + else if( MAKEPAGE_NOSECTION == eMakePage ) + return pLayLeaf; + else if (pSect->GetSection()) + { + pNew = new SwSectionFrame( *pSect, false ); + pNew->InsertBefore( pLayLeaf, pLayLeaf->Lower() ); + pNew->Init(); + SwRectFnSet aRectFnSet(pNew); + aRectFnSet.MakePos( *pNew, pLayLeaf, nullptr, true ); + +#ifndef NDEBUG + { // sanity check the parents of the new frame vs. the old frame + SwFrame * pTmp = pNew; + auto iter(parents.begin()); + if (parents.size() >= 2 && + parents[0]->IsBodyFrame() && parents[1]->IsColumnFrame()) + { // this only inserts section frame - remove column + assert(parents[2]->IsSctFrame() || IsSctFrame()); + if (parents[2]->IsSctFrame()) + std::advance(iter, +2); + else + pTmp = pTmp->GetUpper(); + } + else if (IsBodyFrame() && parents.size() >= 1 + && parents[0]->IsColumnFrame()) + { // same as above, special case: "this" is the body frame + assert(parents[1]->IsSctFrame()); + std::advance(iter, +1); + } + else if (IsSctFrame()) // special case: "this" is the section + { + pTmp = pTmp->GetUpper(); + } + + for ( ; iter != parents.end(); ++iter) + { + if (pTmp->IsPageFrame()) + { + if ((*iter)->IsColumnFrame() && + (iter + 1) != parents.end() && (*(iter + 1))->IsBodyFrame()) + { // page style has columns - evidently these are + break; // added later? + } + assert(!pTmp->IsPageFrame()); + } + assert(pTmp->GetType() == (*iter)->GetType()); + // for cell frames and table frames: + // 1) there may be multiple follow frames of the old one + // 2) the new frame may be identical to the old one + // (not sure if this is allowed, but it happens now + // for the outer table of a nested table) + if (pTmp->IsCellFrame()) + { + SwCellFrame const*const pNewF(static_cast<SwCellFrame*>(pTmp)); + SwCellFrame const*const pOldF(static_cast<SwCellFrame*>(*iter)); + bool bFollowFound(false); + for (SwCellFrame const* pOldIter = pOldF; + pOldIter; pOldIter = pOldIter->GetFollowCell()) + { + if (pOldIter == pNewF) + { + bFollowFound = true; + break; + } + } + assert(bFollowFound); + } + else if (pTmp->IsFlowFrame()) + { + SwFlowFrame const*const pNewF(SwFlowFrame::CastFlowFrame(pTmp)); + SwFlowFrame const*const pOldF(SwFlowFrame::CastFlowFrame(*iter)); + bool bFollowFound(false); + for (SwFlowFrame const* pOldIter = pOldF; + pOldIter; pOldIter = pOldIter->GetFollow()) + { + if (pOldIter == pNewF) + { + bFollowFound = true; + break; + } + } + assert(bFollowFound); + } + pTmp = pTmp->GetUpper(); + } + assert(pTmp == nullptr /* SwFlyAtContentFrame case */ + || pTmp->IsPageFrame() // usual case + // the new page has columns, but the old page did not + || (pTmp->IsColumnFrame() && pTmp->GetUpper()->IsBodyFrame() + && pTmp->GetUpper()->GetUpper()->IsPageFrame())); + } +#endif + + // If our section frame has a successor then that has to be + // moved behind the new Follow of the section frames + SwFrame* pTmp = pSect->GetNext(); + if( pTmp && pTmp != pSect->GetFollow() ) + { + SwFlowFrame* pNxt; + SwContentFrame* pNxtContent = nullptr; + if( pTmp->IsContentFrame() ) + { + pNxt = static_cast<SwContentFrame*>(pTmp); + pNxtContent = static_cast<SwContentFrame*>(pTmp); + } + else + { + pNxtContent = static_cast<SwLayoutFrame*>(pTmp)->ContainsContent(); + if( pTmp->IsSctFrame() ) + pNxt = static_cast<SwSectionFrame*>(pTmp); + else + { + assert(pTmp->IsTabFrame()); + pNxt = static_cast<SwTabFrame*>(pTmp); + } + while( !pNxtContent && nullptr != ( pTmp = pTmp->GetNext() ) ) + { + if( pTmp->IsContentFrame() ) + pNxtContent = static_cast<SwContentFrame*>(pTmp); + else + pNxtContent = static_cast<SwLayoutFrame*>(pTmp)->ContainsContent(); + } + } + if( pNxtContent ) + { + SwFootnoteBossFrame* pOldBoss = pSect->FindFootnoteBossFrame( true ); + if( pOldBoss == pNxtContent->FindFootnoteBossFrame( true ) ) + { + SwSaveFootnoteHeight aHeight( pOldBoss, + pOldBoss->getFrameArea().Top() + pOldBoss->getFrameArea().Height() ); + pSect->GetUpper()->MoveLowerFootnotes( pNxtContent, pOldBoss, + pLayLeaf->FindFootnoteBossFrame( true ), false ); + } + } + pNxt->MoveSubTree( pLayLeaf, pNew->GetNext() ); + } + if( pNew->GetFollow() ) + pNew->SimpleFormat(); + } + // The wanted layout sheet is now the first of the determined SctFrames: + pLayLeaf = pNew ? FirstLeaf(pNew) : nullptr; + } + return pLayLeaf; +} + +/// Returns the preceding layout sheet where the frame can be moved into +SwLayoutFrame *SwFrame::GetPrevSctLeaf() +{ + PROTOCOL_ENTER( this, PROT::Leaf, DbgAction::PrevSect, GetUpper()->FindSctFrame() ) + + SwLayoutFrame* pCol; + // ColumnFrame always contain a BodyFrame now + if( IsColBodyFrame() ) + pCol = GetUpper(); + else if( GetUpper()->IsColBodyFrame() ) + pCol = GetUpper()->GetUpper(); + else + pCol = nullptr; + bool bJump = false; + if( pCol ) + { + if( pCol->GetPrev() ) + { + do + { + pCol = static_cast<SwLayoutFrame*>(pCol->GetPrev()); + // Is there any content? + if( static_cast<SwLayoutFrame*>(pCol->Lower())->Lower() ) + { + if( bJump ) // Did we skip a blank page? + SwFlowFrame::SetMoveBwdJump( true ); + return static_cast<SwLayoutFrame*>(pCol->Lower()); // The columnm body + } + bJump = true; + } while( pCol->GetPrev() ); + + // We get here when all columns are empty, pCol is now the + // first column, we need the body though + pCol = static_cast<SwLayoutFrame*>(pCol->Lower()); + } + else + pCol = nullptr; + } + + if( bJump ) // Did we skip a blank page? + SwFlowFrame::SetMoveBwdJump( true ); + + SwSectionFrame *pSect = FindSctFrame(); + if (!pCol && pSect && IsInTab() && CanContainSplitSection(this)) + { + // We don't have a previous section yet, and we're in a + // section-in-table. + if (SwFlowFrame* pPrecede = pSect->GetPrecede()) + { + // Our section has a precede, work with that. + if (pPrecede->GetFrame().IsLayoutFrame()) + pCol = static_cast<SwLayoutFrame*>(&pPrecede->GetFrame()); + } + } + + // Within sections in tables or section in headers/footers there can + // be only one column change be made, one of the above shortcuts should + // have applied, also when the section has a pPrev. + // Now we even consider an empty column... + OSL_ENSURE( pSect, "GetNextSctLeaf: Missing SectionFrame" ); + if (!pSect || (IsInTab() && !IsTabFrame()) || FindFooterOrHeader()) + return pCol; + + // === IMPORTANT === + // Precondition, which needs to be hold, is that the <this> frame can be + // inside a table, but then the found section frame <pSect> is also inside + // this table. + + // #i95698# + // A table cell containing directly a section does not break - see lcl_FindSectionsInRow(..) + // Thus, a table inside a section, which is inside another table can only + // flow backward in the columns of its section. + // Note: The table cell, which contains the section, can not have a master table cell. + if ( IsTabFrame() && pSect->IsInTab() ) + { + return pCol; + } + + { + if (SwFrame *pPrv = pSect->GetIndPrev()) + { + // Mooching, half dead SectionFrames shouldn't confuse us + while( pPrv && pPrv->IsSctFrame() && !static_cast<SwSectionFrame*>(pPrv)->GetSection() ) + pPrv = pPrv->GetPrev(); + if( pPrv ) + return pCol; + } + } + + const bool bBody = IsInDocBody(); + const bool bFly = IsInFly(); + + SwLayoutFrame *pLayLeaf = GetPrevLayoutLeaf(); + SwLayoutFrame *pPrevLeaf = nullptr; + + while ( pLayLeaf ) + { + // Never step into tables or sections + if ( pLayLeaf->IsInTab() || pLayLeaf->IsInSct() ) + { + pLayLeaf = pLayLeaf->GetPrevLayoutLeaf(); + } + else if ( bBody && pLayLeaf->IsInDocBody() ) + { + // If there is a pLayLeaf has a lower pLayLeaf is the frame we are looking for. + // Exception: pLayLeaf->Lower() is a zombie section frame + const SwFrame* pTmp = pLayLeaf->Lower(); + // OD 11.04.2003 #108824# - consider, that the zombie section frame + // can have frame below it in the found layout leaf. + // Thus, skipping zombie section frame, if possible. + while ( pTmp && pTmp->IsSctFrame() && + !( static_cast<const SwSectionFrame*>(pTmp)->GetSection() ) && + pTmp->GetNext() + ) + { + pTmp = pTmp->GetNext(); + } + if ( pTmp && + ( !pTmp->IsSctFrame() || + ( static_cast<const SwSectionFrame*>(pTmp)->GetSection() ) + ) + ) + { + break; + } + pPrevLeaf = pLayLeaf; + pLayLeaf = pLayLeaf->GetPrevLayoutLeaf(); + if ( pLayLeaf ) + SwFlowFrame::SetMoveBwdJump( true ); + } + else if ( bFly ) + break; // Contents in Flys every layout sheet should be right. Why? + else + pLayLeaf = pLayLeaf->GetPrevLayoutLeaf(); + } + if( !pLayLeaf ) + { + if( !pPrevLeaf ) + return pCol; + pLayLeaf = pPrevLeaf; + } + + SwSectionFrame* pNew = nullptr; + // At first go to the end of the layout sheet + SwFrame *pTmp = pLayLeaf->Lower(); + if( pTmp ) + { + while( pTmp->GetNext() ) + pTmp = pTmp->GetNext(); + if( pTmp->IsSctFrame() ) + { + // Half dead ones only interfere here + while( !static_cast<SwSectionFrame*>(pTmp)->GetSection() && pTmp->GetPrev() && + pTmp->GetPrev()->IsSctFrame() ) + pTmp = pTmp->GetPrev(); + if( static_cast<SwSectionFrame*>(pTmp)->GetFollow() == pSect ) + pNew = static_cast<SwSectionFrame*>(pTmp); + } + } + if( !pNew ) + { + pNew = new SwSectionFrame( *pSect, true ); + pNew->InsertBefore( pLayLeaf, nullptr ); + pNew->Init(); + SwRectFnSet aRectFnSet(pNew); + aRectFnSet.MakePos( *pNew, pLayLeaf, pNew->GetPrev(), true ); + + pLayLeaf = FirstLeaf( pNew ); + if( !pNew->Lower() ) // Format single column sections + { + pNew->MakePos(); + pLayLeaf->Format(getRootFrame()->GetCurrShell()->GetOut()); // In order that the PrtArea is correct for the MoveBwd + } + else + pNew->SimpleFormat(); + } + else + { + pLayLeaf = FirstLeaf( pNew ); + if( pLayLeaf->IsColBodyFrame() ) + { + // In existent section columns we're looking for the last not empty + // column. + SwLayoutFrame *pTmpLay = pLayLeaf; + while( pLayLeaf->GetUpper()->GetNext() ) + { + pLayLeaf = static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(pLayLeaf->GetUpper()->GetNext())->Lower()); + if( pLayLeaf->Lower() ) + pTmpLay = pLayLeaf; + } + // If we skipped an empty column, we've to set the jump-flag + if( pLayLeaf != pTmpLay ) + { + pLayLeaf = pTmpLay; + SwFlowFrame::SetMoveBwdJump( true ); + } + } + } + return pLayLeaf; +} + +static SwTwips lcl_DeadLine( const SwFrame* pFrame ) +{ + const SwLayoutFrame* pUp = pFrame->GetUpper(); + while( pUp && pUp->IsInSct() ) + { + if( pUp->IsSctFrame() ) + pUp = pUp->GetUpper(); + // Columns now with BodyFrame + else if( pUp->IsColBodyFrame() && pUp->GetUpper()->GetUpper()->IsSctFrame() ) + pUp = pUp->GetUpper()->GetUpper(); + else + break; + } + SwRectFnSet aRectFnSet(pFrame); + return pUp ? aRectFnSet.GetPrtBottom(*pUp) : + aRectFnSet.GetBottom(pFrame->getFrameArea()); +} + +/// checks whether the SectionFrame is still able to grow, as case may be the environment has to be asked +bool SwSectionFrame::Growable() const +{ + SwRectFnSet aRectFnSet(this); + if( aRectFnSet.YDiff( lcl_DeadLine( this ), + aRectFnSet.GetBottom(getFrameArea()) ) > 0 ) + return true; + + return ( GetUpper() && const_cast<SwFrame*>(static_cast<SwFrame const *>(GetUpper()))->Grow( LONG_MAX, true ) ); +} + +SwTwips SwSectionFrame::Grow_( SwTwips nDist, bool bTst ) +{ + if ( !IsColLocked() && !HasFixSize() ) + { + SwRectFnSet aRectFnSet(this); + tools::Long nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if( nFrameHeight > 0 && nDist > (LONG_MAX - nFrameHeight) ) + nDist = LONG_MAX - nFrameHeight; + + if ( nDist <= 0 ) + return 0; + + bool bInCalcContent = GetUpper() && IsInFly() && FindFlyFrame()->IsLocked(); + // OD 2004-03-15 #116561# - allow grow in online layout + bool bGrow = !Lower() || !Lower()->IsColumnFrame() || !Lower()->GetNext(); + if (!bGrow) + { + SwSection* pSection = GetSection(); + bGrow = pSection && pSection->GetFormat()->GetBalancedColumns().GetValue(); + } + if( !bGrow ) + { + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + bGrow = pSh && pSh->GetViewOptions()->getBrowseMode(); + } + if( bGrow ) + { + SwTwips nGrow; + if( IsInFootnote() ) + nGrow = 0; + else + { + nGrow = lcl_DeadLine( this ); + nGrow = aRectFnSet.YDiff( nGrow, aRectFnSet.GetBottom(getFrameArea()) ); + } + SwTwips nSpace = nGrow; + if( !bInCalcContent && nGrow < nDist && GetUpper() ) + nGrow = o3tl::saturating_add( + nGrow, GetUpper()->Grow( LONG_MAX, true )); + + if( nGrow > nDist ) + nGrow = nDist; + if( nGrow <= 0 ) + { + nGrow = 0; + if (!bTst) + { + if( bInCalcContent ) + InvalidateSize_(); + else + InvalidateSize(); + } + } + else if( !bTst ) + { + if( bInCalcContent ) + InvalidateSize_(); + else if( nSpace < nGrow && nDist != nSpace + GetUpper()-> + Grow( nGrow - nSpace ) ) + InvalidateSize(); + else + { + const SvxGraphicPosition ePos = + GetAttrSet()->GetBackground().GetGraphicPos(); + if ( GPOS_RT < ePos && GPOS_TILED != ePos ) + { + SetCompletePaint(); + InvalidatePage(); + } + if( GetUpper() && GetUpper()->IsHeaderFrame() ) + GetUpper()->InvalidateSize(); + } + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nGrow ); + } + + { + const tools::Long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()) + nGrow; + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, nPrtHeight ); + } + + if( Lower() && Lower()->IsColumnFrame() && Lower()->GetNext() ) + { + SwFrame* pTmp = Lower(); + do + { + pTmp->InvalidateSize_(); + pTmp = pTmp->GetNext(); + } while ( pTmp ); + InvalidateSize_(); + } + if( GetNext() ) + { + // Own height changed, need to invalidate the position of + // next frames. + SwFrame *pFrame = GetNext(); + while( pFrame && pFrame->IsSctFrame() && !static_cast<SwSectionFrame*>(pFrame)->GetSection() ) + { + // Invalidate all in-between frames, otherwise position + // calculation (which only looks back to one relative + // frame) will have an incorrect result. + InvalidateFramePos(pFrame, bInCalcContent); + pFrame = pFrame->GetNext(); + } + if( pFrame ) + { + InvalidateFramePos(pFrame, bInCalcContent); + } + } + // #i28701# - Due to the new object positioning + // the frame on the next page/column can flow backward (e.g. it + // was moved forward due to the positioning of its objects ). + // Thus, invalivate this next frame, if document compatibility + // option 'Consider wrapping style influence on object positioning' is ON. + else if ( GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) + { + InvalidateNextPos(); + } + } + return nGrow; + } + if ( !bTst ) + { + if( bInCalcContent ) + InvalidateSize_(); + else + InvalidateSize(); + } + } + return 0; +} + +SwTwips SwSectionFrame::Shrink_( SwTwips nDist, bool bTst ) +{ + if ( Lower() && !IsColLocked() && !HasFixSize() ) + { + if( ToMaximize( false ) ) + { + if( !bTst ) + InvalidateSize(); + } + else + { + SwRectFnSet aRectFnSet(this); + tools::Long nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if ( nDist > nFrameHeight ) + nDist = nFrameHeight; + + if ( Lower()->IsColumnFrame() && Lower()->GetNext() && // FootnoteAtEnd + !GetSection()->GetFormat()->GetBalancedColumns().GetValue() ) + { // With column bases the format takes over the control of the + // growth (because of the balance) + if ( !bTst ) + InvalidateSize(); + return nDist; + } + else if( !bTst ) + { + const SvxGraphicPosition ePos = + GetAttrSet()->GetBackground().GetGraphicPos(); + if ( GPOS_RT < ePos && GPOS_TILED != ePos ) + { + SetCompletePaint(); + InvalidatePage(); + } + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, -nDist ); + } + + { + const tools::Long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()) - nDist; + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, nPrtHeight ); + } + + // We do not allow a section frame to shrink the its upper + // footer frame. This is because in the calculation of a + // footer frame, the content of the section frame is _not_ + // calculated. If there is a fly frame overlapping with the + // footer frame, the section frame is not affected by this + // during the calculation of the footer frame size. + // The footer frame does not grow in its FormatSize function + // but during the calculation of the content of the section + // frame. The section frame grows until some of its text is + // located on top of the fly frame. The next call of CalcContent + // tries to shrink the section and here it would also shrink + // the footer. This may not happen, because shrinking the footer + // would cause the top of the section frame to overlap with the + // fly frame again, this would result in a perfect loop. + if( GetUpper() && !GetUpper()->IsFooterFrame() ) + GetUpper()->Shrink( nDist, bTst ); + + if( Lower() && Lower()->IsColumnFrame() && Lower()->GetNext() ) + { + SwFrame* pTmp = Lower(); + do + { + pTmp->InvalidateSize_(); + pTmp = pTmp->GetNext(); + } while ( pTmp ); + } + if( GetNext() ) + { + SwFrame* pFrame = GetNext(); + while( pFrame && pFrame->IsSctFrame() && !static_cast<SwSectionFrame*>(pFrame)->GetSection() ) + pFrame = pFrame->GetNext(); + if( pFrame ) + pFrame->InvalidatePos(); + else + SetRetouche(); + } + else + SetRetouche(); + return nDist; + } + } + } + return 0; +} + +/* +|* When are Frames within a SectionFrames moveable? +|* If they are not in the last column of a SectionFrames yet, +|* if there is no Follow, +|* if the SectionFrame cannot grow anymore, then it gets more complicated, +|* in that case it depends on whether the SectionFrame can find a next +|* layout sheet. In (column based/chained) Flys this is checked via +|* GetNextLayout, in tables and headers/footers there is none, however in the +|* DocBody and in foot notes there is always one. +|* +|* This routine is used in the TextFormatter to decided whether it's allowed to +|* create a (paragraph-)Follow or whether the paragraph has to stick together +|*/ +bool SwSectionFrame::MoveAllowed( const SwFrame* pFrame) const +{ + // Is there a Follow or is the Frame not in the last column? + if( HasFollow() || ( pFrame->GetUpper()->IsColBodyFrame() && + pFrame->GetUpper()->GetUpper()->GetNext() ) ) + return true; + if( pFrame->IsInFootnote() ) + { + if( IsInFootnote() ) + { + if( GetUpper()->IsInSct() ) + { + if( Growable() ) + return false; + return GetUpper()->FindSctFrame()->MoveAllowed( this ); + } + else + return true; + } + // The content of footnote inside a columned sectionfrm is moveable + // except in the last column + const SwLayoutFrame *pLay = pFrame->FindFootnoteFrame()->GetUpper()->GetUpper(); + if( pLay->IsColumnFrame() && pLay->GetNext() ) + { + // The first paragraph in the first footnote in the first column + // in the sectionfrm at the top of the page is not moveable, + // if the columnbody is empty. + bool bRet = false; + if( pLay->GetIndPrev() || pFrame->GetIndPrev() || + pFrame->FindFootnoteFrame()->GetPrev() ) + bRet = true; + else + { + const SwLayoutFrame* pBody = static_cast<const SwColumnFrame*>(pLay)->FindBodyCont(); + if( pBody && pBody->Lower() ) + bRet = true; + } + if( bRet && ( IsFootnoteAtEnd() || !Growable() ) ) + return true; + } + } + // Or can the section still grow? + if( !IsColLocked() && Growable() ) + return false; + // Now it has to be examined whether there is a layout sheet wherein + // a section Follow can be created + if( !CanContainSplitSection(this) || ( !IsInDocBody() && FindFooterOrHeader() ) ) + return false; // It doesn't work in table-in-sections/nested tables/headers/footers + if( IsInFly() ) // In column based or chained frames + return nullptr != const_cast<SwFrame*>(static_cast<SwFrame const *>(GetUpper()))->GetNextLeaf( MAKEPAGE_NONE ); + return true; +} + +/** Called for a frame inside a section with no direct previous frame (or only + previous empty section frames) the previous frame of the outer section is + returned, if the frame is the first flowing content of this section. + + Note: For a frame inside a table frame, which is inside a section frame, + NULL is returned. +*/ +SwFrame* SwFrame::GetIndPrev_() const +{ + SwFrame *pRet = nullptr; + // #i79774# + // Do not assert, if the frame has a direct previous frame, because it + // could be an empty section frame. The caller has to assure, that the + // frame has no direct previous frame or only empty section frames as + // previous frames. + OSL_ENSURE( /*!pPrev &&*/ IsInSct(), "Why?" ); + const SwFrame* pSct = GetUpper(); + if( !pSct ) + return nullptr; + if( pSct->IsSctFrame() ) + pRet = pSct->GetIndPrev(); + else if( pSct->IsColBodyFrame() && (pSct = pSct->GetUpper()->GetUpper())->IsSctFrame() ) + { + // Do not return the previous frame of the outer section, if in one + // of the previous columns is content. + const SwFrame* pCol = GetUpper()->GetUpper()->GetPrev(); + while( pCol ) + { + assert(pCol->IsColumnFrame()); + assert(pCol->GetLower() && pCol->GetLower()->IsBodyFrame()); + if( static_cast<const SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(pCol)->Lower())->Lower() ) + return nullptr; + pCol = pCol->GetPrev(); + } + pRet = pSct->GetIndPrev(); + } + + // skip empty section frames + while( pRet && pRet->IsSctFrame() && !static_cast<SwSectionFrame*>(pRet)->GetSection() ) + pRet = pRet->GetIndPrev(); + return pRet; +} + +SwFrame* SwFrame::GetIndNext_() +{ + OSL_ENSURE( !mpNext && IsInSct(), "Why?" ); + SwFrame* pSct = GetUpper(); + if( !pSct ) + return nullptr; + if( pSct->IsSctFrame() ) + return pSct->GetIndNext(); + if( pSct->IsColBodyFrame() && (pSct = pSct->GetUpper()->GetUpper())->IsSctFrame() ) + { // We can only return the successor of the SectionFrames if there is no + // content in the successive columns + SwFrame* pCol = GetUpper()->GetUpper()->GetNext(); + while( pCol ) + { + assert(pCol->IsColumnFrame()); + assert(pCol->GetLower() && pCol->GetLower()->IsBodyFrame()); + if( static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(pCol)->Lower())->Lower() ) + return nullptr; + pCol = pCol->GetNext(); + } + return pSct->GetIndNext(); + } + return nullptr; +} + +bool SwSectionFrame::IsDescendantFrom( const SwSectionFormat* pFormat ) const +{ + if( !m_pSection || !pFormat ) + return false; + const SwSectionFormat *pMyFormat = m_pSection->GetFormat(); + while( pFormat != pMyFormat ) + { + if( auto pNewFormat = dynamic_cast< const SwSectionFormat *>( pMyFormat->GetRegisteredIn()) ) + pMyFormat = pNewFormat; + else + return false; + } + return true; +} + +void SwSectionFrame::CalcFootnoteAtEndFlag() +{ + SwSectionFormat *pFormat = GetSection()->GetFormat(); + sal_uInt16 nVal = pFormat->GetFootnoteAtTextEnd( false ).GetValue(); + m_bFootnoteAtEnd = FTNEND_ATPGORDOCEND != nVal; + m_bOwnFootnoteNum = FTNEND_ATTXTEND_OWNNUMSEQ == nVal || + FTNEND_ATTXTEND_OWNNUMANDFMT == nVal; + while( !m_bFootnoteAtEnd && !m_bOwnFootnoteNum ) + { + if( auto pNewFormat = dynamic_cast<SwSectionFormat *>( pFormat->GetRegisteredIn()) ) + pFormat = pNewFormat; + else + break; + nVal = pFormat->GetFootnoteAtTextEnd( false ).GetValue(); + if( FTNEND_ATPGORDOCEND != nVal ) + { + m_bFootnoteAtEnd = true; + m_bOwnFootnoteNum = m_bOwnFootnoteNum ||FTNEND_ATTXTEND_OWNNUMSEQ == nVal || + FTNEND_ATTXTEND_OWNNUMANDFMT == nVal; + } + } +} + +bool SwSectionFrame::IsEndnoteAtMyEnd() const +{ + return m_pSection->GetFormat()->GetEndAtTextEnd( false ).IsAtEnd(); +} + +void SwSectionFrame::CalcEndAtEndFlag() +{ + SwSectionFormat *pFormat = GetSection()->GetFormat(); + m_bEndnAtEnd = pFormat->GetEndAtTextEnd( false ).IsAtEnd(); + while( !m_bEndnAtEnd ) + { + if( auto pNewFormat = dynamic_cast<SwSectionFormat *>( pFormat->GetRegisteredIn()) ) + pFormat = pNewFormat; + else + break; + m_bEndnAtEnd = pFormat->GetEndAtTextEnd( false ).IsAtEnd(); + } +} + +void SwSectionFrame::Notify(SfxHint const& rHint) +{ + SwSectionFormat *const pFormat(GetSection()->GetFormat()); + assert(pFormat); + SwClientNotify(*pFormat, rHint); +} + +void SwSectionFrame::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) +{ + if (rHint.GetId() == SfxHintId::SwLegacyModify) + { + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + SwSectionFrameInvFlags eInvFlags = SwSectionFrameInvFlags::NONE; + if(pLegacy->m_pNew && RES_ATTRSET_CHG == pLegacy->m_pNew->Which()) + { + auto& rOldSetChg = *static_cast<const SwAttrSetChg*>(pLegacy->m_pOld); + auto& rNewSetChg = *static_cast<const SwAttrSetChg*>(pLegacy->m_pNew); + SfxItemIter aOIter(*rOldSetChg.GetChgSet()); + SfxItemIter aNIter(*rNewSetChg.GetChgSet()); + const SfxPoolItem* pOItem = aOIter.GetCurItem(); + const SfxPoolItem* pNItem = aNIter.GetCurItem(); + SwAttrSetChg aOldSet(rOldSetChg); + SwAttrSetChg aNewSet(rNewSetChg); + do + { + UpdateAttr_(pOItem, pNItem, eInvFlags, &aOldSet, &aNewSet); + pNItem = aNIter.NextItem(); + pOItem = aOIter.NextItem(); + } while (pNItem); + if(aOldSet.Count() || aNewSet.Count()) + SwLayoutFrame::SwClientNotify(rMod, sw::LegacyModifyHint(&aOldSet, &aNewSet)); + } + else + UpdateAttr_(pLegacy->m_pOld, pLegacy->m_pNew, eInvFlags); + + if (eInvFlags != SwSectionFrameInvFlags::NONE) + { + if(eInvFlags & SwSectionFrameInvFlags::InvalidateSize) + InvalidateSize(); + if(eInvFlags & SwSectionFrameInvFlags::SetCompletePaint) + SetCompletePaint(); + } + } + else if(const auto pHint = dynamic_cast<const SwSectionFrameMoveAndDeleteHint*>(&rHint)) + { + // #i117863# + if(&rMod != GetDep()) + return; + SwSectionFrame::MoveContentAndDelete(this, pHint->IsSaveContent()); + } + else + SwFrame::SwClientNotify(rMod, rHint); +} + +void SwSectionFrame::UpdateAttr_( const SfxPoolItem *pOld, const SfxPoolItem *pNew, + SwSectionFrameInvFlags &rInvFlags, + SwAttrSetChg *pOldSet, SwAttrSetChg *pNewSet ) +{ + bool bClear = true; + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + switch( nWhich ) + { // Suppress multi columns in foot notes + case RES_FMT_CHG: + { + const SwFormatCol& rNewCol = GetFormat()->GetCol(); + if( !IsInFootnote() ) + { + // Nasty case. When allocating a template we can not count + // on the old column attribute. We're left with creating a + // temporary attribute here. + SwFormatCol aCol; + if ( Lower() && Lower()->IsColumnFrame() ) + { + sal_uInt16 nCol = 0; + SwFrame *pTmp = Lower(); + do + { ++nCol; + pTmp = pTmp->GetNext(); + } while ( pTmp ); + aCol.Init( nCol, 0, 1000 ); + } + bool bChgFootnote = IsFootnoteAtEnd(); + bool const bChgEndn = IsEndnAtEnd(); + bool const bChgMyEndn = IsEndnoteAtMyEnd(); + CalcFootnoteAtEndFlag(); + CalcEndAtEndFlag(); + bChgFootnote = ( bChgFootnote != IsFootnoteAtEnd() ) || + ( bChgEndn != IsEndnAtEnd() ) || + ( bChgMyEndn != IsEndnoteAtMyEnd() ); + ChgColumns( aCol, rNewCol, bChgFootnote ); + rInvFlags |= SwSectionFrameInvFlags::SetCompletePaint; + } + rInvFlags |= SwSectionFrameInvFlags::InvalidateSize; + bClear = false; + } + break; + + case RES_COL: + if( !IsInFootnote() ) + { + assert(pOld && pNew); + if (pOld && pNew) + { + ChgColumns( *static_cast<const SwFormatCol*>(pOld), *static_cast<const SwFormatCol*>(pNew) ); + rInvFlags |= SwSectionFrameInvFlags::InvalidateSize | SwSectionFrameInvFlags::SetCompletePaint; + } + } + break; + + case RES_FTN_AT_TXTEND: + if( !IsInFootnote() ) + { + bool const bOld = IsFootnoteAtEnd(); + CalcFootnoteAtEndFlag(); + if (bOld != IsFootnoteAtEnd()) + { + const SwFormatCol& rNewCol = GetFormat()->GetCol(); + ChgColumns( rNewCol, rNewCol, true ); + rInvFlags |= SwSectionFrameInvFlags::InvalidateSize; + } + } + break; + + case RES_END_AT_TXTEND: + if( !IsInFootnote() ) + { + bool const bOld = IsEndnAtEnd(); + bool const bMyOld = IsEndnoteAtMyEnd(); + CalcEndAtEndFlag(); + if (bOld != IsEndnAtEnd() || bMyOld != IsEndnoteAtMyEnd()) + { + const SwFormatCol& rNewCol = GetFormat()->GetCol(); + ChgColumns( rNewCol, rNewCol, true ); + rInvFlags |= SwSectionFrameInvFlags::InvalidateSize; + } + } + break; + case RES_COLUMNBALANCE: + rInvFlags |= SwSectionFrameInvFlags::InvalidateSize; + break; + + case RES_FRAMEDIR : + SetDerivedR2L( false ); + CheckDirChange(); + break; + + case RES_PROTECT: +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + { + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetLayout()->IsAnyShellAccessible() ) + pSh->Imp()->InvalidateAccessibleEditableState( true, this ); + } +#endif + break; + + default: + bClear = false; + } + if ( !bClear ) + return; + + if ( pOldSet || pNewSet ) + { + if ( pOldSet ) + pOldSet->ClearItem( nWhich ); + if ( pNewSet ) + pNewSet->ClearItem( nWhich ); + } + else + { + SwModify aMod; + SwLayoutFrame::SwClientNotify(aMod, sw::LegacyModifyHint(pOld, pNew)); + } +} + +/// A follow or a ftncontainer at the end of the page causes a maximal Size of the sectionframe. +bool SwSectionFrame::ToMaximize( bool bCheckFollow ) const +{ + if( HasFollow() ) + { + if( !bCheckFollow ) // Don't check superfluous follows + return true; + const SwSectionFrame* pFoll = GetFollow(); + while( pFoll && pFoll->IsSuperfluous() ) + pFoll = pFoll->GetFollow(); + if( pFoll ) + return true; + } + if( IsFootnoteAtEnd() ) + return false; + const SwFootnoteContFrame* pCont = ContainsFootnoteCont(); + if( !IsEndnAtEnd() ) + return nullptr != pCont; + bool bRet = false; + while( pCont && !bRet ) + { + if( pCont->FindFootNote() ) + bRet = true; + else + pCont = ContainsFootnoteCont( pCont ); + } + return bRet; +} + +/// Check every Column for FootnoteContFrames. +SwFootnoteContFrame* SwSectionFrame::ContainsFootnoteCont( const SwFootnoteContFrame* pCont ) const +{ + SwFootnoteContFrame* pRet = nullptr; + const SwLayoutFrame* pLay; + if( pCont ) + { + pLay = pCont->FindFootnoteBossFrame(); + OSL_ENSURE( IsAnLower( pLay ), "ContainsFootnoteCont: Wrong FootnoteContainer" ); + pLay = static_cast<const SwLayoutFrame*>(pLay->GetNext()); + } + else if( Lower() && Lower()->IsColumnFrame() ) + pLay = static_cast<const SwLayoutFrame*>(Lower()); + else + pLay = nullptr; + while ( !pRet && pLay ) + { + if( pLay->Lower() && pLay->Lower()->GetNext() ) + { + OSL_ENSURE( pLay->Lower()->GetNext()->IsFootnoteContFrame(), + "ToMaximize: Unexpected Frame" ); + pRet = const_cast<SwFootnoteContFrame*>(static_cast<const SwFootnoteContFrame*>(pLay->Lower()->GetNext())); + } + OSL_ENSURE( !pLay->GetNext() || pLay->GetNext()->IsLayoutFrame(), + "ToMaximize: ColFrame expected" ); + pLay = static_cast<const SwLayoutFrame*>(pLay->GetNext()); + } + return pRet; +} + +void SwSectionFrame::InvalidateFootnotePos() +{ + SwFootnoteContFrame* pCont = ContainsFootnoteCont(); + if( pCont ) + { + SwFrame *pTmp = pCont->ContainsContent(); + if( pTmp ) + pTmp->InvalidatePos_(); + } +} + +SwTwips SwSectionFrame::CalcUndersize() const +{ + SwRectFnSet aRectFnSet(this); + return InnerHeight() - aRectFnSet.GetHeight(getFramePrintArea()); +} + +SwTwips SwSectionFrame::Undersize() +{ + const auto nRet = CalcUndersize(); + m_bUndersized = (nRet > 0); + return nRet <= 0 ? 0 : nRet; +} + +void SwSectionFrame::CalcFootnoteContent() +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + SwFootnoteContFrame* pCont = ContainsFootnoteCont(); + if( !pCont ) + return; + + SwFrame* pFrame = pCont->ContainsAny(); + if( pFrame ) + pCont->Calc(pRenderContext); + while( pFrame && IsAnLower( pFrame ) ) + { + SwFootnoteFrame* pFootnote = pFrame->FindFootnoteFrame(); + if( pFootnote ) + pFootnote->Calc(pRenderContext); + pFrame->Calc(pRenderContext); + if( pFrame->IsSctFrame() ) + { + SwFrame *pTmp = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pTmp ) + { + pFrame = pTmp; + continue; + } + } + pFrame = pFrame->FindNext(); + } +} + +/* + * If a SectionFrame gets empty, e.g. because its content changes the page/column, + * it is not destroyed immediately (there could be a pointer left to it on the + * stack), instead it puts itself in a list at the RootFrame, which is processed + * later on (in Layaction::Action among others). Its size is set to Null and + * the pointer to its page as well. Such SectionFrames that are to be deleted + * must be ignored by the layout/during formatting. + * + * With InsertEmptySct the RootFrame stores a SectionFrame in the list, + * with RemoveFromList it can be removed from the list (Dtor), + * with DeleteEmptySct the list is processed and the SectionFrames are destroyed. + */ +void SwRootFrame::InsertEmptySct( SwSectionFrame* pDel ) +{ + if( !mpDestroy ) + mpDestroy.reset( new SwDestroyList ); + mpDestroy->insert( pDel ); +} + +void SwRootFrame::DeleteEmptySct_() +{ + assert(mpDestroy); + while( !mpDestroy->empty() ) + { + SwSectionFrame* pSect = *mpDestroy->begin(); + mpDestroy->erase( mpDestroy->begin() ); + OSL_ENSURE( !pSect->IsColLocked() && !pSect->IsJoinLocked(), + "DeleteEmptySct: Locked SectionFrame" ); + SAL_WARN_IF(pSect->IsDeleteForbidden(), "sw.layout", "not allowed delete SwFrame"); + if( !pSect->getFrameArea().HasArea() && !pSect->ContainsContent() && !pSect->IsDeleteForbidden() ) + { + SwLayoutFrame* pUp = pSect->GetUpper(); + pSect->RemoveFromLayout(); + SwFrame::DestroyFrame(pSect); + if( pUp && !pUp->Lower() ) + { + if( pUp->IsPageBodyFrame() ) + pUp->getRootFrame()->SetSuperfluous(); + else if( pUp->IsFootnoteFrame() && !pUp->IsColLocked() && + pUp->GetUpper() ) + { + pUp->Cut(); + SwFrame::DestroyFrame(pUp); + } + } + } + else { + OSL_ENSURE( pSect->GetSection(), "DeleteEmptySct: Half-dead SectionFrame?!" ); + } + } +} + +void SwRootFrame::RemoveFromList_( SwSectionFrame* pSct ) +{ + assert(mpDestroy && "Where's my list?"); + mpDestroy->erase( pSct ); +} + +#ifdef DBG_UTIL +bool SwRootFrame::IsInDelList( SwSectionFrame* pSct ) const +{ + return mpDestroy && mpDestroy->find( pSct ) != mpDestroy->end(); +} +#endif + +bool SwSectionFrame::IsBalancedSection() const +{ + bool bRet = false; + if ( GetSection() && Lower() && Lower()->IsColumnFrame() && Lower()->GetNext() ) + { + bRet = !GetSection()->GetFormat()->GetBalancedColumns().GetValue(); + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/softpagebreak.cxx b/sw/source/core/layout/softpagebreak.cxx new file mode 100644 index 000000000..87ba7c24e --- /dev/null +++ b/sw/source/core/layout/softpagebreak.cxx @@ -0,0 +1,154 @@ +/* -*- 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 <txtfrm.hxx> +#include <pagefrm.hxx> +#include <swtable.hxx> +#include <frmfmt.hxx> +#include <rowfrm.hxx> +#include <tabfrm.hxx> +#include <calbck.hxx> +#include <ndtxt.hxx> + +void SwTextNode::fillSoftPageBreakList( SwSoftPageBreakList& rBreak ) const +{ + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this); + for( const SwTextFrame *pFrame = aIter.First(); pFrame; pFrame = aIter.Next() ) + { + // No soft page break in header or footer + if( pFrame->FindFooterOrHeader() || pFrame->IsInFly() ) + return; + // No soft page break if I'm not the first frame in my layout frame + if( pFrame->GetIndPrev() ) + continue; + const SwPageFrame* pPage = pFrame->FindPageFrame(); + // No soft page break at the first page + if( pPage && pPage->GetPrev() ) + { + const SwContentFrame* pFirst2 = pPage->FindFirstBodyContent(); + // Special handling for content frame in table frames + if( pFrame->IsInTab() ) + { + // No soft page break if I'm in a table but the first content frame + // at my page is not in a table + if( !pFirst2 || !pFirst2->IsInTab() ) + continue; + const SwLayoutFrame *pRow = pFrame->GetUpper(); + // Looking for the "most upper" row frame, + // skipping sub tables and/or table in table + while( !pRow->IsRowFrame() || !pRow->GetUpper()->IsTabFrame() || + pRow->GetUpper()->GetUpper()->IsInTab() ) + pRow = pRow->GetUpper(); + const SwTabFrame *pTab = pRow->FindTabFrame(); + // For master tables the soft page break will exported at the table row, + // not at the content frame. + // If the first content is outside my table frame, no soft page break. + if( !pTab->IsFollow() || !pTab->IsAnLower( pFirst2 ) ) + continue; + // Only content of non-heading-rows can get a soft page break + const SwFrame* pFirstRow = pTab->GetFirstNonHeadlineRow(); + // If there's no follow flow line, the soft page break will be + // exported at the row, not at the content. + if( pRow == pFirstRow && + pTab->FindMaster()->HasFollowFlowLine() ) + { + // Now we have the row which causes a new page, + // this row is a follow flow line and therefore cannot get + // the soft page break itself. + // Every first content frame of every cell frame in this row + // will get the soft page break + const SwFrame* pCell = pRow->Lower(); + while( pCell ) + { + pFirst2 = static_cast<const SwLayoutFrame*>(pCell)->ContainsContent(); + if( pFirst2 == pFrame ) + { // Here we are: a first content inside a cell + // inside the split row => soft page break + auto const pos(pFrame->MapViewToModel(pFrame->GetOffset())); + if (pos.first == this) + { + rBreak.insert(pos.second); + } + break; + } + pCell = pCell->GetNext(); + } + } + } + else // No soft page break if there's a "hard" page break attribute + if( pFirst2 == pFrame && !pFrame->IsPageBreak( true ) ) + { + auto const pos(pFrame->MapViewToModel(pFrame->GetOffset())); + if (pos.first == this) + { // in the !Show case, we have to iterate over the merged + // SwTextFrame for every node + rBreak.insert(pos.second); + } + } + } + } +} + +bool SwTableLine::hasSoftPageBreak() const +{ + // No soft page break for sub tables + if( GetUpper() || !GetFrameFormat() ) + return false; + SwIterator<SwRowFrame,SwFormat> aIter( *GetFrameFormat() ); + for( SwRowFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if( pLast->GetTabLine() == this ) + { + const SwTabFrame* pTab = pLast->FindTabFrame(); + // No soft page break for + // tables with prevs, i.e. if the frame is not the first in its layout frame + // tables in footer or header + // tables in flies + // inner tables of nested tables + // master table frames with "hard" page break attribute + if( pTab->GetIndPrev() || pTab->FindFooterOrHeader() + || pTab->IsInFly() || pTab->GetUpper()->IsInTab() || + ( !pTab->IsFollow() && pTab->IsPageBreak( true ) ) ) + return false; + const SwPageFrame* pPage = pTab->FindPageFrame(); + // No soft page break at the first page of the document + if( pPage && !pPage->GetPrev() ) + return false; + const SwContentFrame* pFirst = pPage ? pPage->FindFirstBodyContent() : nullptr; + // No soft page break for + // tables which does not contain the first body content of the page + if( !pFirst || !pTab->IsAnLower( pFirst->FindTabFrame() ) ) + return false; + // The row which could get a soft page break must be either the first + // row of a master table frame or the first "non-headline-row" of a + // follow table frame... + const SwFrame* pRow = pTab->IsFollow() ? + pTab->GetFirstNonHeadlineRow() : pTab->Lower(); + if( pRow == pLast ) + { + // The last check: no soft page break for "follow" table lines + return !pTab->IsFollow() || !pTab->FindMaster()->HasFollowFlowLine(); + } + return false; + } + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/sortedobjs.cxx b/sw/source/core/layout/sortedobjs.cxx new file mode 100644 index 000000000..ad3759466 --- /dev/null +++ b/sw/source/core/layout/sortedobjs.cxx @@ -0,0 +1,293 @@ +/* -*- 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 <sortedobjs.hxx> + +#include <algorithm> +#include <anchoredobject.hxx> +#include <fmtanchr.hxx> +#include <fmtsrnd.hxx> +#include <fmtwrapinfluenceonobjpos.hxx> +#include <frmfmt.hxx> +#include <pam.hxx> +#include <svx/svdobj.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; + +SwSortedObjs::SwSortedObjs() +{ +} + +SwSortedObjs::~SwSortedObjs() +{ +} + +size_t SwSortedObjs::size() const +{ + return maSortedObjLst.size(); +} + +SwAnchoredObject* SwSortedObjs::operator[]( size_t _nIndex ) const +{ + SwAnchoredObject* pAnchoredObj = nullptr; + + if ( _nIndex >= size() ) + { + OSL_FAIL( "<SwSortedObjs::operator[]> - index out of range" ); + } + else + { + pAnchoredObj = maSortedObjLst[ _nIndex ]; + } + + return pAnchoredObj; +} + +namespace +{ + int GetAnchorWeight(RndStdIds eAnchor) + { + if (eAnchor == RndStdIds::FLY_AT_CHAR) + return 0; + if (eAnchor == RndStdIds::FLY_AS_CHAR) + return 1; + return 2; + } + +struct ObjAnchorOrder +{ + bool operator()( const SwAnchoredObject* _pListedAnchoredObj, + const SwAnchoredObject* _pNewAnchoredObj ) + { + // get attributes of listed object + const SwFrameFormat& rFormatListed = _pListedAnchoredObj->GetFrameFormat(); + const SwFormatAnchor* pAnchorListed = &(rFormatListed.GetAnchor()); + + // get attributes of new object + const SwFrameFormat& rFormatNew = _pNewAnchoredObj->GetFrameFormat(); + const SwFormatAnchor* pAnchorNew = &(rFormatNew.GetAnchor()); + + // check for to-page anchored objects + if ((pAnchorListed->GetAnchorId() == RndStdIds::FLY_AT_PAGE) && + (pAnchorNew ->GetAnchorId() != RndStdIds::FLY_AT_PAGE)) + { + return true; + } + else if ((pAnchorListed->GetAnchorId() != RndStdIds::FLY_AT_PAGE) && + (pAnchorNew ->GetAnchorId() == RndStdIds::FLY_AT_PAGE)) + { + return false; + } + else if ((pAnchorListed->GetAnchorId() == RndStdIds::FLY_AT_PAGE) && + (pAnchorNew ->GetAnchorId() == RndStdIds::FLY_AT_PAGE)) + { + return pAnchorListed->GetOrder() < pAnchorNew->GetOrder(); + } + + // Both objects aren't anchored to page. + // Thus, check for to-fly anchored objects + if ((pAnchorListed->GetAnchorId() == RndStdIds::FLY_AT_FLY) && + (pAnchorNew ->GetAnchorId() != RndStdIds::FLY_AT_FLY)) + { + return true; + } + else if ((pAnchorListed->GetAnchorId() != RndStdIds::FLY_AT_FLY) && + (pAnchorNew ->GetAnchorId() == RndStdIds::FLY_AT_FLY)) + { + return false; + } + else if ((pAnchorListed->GetAnchorId() == RndStdIds::FLY_AT_FLY) && + (pAnchorNew ->GetAnchorId() == RndStdIds::FLY_AT_FLY)) + { + return pAnchorListed->GetOrder() < pAnchorNew->GetOrder(); + } + + // Both objects aren't anchor to page or to fly + // Thus, compare content anchor nodes, if existing. + const SwPosition* pContentAnchorListed = pAnchorListed->GetContentAnchor(); + const SwPosition* pContentAnchorNew = pAnchorNew->GetContentAnchor(); + if ( pContentAnchorListed && pContentAnchorNew && + pContentAnchorListed->nNode != pContentAnchorNew->nNode ) + { + return pContentAnchorListed->nNode < pContentAnchorNew->nNode; + } + + // objects anchored at the same content. + // --> OD 2006-11-29 #???# - objects have to be ordered by anchor node position + // Thus, compare content anchor node positions and anchor type, + // if not anchored at-paragraph + if (pContentAnchorListed && pContentAnchorNew) + { + sal_Int32 nListedIndex = pAnchorListed->GetAnchorId() != RndStdIds::FLY_AT_PARA ? + pContentAnchorListed->nContent.GetIndex() : 0; + sal_Int32 nNewIndex = pAnchorNew->GetAnchorId() != RndStdIds::FLY_AT_PARA ? + pContentAnchorNew->nContent.GetIndex() : 0; + if (nListedIndex != nNewIndex) + { + return nListedIndex < nNewIndex; + } + } + + int nAnchorListedWeight = GetAnchorWeight(pAnchorListed->GetAnchorId()); + int nAnchorNewWeight = GetAnchorWeight(pAnchorNew->GetAnchorId()); + if (nAnchorListedWeight != nAnchorNewWeight) + { + return nAnchorListedWeight < nAnchorNewWeight; + } + + // objects anchored at the same content and at the same content anchor + // node position with the same anchor type + // Thus, compare its wrapping style including its layer + const IDocumentDrawModelAccess& rIDDMA = rFormatListed.getIDocumentDrawModelAccess(); + const SdrLayerID nHellId = rIDDMA.GetHellId(); + const SdrLayerID nInvisibleHellId = rIDDMA.GetInvisibleHellId(); + const bool bWrapThroughOrHellListed = + rFormatListed.GetSurround().GetSurround() == css::text::WrapTextMode_THROUGH || + _pListedAnchoredObj->GetDrawObj()->GetLayer() == nHellId || + _pListedAnchoredObj->GetDrawObj()->GetLayer() == nInvisibleHellId; + const bool bWrapThroughOrHellNew = + rFormatNew.GetSurround().GetSurround() == css::text::WrapTextMode_THROUGH || + _pNewAnchoredObj->GetDrawObj()->GetLayer() == nHellId || + _pNewAnchoredObj->GetDrawObj()->GetLayer() == nInvisibleHellId; + if ( bWrapThroughOrHellListed != bWrapThroughOrHellNew ) + { + return !bWrapThroughOrHellListed; + } + else if ( bWrapThroughOrHellListed && bWrapThroughOrHellNew ) + { + return pAnchorListed->GetOrder() < pAnchorNew->GetOrder(); + } + + // objects anchored at the same content with a set text wrapping + // Thus, compare wrap influences on object position + const SwFormatWrapInfluenceOnObjPos* pWrapInfluenceOnObjPosListed = + &(rFormatListed.GetWrapInfluenceOnObjPos()); + const SwFormatWrapInfluenceOnObjPos* pWrapInfluenceOnObjPosNew = + &(rFormatNew.GetWrapInfluenceOnObjPos()); + // #i35017# - handle ITERATIVE as ONCE_SUCCESSIVE + if ( pWrapInfluenceOnObjPosListed->GetWrapInfluenceOnObjPos( true ) != + pWrapInfluenceOnObjPosNew->GetWrapInfluenceOnObjPos( true ) ) + { + // #i35017# - constant name has changed + return pWrapInfluenceOnObjPosListed->GetWrapInfluenceOnObjPos( true ) + == text::WrapInfluenceOnPosition::ONCE_SUCCESSIVE; + } + + // objects anchored at the same content position/page/fly with same + // wrap influence. + // Thus, compare anchor order number + return pAnchorListed->GetOrder() < pAnchorNew->GetOrder(); + } +}; + +} + +bool SwSortedObjs::is_sorted() const +{ + return std::is_sorted(maSortedObjLst.begin(), maSortedObjLst.end(), ObjAnchorOrder()); +} + +bool SwSortedObjs::Insert( SwAnchoredObject& _rAnchoredObj ) +{ + // #i51941# + if ( Contains( _rAnchoredObj ) ) + { + // list already contains object + OSL_FAIL( "<SwSortedObjs::Insert()> - already contains object" ); + return true; + } + + // find insert position + std::vector< SwAnchoredObject* >::iterator aInsPosIter = + std::lower_bound( maSortedObjLst.begin(), maSortedObjLst.end(), + &_rAnchoredObj, ObjAnchorOrder() ); + + // insert object into list + maSortedObjLst.insert( aInsPosIter, &_rAnchoredObj ); + + return Contains( _rAnchoredObj ); +} + +void SwSortedObjs::Remove( SwAnchoredObject& _rAnchoredObj ) +{ + std::vector< SwAnchoredObject* >::iterator aDelPosIter = + std::find( maSortedObjLst.begin(), maSortedObjLst.end(), &_rAnchoredObj ); + + if ( aDelPosIter == maSortedObjLst.end() ) + { + // object not found. + OSL_FAIL( "<SwSortedObjs::Remove()> - object not found" ); + } + else + { + maSortedObjLst.erase( aDelPosIter ); + } +} + +bool SwSortedObjs::Contains( const SwAnchoredObject& _rAnchoredObj ) const +{ + std::vector< SwAnchoredObject* >::const_iterator aIter = + std::find( maSortedObjLst.begin(), maSortedObjLst.end(), &_rAnchoredObj ); + + return aIter != maSortedObjLst.end(); +} + +void SwSortedObjs::Update( SwAnchoredObject& _rAnchoredObj ) +{ + if ( !Contains( _rAnchoredObj ) ) + { + // given anchored object not found in list + OSL_FAIL( "<SwSortedObjs::Update(..) - sorted list doesn't contain given anchored object" ); + return; + } + + if ( size() == 1 ) + { + // given anchored object is the only one in the list. + return; + } + + Remove( _rAnchoredObj ); + Insert( _rAnchoredObj ); +} + +void SwSortedObjs::UpdateAll() +{ + std::stable_sort(maSortedObjLst.begin(), maSortedObjLst.end(), ObjAnchorOrder()); +} + +size_t SwSortedObjs::ListPosOf( const SwAnchoredObject& _rAnchoredObj ) const +{ + std::vector< SwAnchoredObject* >::const_iterator aIter = + std::find( maSortedObjLst.begin(), maSortedObjLst.end(), &_rAnchoredObj ); + + if ( aIter != maSortedObjLst.end() ) + { + // #i51941# + std::vector< SwAnchoredObject* >::difference_type nPos = + aIter - maSortedObjLst.begin(); + return static_cast<size_t>( nPos ); + } + + return size(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/ssfrm.cxx b/sw/source/core/layout/ssfrm.cxx new file mode 100644 index 000000000..c7d11b3ca --- /dev/null +++ b/sw/source/core/layout/ssfrm.cxx @@ -0,0 +1,752 @@ +/* -*- 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 <config_wasm_strip.h> + +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <dcontact.hxx> +#include <flyfrm.hxx> +#include <txtfrm.hxx> +#include <cellfrm.hxx> +#include <swtable.hxx> +#include <fmtfsize.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/shaditem.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentMarkAccess.hxx> +#include <fmtclds.hxx> +#include <viewimp.hxx> +#include <sortedobjs.hxx> +#include <hints.hxx> +#include <frmtool.hxx> +#include <ndtxt.hxx> +#include <osl/diagnose.h> + + // No inline cause we need the function pointers +tools::Long SwFrame::GetTopMargin() const + { return getFramePrintArea().Top(); } +tools::Long SwFrame::GetBottomMargin() const + { return getFrameArea().Height() -getFramePrintArea().Height() -getFramePrintArea().Top(); } +tools::Long SwFrame::GetLeftMargin() const + { return getFramePrintArea().Left(); } +tools::Long SwFrame::GetRightMargin() const + { return getFrameArea().Width() - getFramePrintArea().Width() - getFramePrintArea().Left(); } +tools::Long SwFrame::GetPrtLeft() const + { return getFrameArea().Left() + getFramePrintArea().Left(); } +tools::Long SwFrame::GetPrtBottom() const + { return getFrameArea().Top() + getFramePrintArea().Height() + getFramePrintArea().Top(); } +tools::Long SwFrame::GetPrtRight() const + { return getFrameArea().Left() + getFramePrintArea().Width() + getFramePrintArea().Left(); } +tools::Long SwFrame::GetPrtTop() const + { return getFrameArea().Top() + getFramePrintArea().Top(); } + +bool SwFrame::SetMinLeft( tools::Long nDeadline ) +{ + SwTwips nDiff = nDeadline - getFrameArea().Left(); + if( nDiff > 0 ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Left( nDeadline ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( aPrt.Width() - nDiff ); + + return true; + } + return false; +} + +bool SwFrame::SetMaxBottom( tools::Long nDeadline ) +{ + SwTwips nDiff = getFrameArea().Top() + getFrameArea().Height() - nDeadline; + if( nDiff > 0 ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( aFrm.Height() - nDiff ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( aPrt.Height() - nDiff ); + + return true; + } + return false; +} + +bool SwFrame::SetMaxRight( tools::Long nDeadline ) +{ + SwTwips nDiff = getFrameArea().Left() + getFrameArea().Width() - nDeadline; + if( nDiff > 0 ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( aFrm.Width() - nDiff ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( aPrt.Width() - nDiff ); + + return true; + } + return false; +} + +void SwFrame::MakeBelowPos( const SwFrame* pUp, const SwFrame* pPrv, bool bNotify ) +{ + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + if( pPrv ) + { + aFrm.Pos( pPrv->getFrameArea().Pos() ); + aFrm.Pos().AdjustY(pPrv->getFrameArea().Height() ); + } + else + { + aFrm.Pos( pUp->getFrameArea().Pos() ); + aFrm.Pos() += pUp->getFramePrintArea().Pos(); + } + + if( bNotify ) + { + aFrm.Pos().AdjustY(1 ); + } +} + +void SwFrame::MakeLeftPos( const SwFrame* pUp, const SwFrame* pPrv, bool bNotify ) +{ + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + if( pPrv ) + { + aFrm.Pos( pPrv->getFrameArea().Pos() ); + aFrm.Pos().AdjustX( -(aFrm.Width()) ); + } + else + { + aFrm.Pos( pUp->getFrameArea().Pos() ); + aFrm.Pos() += pUp->getFramePrintArea().Pos(); + aFrm.Pos().AdjustX(pUp->getFramePrintArea().Width() - aFrm.Width() ); + } + + if( bNotify ) + { + aFrm.Pos().AdjustX( -1 ); + } +} + +void SwFrame::MakeRightPos( const SwFrame* pUp, const SwFrame* pPrv, bool bNotify ) +{ + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + if( pPrv ) + { + aFrm.Pos( pPrv->getFrameArea().Pos() ); + aFrm.Pos().AdjustX(pPrv->getFrameArea().Width() ); + } + else + { + aFrm.Pos( pUp->getFrameArea().Pos() ); + aFrm.Pos() += pUp->getFramePrintArea().Pos(); + } + + if( bNotify ) + { + aFrm.Pos().AdjustX(1 ); + } +} + +void SwFrame::SetTopBottomMargins( tools::Long nTop, tools::Long nBot ) +{ + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Top( nTop ); + aPrt.Height( getFrameArea().Height() - nTop - nBot ); +} + +void SwFrame::SetLeftRightMargins( tools::Long nLeft, tools::Long nRight) +{ + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Left( nLeft ); + aPrt.Width( getFrameArea().Width() - nLeft - nRight ); +} + +void SwFrame::SetRightLeftMargins( tools::Long nRight, tools::Long nLeft) +{ + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Left( nLeft ); + aPrt.Width( getFrameArea().Width() - nLeft - nRight ); +} + +/// checks the layout direction and invalidates the lower frames recursively, if necessary. +void SwFrame::CheckDirChange() +{ + bool bOldVert = mbVertical; + bool bOldR2L = mbRightToLeft; + SetInvalidVert( true ); + mbInvalidR2L = true; + bool bChg = bOldR2L != IsRightToLeft(); + bool bOldVertL2R = IsVertLR(); + if( !(( IsVertical() != bOldVert ) || bChg || bOldVertL2R != IsVertLR()) ) + return; + + InvalidateAll(); + if( IsLayoutFrame() ) + { + // set minimum row height for vertical cells in horizontal table: + if ( IsCellFrame() && GetUpper() ) + { + if ( IsVertical() != GetUpper()->IsVertical() && + static_cast<SwCellFrame*>(this)->GetTabBox()->getRowSpan() == 1 ) + { + enum { + MIN_VERT_CELL_HEIGHT = 1135 + }; + + SwTableLine* pLine = const_cast<SwTableLine*>(static_cast<SwCellFrame*>(this)->GetTabBox()->GetUpper()); + SwFrameFormat* pFrameFormat = pLine->GetFrameFormat(); + SwFormatFrameSize aNew( pFrameFormat->GetFrameSize() ); + if ( SwFrameSize::Fixed != aNew.GetHeightSizeType() ) + aNew.SetHeightSizeType( SwFrameSize::Minimum ); + if ( aNew.GetHeight() < MIN_VERT_CELL_HEIGHT ) + aNew.SetHeight( MIN_VERT_CELL_HEIGHT ); + SwDoc* pDoc = pFrameFormat->GetDoc(); + pDoc->SetAttr( aNew, *pLine->ClaimFrameFormat() ); + } + } + + SwFrame* pFrame = static_cast<SwLayoutFrame*>(this)->Lower(); + const SwFormatCol* pCol = nullptr; + SwLayoutFrame* pBody = nullptr; + if( pFrame ) + { + if( IsPageFrame() ) + { + // If we're a page frame and we change our layout direction, + // we have to look for columns and rearrange them. + pBody = static_cast<SwPageFrame*>(this)->FindBodyCont(); + if(pBody && pBody->Lower() && pBody->Lower()->IsColumnFrame()) + pCol = &static_cast<SwPageFrame*>(this)->GetFormat()->GetCol(); + } + else if( pFrame->IsColumnFrame() ) + { + pBody = static_cast<SwLayoutFrame*>(this); + const SwFrameFormat *pFormat = pBody->GetFormat(); + if( pFormat ) + pCol = &pFormat->GetCol(); + } + } + while( pFrame ) + { + pFrame->CheckDirChange(); + pFrame = pFrame->GetNext(); + } + if( pCol ) + pBody->AdjustColumns( pCol, true ); + } + else if( IsTextFrame() ) + static_cast<SwTextFrame*>(this)->Prepare(); + + // #i31698# - notify anchored objects also for page frames. + // Remove code above for special handling of page frames + if ( !GetDrawObjs() ) + return; + + const SwSortedObjs *pObjs = GetDrawObjs(); + const size_t nCnt = pObjs->size(); + for ( size_t i = 0; i < nCnt; ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pObjs)[i]; + if( auto pFlyFrame = pAnchoredObj->DynCastFlyFrame() ) + pFlyFrame->CheckDirChange(); + else + { + // OD 2004-04-06 #i26791# - direct object + // positioning no longer needed. Instead + // invalidate + pAnchoredObj->InvalidateObjPos(); + } + // #i31698# - update layout direction of + // anchored object + { + ::setContextWritingMode( pAnchoredObj->DrawObj(), pAnchoredObj->GetAnchorFrameContainingAnchPos() ); + pAnchoredObj->UpdateLayoutDir(); + } + } +} + +/// returns the position for anchors based on frame direction +// OD 2004-03-10 #i11860# - consider lower space and line spacing of +// previous frame according to new option 'Use former object positioning' +Point SwFrame::GetFrameAnchorPos( bool bIgnoreFlysAnchoredAtThisFrame ) const +{ + Point aAnchor = getFrameArea().Pos(); + + if ( ( IsVertical() && !IsVertLR() ) || IsRightToLeft() ) + aAnchor.AdjustX(getFrameArea().Width() ); + + if ( IsTextFrame() ) + { + SwTwips nBaseOfstForFly = + static_cast<const SwTextFrame*>(this)->GetBaseOffsetForFly( bIgnoreFlysAnchoredAtThisFrame ); + if ( IsVertical() ) + aAnchor.AdjustY(nBaseOfstForFly ); + else + aAnchor.AdjustX(nBaseOfstForFly ); + + // OD 2004-03-10 #i11860# - if option 'Use former object positioning' + // is OFF, consider the lower space and the line spacing of the + // previous frame and the spacing considered for the page grid + const SwTextFrame* pThisTextFrame = static_cast<const SwTextFrame*>(this); + const SwTwips nUpperSpaceAmountConsideredForPrevFrameAndPageGrid = + pThisTextFrame->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); + if ( IsVertical() ) + { + aAnchor.AdjustX( -nUpperSpaceAmountConsideredForPrevFrameAndPageGrid ); + } + else + { + aAnchor.AdjustY(nUpperSpaceAmountConsideredForPrevFrameAndPageGrid ); + } + } + + return aAnchor; +} + +void SwFrame::DestroyImpl() +{ + mbInDtor = true; + + // accessible objects for fly and cell frames have been already disposed + // by the destructors of the derived classes. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if (IsAccessibleFrame() && !(IsFlyFrame() || IsCellFrame()) + && (GetDep() || IsTextFrame())) // sw_redlinehide: text frame may not have Dep! + { + assert(!IsTextFrame() || GetDep() || static_cast<SwTextFrame*>(this)->GetMergedPara()); + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() ) + { + SwViewShell *pVSh = pRootFrame->GetCurrShell(); + if( pVSh && pVSh->Imp() ) + { + OSL_ENSURE( !GetLower(), "Lowers should be dispose already!" ); + pVSh->Imp()->DisposeAccessibleFrame( this ); + } + } + } +#endif + + if (!m_pDrawObjs) + return; + + for (size_t i = m_pDrawObjs->size(); i; ) + { + SwAnchoredObject* pAnchoredObj = (*m_pDrawObjs)[--i]; + if ( auto pFlyFrame = pAnchoredObj->DynCastFlyFrame() ) + { + SwFrame::DestroyFrame(pFlyFrame); + } + else + { + SdrObject* pSdrObj = pAnchoredObj->DrawObj(); + SwDrawContact* pContact = + static_cast<SwDrawContact*>(pSdrObj->GetUserCall()); + OSL_ENSURE( pContact, + "<SwFrame::~SwFrame> - missing contact for drawing object" ); + if ( pContact ) + { + pContact->DisconnectObjFromLayout( pSdrObj ); + } + } + } + m_pDrawObjs.reset(); +} + +SwFrame::~SwFrame() +{ + assert(m_isInDestroy); // check that only DestroySwFrame does "delete" + assert(!IsDeleteForbidden()); // check that it's not deleted while deletes are forbidden +#if OSL_DEBUG_LEVEL > 0 + // JP 15.10.2001: for detection of access to deleted frames + mpRoot = reinterpret_cast<SwRootFrame*>(0x33333333); +#endif +} + +void SwFrame::DestroyFrame(SwFrame *const pFrame) +{ + if (pFrame) + { + pFrame->m_isInDestroy = true; + pFrame->DestroyImpl(); + assert(pFrame->mbInDtor); // check that nobody forgot to call base class + delete pFrame; + } +} + +const SwFrameFormat * SwLayoutFrame::GetFormat() const +{ + return static_cast< const SwFrameFormat * >( GetDep() ); +} + +SwFrameFormat * SwLayoutFrame::GetFormat() +{ + return static_cast< SwFrameFormat * >( GetDep() ); +} + +void SwLayoutFrame::SetFrameFormat(SwFrameFormat* pNew) +{ + if(pNew == GetFormat()) + return; + const SwFormatChg aOldFormat(GetFormat()); + pNew->Add(this); + const SwFormatChg aNewFormat(pNew); + SwClientNotify(*pNew, sw::LegacyModifyHint(&aOldFormat, &aNewFormat)); +} + +SwContentFrame::SwContentFrame( SwContentNode * const pContent, SwFrame* pSib ) : + SwFrame( pContent, pSib ), + SwFlowFrame( static_cast<SwFrame&>(*this) ) +{ + assert(!getRootFrame()->HasMergedParas() || pContent->IsCreateFrameWhenHidingRedlines()); +} + +void SwContentFrame::DestroyImpl() +{ + const SwContentNode* pCNd(dynamic_cast<SwContentNode*>(GetDep())); + if (nullptr == pCNd && IsTextFrame()) + { + pCNd = static_cast<SwTextFrame*>(this)->GetTextNodeFirst(); + } + // IsInDtor shouldn't be happening with ViewShell owning layout + assert(nullptr == pCNd || !pCNd->GetDoc().IsInDtor()); + if (nullptr != pCNd && !pCNd->GetDoc().IsInDtor()) + { + //Unregister from root if I'm still in turbo there. + SwRootFrame *pRoot = getRootFrame(); + if( pRoot && pRoot->GetTurbo() == this ) + { + pRoot->DisallowTurbo(); + pRoot->ResetTurbo(); + } + } + + SwFrame::DestroyImpl(); +} + +SwContentFrame::~SwContentFrame() +{ +} + +void SwTextFrame::RegisterToNode(SwTextNode & rNode, bool const isForceNodeAsFirst) +{ + if (isForceNodeAsFirst && m_pMergedPara) + { // nothing registered here, in particular no delete redlines (insert + // redline might end on empty node where delete rl starts, should be ok) + assert(m_pMergedPara->pFirstNode->GetIndex() + 1 == rNode.GetIndex()); + assert(rNode.GetDoc().getIDocumentRedlineAccess().GetRedlinePos( + *m_pMergedPara->pFirstNode, RedlineType::Delete) == SwRedlineTable::npos); + assert(std::find_if( + rNode.GetDoc().getIDocumentMarkAccess()->getFieldmarksBegin(), + rNode.GetDoc().getIDocumentMarkAccess()->getFieldmarksEnd(), + [this](::sw::mark::IMark const*const pMark) { + return pMark->GetMarkStart().nNode == *m_pMergedPara->pFirstNode + && pMark->GetMarkEnd().nNode != *m_pMergedPara->pFirstNode; + }) == rNode.GetDoc().getIDocumentMarkAccess()->getFieldmarksEnd()); + } + assert(&rNode != GetDep()); + assert(!m_pMergedPara + || (m_pMergedPara->pFirstNode->GetIndex() < rNode.GetIndex()) + || (rNode.GetIndex() + 1 == m_pMergedPara->pFirstNode->GetIndex())); + SwTextNode & rFirstNode( + (!isForceNodeAsFirst && m_pMergedPara && m_pMergedPara->pFirstNode->GetIndex() < rNode.GetIndex()) + ? *m_pMergedPara->pFirstNode + : rNode); + // sw_redlinehide: use New here, because the only caller also calls lcl_ChangeFootnoteRef + m_pMergedPara = sw::CheckParaRedlineMerge(*this, rFirstNode, sw::FrameMode::New); + if (!m_pMergedPara) + { + rNode.Add(this); + } +} + +void SwLayoutFrame::DestroyImpl() +{ + while (!m_VertPosOrientFramesFor.empty()) + { + SwAnchoredObject *pObj = *m_VertPosOrientFramesFor.begin(); + pObj->ClearVertPosOrientFrame(); + } + + assert(m_VertPosOrientFramesFor.empty()); + + SwFrame *pFrame = m_pLower; + + if( GetFormat() && !GetFormat()->GetDoc()->IsInDtor() ) + { + while ( pFrame ) + { + //First delete the Objs of the Frame because they can't unregister + //from the page after remove. + //We don't want to create an endless loop only because one couldn't + //unregister. + + while ( pFrame->GetDrawObjs() && pFrame->GetDrawObjs()->size() ) + { + const size_t nCnt = pFrame->GetDrawObjs()->size(); + // #i28701# + SwAnchoredObject* pAnchoredObj = (*pFrame->GetDrawObjs())[0]; + if (SwFlyFrame* pFlyFrame = pAnchoredObj->DynCastFlyFrame()) + { + SwFrame::DestroyFrame(pFlyFrame); + assert(!pFrame->GetDrawObjs() || nCnt > pFrame->GetDrawObjs()->size()); + } + else + { + pAnchoredObj->ClearTmpConsiderWrapInfluence(); + SdrObject* pSdrObj = pAnchoredObj->DrawObj(); + SwDrawContact* pContact = + static_cast<SwDrawContact*>(pSdrObj->GetUserCall()); + OSL_ENSURE( pContact, + "<SwFrame::~SwFrame> - missing contact for drawing object" ); + if ( pContact ) + { + pContact->DisconnectObjFromLayout( pSdrObj ); + } + + if ( pFrame->GetDrawObjs() && + nCnt == pFrame->GetDrawObjs()->size() ) + { + pFrame->GetDrawObjs()->Remove( *pAnchoredObj ); + } + } + } + pFrame->RemoveFromLayout(); + SwFrame::DestroyFrame(pFrame); + pFrame = m_pLower; + } + //Delete the Flys, the last one also deletes the array. + while ( GetDrawObjs() && GetDrawObjs()->size() ) + { + const size_t nCnt = GetDrawObjs()->size(); + + // #i28701# + SwAnchoredObject* pAnchoredObj = (*GetDrawObjs())[0]; + if ( auto pFlyFrame = pAnchoredObj->DynCastFlyFrame() ) + { + SwFrame::DestroyFrame(pFlyFrame); + assert(!GetDrawObjs() || nCnt > GetDrawObjs()->size()); + } + else + { + SdrObject* pSdrObj = pAnchoredObj->DrawObj(); + SwDrawContact* pContact = + static_cast<SwDrawContact*>(pSdrObj->GetUserCall()); + OSL_ENSURE( pContact, + "<SwFrame::~SwFrame> - missing contact for drawing object" ); + if ( pContact ) + { + pContact->DisconnectObjFromLayout( pSdrObj ); + } + + if ( GetDrawObjs() && nCnt == GetDrawObjs()->size() ) + { + GetDrawObjs()->Remove( *pAnchoredObj ); + } + } + } + } + else + { + while( pFrame ) + { + SwFrame *pNxt = pFrame->GetNext(); + SwFrame::DestroyFrame(pFrame); + pFrame = pNxt; + } + } + + SwFrame::DestroyImpl(); +} + +SwLayoutFrame::~SwLayoutFrame() +{ +} + +/** +|* The paintarea is the area, in which the content of a frame is allowed +|* to be displayed. This region could be larger than the printarea (getFramePrintArea()) +|* of the upper, it includes e.g. often the margin of the page. +|*/ +SwRect SwFrame::GetPaintArea() const +{ + // NEW TABLES + // Cell frames may not leave their upper: + SwRect aRect = IsRowFrame() ? GetUpper()->getFrameArea() : getFrameArea(); + const bool bVert = IsVertical(); + SwRectFn fnRect = bVert ? ( IsVertLR() ? (IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ) : fnRectHori; + SwRectFnSet aRectFnSet(this); + tools::Long nRight = (aRect.*fnRect->fnGetRight)(); + tools::Long nLeft = (aRect.*fnRect->fnGetLeft)(); + const SwFrame* pTmp = this; + bool bLeft = true; + bool bRight = true; + tools::Long nRowSpan = 0; + while( pTmp ) + { + if( pTmp->IsCellFrame() && pTmp->GetUpper() && + pTmp->GetUpper()->IsVertical() != pTmp->IsVertical() ) + nRowSpan = static_cast<const SwCellFrame*>(pTmp)->GetTabBox()->getRowSpan(); + tools::Long nTmpRight = (pTmp->getFrameArea().*fnRect->fnGetRight)(); + tools::Long nTmpLeft = (pTmp->getFrameArea().*fnRect->fnGetLeft)(); + if( pTmp->IsRowFrame() && nRowSpan > 1 ) + { + const SwFrame* pNxt = pTmp; + while( --nRowSpan > 0 && pNxt->GetNext() ) + pNxt = pNxt->GetNext(); + if( pTmp->IsVertical() ) + nTmpLeft = (pNxt->getFrameArea().*fnRect->fnGetLeft)(); + else + { + // pTmp is a row frame, but it's not vertical. + if (IsVertLRBT()) + { + // This frame cell is OK to expand towards the physical down direction. + // Physical down is left. + nTmpLeft = (pNxt->getFrameArea().*fnRect->fnGetLeft)(); + } + else + { + nTmpRight = (pNxt->getFrameArea().*fnRect->fnGetRight)(); + } + } + } + OSL_ENSURE( pTmp, "GetPaintArea lost in time and space" ); + if( pTmp->IsPageFrame() || pTmp->IsFlyFrame() || + pTmp->IsCellFrame() || pTmp->IsRowFrame() || //nobody leaves a table! + pTmp->IsRootFrame() ) + { + // BTLR is OK to expand towards the physical down direction. Physical down is left. + if( bLeft || (aRectFnSet.XDiff(nTmpLeft, nLeft) > 0 && !IsVertLRBT()) ) + nLeft = nTmpLeft; + if( bRight || aRectFnSet.XDiff(nRight, nTmpRight) > 0 ) + nRight = nTmpRight; + if( pTmp->IsPageFrame() || pTmp->IsFlyFrame() || pTmp->IsRootFrame() ) + break; + bLeft = false; + bRight = false; + } + else if( pTmp->IsColumnFrame() ) // nobody enters neighbour columns + { + bool bR2L = pTmp->IsRightToLeft(); + // the first column has _no_ influence to the left range + if( bR2L ? pTmp->GetNext() : pTmp->GetPrev() ) + { + if( bLeft || aRectFnSet.XDiff(nTmpLeft, nLeft) > 0 ) + nLeft = nTmpLeft; + bLeft = false; + } + // the last column has _no_ influence to the right range + if( bR2L ? pTmp->GetPrev() : pTmp->GetNext() ) + { + if( bRight || aRectFnSet.XDiff(nRight, nTmpRight) > 0 ) + nRight = nTmpRight; + bRight = false; + } + } + else if( bVert && pTmp->IsBodyFrame() ) + { + // Header and footer frames have always horizontal direction and + // limit the body frame. + // A previous frame of a body frame must be a header, + // the next frame of a body frame may be a footnotecontainer or + // a footer. The footnotecontainer has the same direction like + // the body frame. + if( pTmp->GetPrev() && ( bLeft || aRectFnSet.XDiff(nTmpLeft, nLeft) > 0 ) ) + { + nLeft = nTmpLeft; + bLeft = false; + } + if( pTmp->GetNext() && + ( pTmp->GetNext()->IsFooterFrame() || pTmp->GetNext()->GetNext() ) + && ( bRight || aRectFnSet.XDiff(nRight, nTmpRight) > 0 ) ) + { + nRight = nTmpRight; + bRight = false; + } + } + pTmp = pTmp->GetUpper(); + } + (aRect.*fnRect->fnSetLeft)( nLeft ); + (aRect.*fnRect->fnSetRight)( nRight ); + return aRect; +} + +/** +|* The unionframe is the framearea (getFrameArea()) of a frame expanded by the +|* printarea, if there's a negative margin at the left or right side. +|*/ +SwRect SwFrame::UnionFrame( bool bBorder ) const +{ + bool bVert = IsVertical(); + SwRectFn fnRect = bVert ? ( IsVertLR() ? (IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ) : fnRectHori; + tools::Long nLeft = (getFrameArea().*fnRect->fnGetLeft)(); + tools::Long nWidth = (getFrameArea().*fnRect->fnGetWidth)(); + tools::Long nPrtLeft = (getFramePrintArea().*fnRect->fnGetLeft)(); + tools::Long nPrtWidth = (getFramePrintArea().*fnRect->fnGetWidth)(); + SwRectFnSet aRectFnSet(this); + if (aRectFnSet.XInc(nPrtLeft, nPrtWidth) > nWidth) + nWidth = nPrtLeft + nPrtWidth; + if( nPrtLeft < 0 ) + { + nLeft += nPrtLeft; + nWidth -= nPrtLeft; + } + SwTwips nRight = aRectFnSet.XInc(nLeft, nWidth); + tools::Long nAdd = 0; + if( bBorder ) + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), this ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + const SvxBoxItem &rBox = rAttrs.GetBox(); + if ( rBox.GetLeft() ) + nLeft -= rBox.CalcLineSpace( SvxBoxItemLine::LEFT ); + else + nLeft -= rBox.GetDistance( SvxBoxItemLine::LEFT ) + 1; + if ( rBox.GetRight() ) + nAdd += rBox.CalcLineSpace( SvxBoxItemLine::RIGHT ); + else + nAdd += rBox.GetDistance( SvxBoxItemLine::RIGHT ) + 1; + if( rAttrs.GetShadow().GetLocation() != SvxShadowLocation::NONE ) + { + const SvxShadowItem &rShadow = rAttrs.GetShadow(); + nLeft -= rShadow.CalcShadowSpace( SvxShadowItemSide::LEFT ); + nAdd += rShadow.CalcShadowSpace( SvxShadowItemSide::RIGHT ); + } + } + if( IsTextFrame() && static_cast<const SwTextFrame*>(this)->HasPara() ) + { + tools::Long nTmp = static_cast<const SwTextFrame*>(this)->HangingMargin(); + if( nTmp > nAdd ) + nAdd = nTmp; + } + nWidth = aRectFnSet.XDiff(aRectFnSet.XInc(nRight, nAdd), nLeft); + SwRect aRet( getFrameArea() ); + (aRet.*fnRect->fnSetLeft)(nLeft); + (aRet.*fnRect->fnSetWidth)( nWidth ); + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/swselectionlist.cxx b/sw/source/core/layout/swselectionlist.cxx new file mode 100644 index 000000000..b7628cbac --- /dev/null +++ b/sw/source/core/layout/swselectionlist.cxx @@ -0,0 +1,83 @@ +/* -*- 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 <swselectionlist.hxx> +#include <flyfrm.hxx> +#include <ftnfrm.hxx> + +/** This class is used as parameter for functions to create a rectangular text selection +*/ + +namespace { + + /** Find the context of a given frame + + A context is the environment where text is allowed to flow. + The context is represented by + - the SwRootFrame if the frame is part of a page body + - the SwHeaderFrame if the frame is part of a page header + - the SwFooterFrame if the frame is part of a page footer + - the (master) SwFootnoteFrame if the frame is part of footnote + - the (first) SwFlyFrame if the frame is part of a (linked) fly frame + + @param pFrame + the given frame + + @return the context of the frame, represented by a SwFrame* + */ + const SwFrame* getContext( const SwFrame* pFrame ) + { + while( pFrame ) + { + if( pFrame->IsRootFrame() || pFrame->IsHeaderFrame() || pFrame->IsFooterFrame() ) + break; + if( pFrame->IsFlyFrame() ) + { + const SwFlyFrame* pFly = static_cast<const SwFlyFrame*>( pFrame ); + while( pFly->GetPrevLink() ) + pFly = pFly->GetPrevLink(); + break; + } + if( pFrame->IsFootnoteFrame() ) + { + const SwFootnoteFrame* pFootnote = static_cast<const SwFootnoteFrame*>( pFrame ); + while( pFootnote->GetMaster() ) + pFootnote = pFootnote->GetMaster(); + break; + } + pFrame = pFrame->GetUpper(); + } + return pFrame; + } +} + +SwSelectionList::SwSelectionList( const SwFrame* pInitCxt ) : + m_pContext( getContext( pInitCxt ) ) +{ +} + +bool SwSelectionList::checkContext( const SwFrame* pCheck ) +{ + pCheck = getContext( pCheck ); + if( !m_pContext ) + m_pContext = pCheck; + return m_pContext == pCheck; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/tabfrm.cxx b/sw/source/core/layout/tabfrm.cxx new file mode 100644 index 000000000..8e3a1b5dc --- /dev/null +++ b/sw/source/core/layout/tabfrm.cxx @@ -0,0 +1,6170 @@ +/* -*- 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 <config_wasm_strip.h> + +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <viewimp.hxx> +#include <fesh.hxx> +#include <swtable.hxx> +#include <deletelistener.hxx> +#include <dflyobj.hxx> +#include <anchoreddrawobject.hxx> +#include <fmtanchr.hxx> +#include <viewopt.hxx> +#include <hints.hxx> +#include <dbg_lay.hxx> +#include <ftnidx.hxx> +#include <svl/itemiter.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/boxitem.hxx> +#include <basegfx/range/b1drange.hxx> +#include <fmtlsplt.hxx> +#include <fmtrowsplt.hxx> +#include <fmtsrnd.hxx> +#include <fmtornt.hxx> +#include <fmtpdsc.hxx> +#include <fmtfsize.hxx> +#include <swtblfmt.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <cellfrm.hxx> +#include <flyfrms.hxx> +#include <txtfrm.hxx> +#include <ftnfrm.hxx> +#include <notxtfrm.hxx> +#include <htmltbl.hxx> +#include <sectfrm.hxx> +#include <fmtfollowtextflow.hxx> +#include <sortedobjs.hxx> +#include <objectformatter.hxx> +#include <layouter.hxx> +#include <calbck.hxx> +#include <DocumentSettingManager.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <ndtxt.hxx> +#include <frameformats.hxx> + +using namespace ::com::sun::star; + +SwTabFrame::SwTabFrame( SwTable &rTab, SwFrame* pSib ) + : SwLayoutFrame( rTab.GetFrameFormat(), pSib ) + , SwFlowFrame( static_cast<SwFrame&>(*this) ) + , m_pTable( &rTab ) + , m_bComplete(false) + , m_bCalcLowers(false) + , m_bLowersFormatted(false) + , m_bLockBackMove(false) + , m_bResizeHTMLTable(false) + , m_bONECalcLowers(false) + , m_bHasFollowFlowLine(false) + , m_bIsRebuildLastLine(false) + , m_bRestrictTableGrowth(false) + , m_bRemoveFollowFlowLinePending(false) + , m_bConsiderObjsForMinCellHeight(true) + , m_bObjsDoesFit(true) + , m_bInRecalcLowerRow(false) +{ + mbFixSize = false; //Don't fall for import filter again. + mnFrameType = SwFrameType::Tab; + + //Create the lines and insert them. + const SwTableLines &rLines = rTab.GetTabLines(); + SwFrame *pTmpPrev = nullptr; + bool bHiddenRedlines = getRootFrame()->IsHideRedlines() && + !GetFormat()->GetDoc()->getIDocumentRedlineAccess().GetRedlineTable().empty(); + SwRedlineTable::size_type nRedlinePos = 0; + for ( size_t i = 0; i < rLines.size(); ++i ) + { + // skip lines deleted with track changes + if ( bHiddenRedlines && rLines[i]->IsDeleted(nRedlinePos) ) + continue; + + SwRowFrame *pNew = new SwRowFrame( *rLines[i], this ); + if( pNew->Lower() ) + { + pNew->InsertBehind( this, pTmpPrev ); + pTmpPrev = pNew; + } + else + SwFrame::DestroyFrame(pNew); + } + OSL_ENSURE( Lower() && Lower()->IsRowFrame(), "SwTabFrame::SwTabFrame: No rows." ); +} + +SwTabFrame::SwTabFrame( SwTabFrame &rTab ) + : SwLayoutFrame( rTab.GetFormat(), &rTab ) + , SwFlowFrame( static_cast<SwFrame&>(*this) ) + , m_pTable( rTab.GetTable() ) + , m_bComplete(false) + , m_bCalcLowers(false) + , m_bLowersFormatted(false) + , m_bLockBackMove(false) + , m_bResizeHTMLTable(false) + , m_bONECalcLowers(false) + , m_bHasFollowFlowLine(false) + , m_bIsRebuildLastLine(false) + , m_bRestrictTableGrowth(false) + , m_bRemoveFollowFlowLinePending(false) + , m_bConsiderObjsForMinCellHeight(true) + , m_bObjsDoesFit(true) + , m_bInRecalcLowerRow(false) +{ + mbFixSize = false; //Don't fall for import filter again. + mnFrameType = SwFrameType::Tab; + + SetFollow( rTab.GetFollow() ); + rTab.SetFollow( this ); +} + +void SwTabFrame::DestroyImpl() +{ + // There is some terrible code in fetab.cxx, that + // caches pointers to SwTabFrames. + ::ClearFEShellTabCols(*GetFormat()->GetDoc(), this); + + SwLayoutFrame::DestroyImpl(); +} + +SwTabFrame::~SwTabFrame() +{ +} + +void SwTabFrame::JoinAndDelFollows() +{ + SwTabFrame *pFoll = GetFollow(); + if ( pFoll->HasFollow() ) + pFoll->JoinAndDelFollows(); + pFoll->Cut(); + SetFollow( pFoll->GetFollow() ); + SwFrame::DestroyFrame(pFoll); +} + +void SwTabFrame::RegistFlys() +{ + OSL_ENSURE( Lower() && Lower()->IsRowFrame(), "No rows." ); + + SwPageFrame *pPage = FindPageFrame(); + if ( pPage ) + { + SwRowFrame *pRow = static_cast<SwRowFrame*>(Lower()); + do + { + pRow->RegistFlys( pPage ); + pRow = static_cast<SwRowFrame*>(pRow->GetNext()); + } while ( pRow ); + } +} + +static void SwInvalidateAll( SwFrame *pFrame, tools::Long nBottom ); +static void lcl_RecalcRow( SwRowFrame& rRow, tools::Long nBottom ); +static bool lcl_ArrangeLowers( SwLayoutFrame *pLay, tools::Long lYStart, bool bInva ); +// #i26945# - add parameter <_bOnlyRowsAndCells> to control +// that only row and cell frames are formatted. +static bool lcl_InnerCalcLayout( SwFrame *pFrame, + tools::Long nBottom, + bool _bOnlyRowsAndCells = false ); +// OD 2004-02-18 #106629# - correct type of 1st parameter +// #i26945# - add parameter <_bConsiderObjs> in order to +// control, if floating screen objects have to be considered for the minimal +// cell height. +static SwTwips lcl_CalcMinRowHeight( const SwRowFrame *pRow, + const bool _bConsiderObjs ); +static SwTwips lcl_CalcTopAndBottomMargin( const SwLayoutFrame&, const SwBorderAttrs& ); + +static SwTwips lcl_calcHeightOfRowBeforeThisFrame(const SwRowFrame& rRow); + +static SwTwips lcl_GetHeightOfRows( const SwFrame* pStart, tools::Long nCount ) +{ + if ( !nCount || !pStart) + return 0; + + SwTwips nRet = 0; + SwRectFnSet aRectFnSet(pStart); + while ( pStart && nCount > 0 ) + { + nRet += aRectFnSet.GetHeight(pStart->getFrameArea()); + pStart = pStart->GetNext(); + --nCount; + } + + return nRet; +} + +// Local helper function to insert a new follow flow line +static SwRowFrame* lcl_InsertNewFollowFlowLine( SwTabFrame& rTab, const SwFrame& rTmpRow, bool bRowSpanLine ) +{ + OSL_ENSURE( rTmpRow.IsRowFrame(), "No row frame to copy for FollowFlowLine" ); + const SwRowFrame& rRow = static_cast<const SwRowFrame&>(rTmpRow); + + rTab.SetFollowFlowLine( true ); + SwRowFrame *pFollowFlowLine = new SwRowFrame(*rRow.GetTabLine(), &rTab, false ); + pFollowFlowLine->SetRowSpanLine( bRowSpanLine ); + SwFrame* pFirstRow = rTab.GetFollow()->GetFirstNonHeadlineRow(); + pFollowFlowLine->InsertBefore( rTab.GetFollow(), pFirstRow ); + return pFollowFlowLine; +} + +// #i26945# - local helper function to invalidate all lower +// objects. By parameter <_bMoveObjsOutOfRange> it can be controlled, if +// additionally the objects are moved 'out of range'. +static void lcl_InvalidateLowerObjs( SwLayoutFrame& _rLayoutFrame, + const bool _bMoveObjsOutOfRange = false, + SwPageFrame* _pPageFrame = nullptr ) +{ + // determine page frame, if needed + if ( !_pPageFrame ) + { + _pPageFrame = _rLayoutFrame.FindPageFrame(); + OSL_ENSURE( _pPageFrame, + "<lcl_InvalidateLowerObjs(..)> - missing page frame -> no move of lower objects out of range" ); + if ( !_pPageFrame ) + { + return; + } + } + + // loop on lower frames + SwFrame* pLowerFrame = _rLayoutFrame.Lower(); + while ( pLowerFrame ) + { + if ( pLowerFrame->IsLayoutFrame() ) + { + ::lcl_InvalidateLowerObjs( *static_cast<SwLayoutFrame*>(pLowerFrame), + _bMoveObjsOutOfRange, _pPageFrame ); + } + if ( pLowerFrame->GetDrawObjs() ) + { + for (size_t i = 0, nCount = pLowerFrame->GetDrawObjs()->size(); i < nCount; ++i) + { + SwAnchoredObject* pAnchoredObj = (*pLowerFrame->GetDrawObjs())[i]; + + // invalidate position of anchored object + pAnchoredObj->SetTmpConsiderWrapInfluence( false ); + pAnchoredObj->SetConsiderForTextWrap( false ); + pAnchoredObj->UnlockPosition(); + pAnchoredObj->InvalidateObjPos(); + + SwFlyFrame *pFly = pAnchoredObj->DynCastFlyFrame(); + + // move anchored object 'out of range' + if ( _bMoveObjsOutOfRange ) + { + // indicate, that positioning is progress to avoid + // modification of the anchored object resp. it's attributes + // due to the movement + SwObjPositioningInProgress aObjPosInProgress( *pAnchoredObj ); + pAnchoredObj->SetObjLeft( _pPageFrame->getFrameArea().Right() ); + // #115759# - reset character rectangle, + // top of line and relative position in order to assure, + // that anchored object is correctly positioned. + pAnchoredObj->ClearCharRectAndTopOfLine(); + pAnchoredObj->SetCurrRelPos( Point( 0, 0 ) ); + if ( pAnchoredObj->GetFrameFormat().GetAnchor().GetAnchorId() + == RndStdIds::FLY_AS_CHAR ) + { + pAnchoredObj->AnchorFrame() + ->Prepare( PrepareHint::FlyFrameAttributesChanged, + &(pAnchoredObj->GetFrameFormat()) ); + } + if ( pFly != nullptr ) + { + pFly->GetVirtDrawObj()->SetBoundAndSnapRectsDirty(); + pFly->GetVirtDrawObj()->SetChanged(); + } + } + + // If anchored object is a fly frame, invalidate its lower objects + if ( pFly != nullptr ) + { + ::lcl_InvalidateLowerObjs( *pFly, _bMoveObjsOutOfRange, _pPageFrame ); + } + } + } + pLowerFrame = pLowerFrame->GetNext(); + } +} + +// Local helper function to shrink all lowers of pRow to 0 height +static void lcl_ShrinkCellsAndAllContent( SwRowFrame& rRow ) +{ + SwCellFrame* pCurrMasterCell = static_cast<SwCellFrame*>(rRow.Lower()); + SwRectFnSet aRectFnSet(pCurrMasterCell); + + bool bAllCellsCollapsed = true; + while ( pCurrMasterCell ) + { + // NEW TABLES + SwCellFrame& rToAdjust = pCurrMasterCell->GetTabBox()->getRowSpan() < 1 ? + const_cast<SwCellFrame&>(pCurrMasterCell->FindStartEndOfRowSpanCell( true )) : + *pCurrMasterCell; + + // #i26945# + // all lowers should have the correct position + lcl_ArrangeLowers( &rToAdjust, + aRectFnSet.GetPrtTop(rToAdjust), + false ); + // TODO: Optimize number of frames which are set to 0 height + // we have to start with the last lower frame, otherwise + // the shrink will not shrink the current cell + SwFrame* pTmp = rToAdjust.GetLastLower(); + bool bAllLowersCollapsed = true; + + if ( pTmp && pTmp->IsRowFrame() ) + { + SwRowFrame* pTmpRow = static_cast<SwRowFrame*>(pTmp); + lcl_ShrinkCellsAndAllContent( *pTmpRow ); + } + else + { + // TODO: Optimize number of frames which are set to 0 height + while ( pTmp ) + { + // the frames have to be shrunk + if ( pTmp->IsTabFrame() ) + { + SwRowFrame* pTmpRow = static_cast<SwRowFrame*>(static_cast<SwTabFrame*>(pTmp)->Lower()); + bool bAllRowsCollapsed = true; + + while ( pTmpRow ) + { + lcl_ShrinkCellsAndAllContent( *pTmpRow ); + + if (aRectFnSet.GetHeight(pTmpRow->getFrameArea()) > 0) + bAllRowsCollapsed = false; + + pTmpRow = static_cast<SwRowFrame*>(pTmpRow->GetNext()); + } + + if (bAllRowsCollapsed) + { + // All rows of this table have 0 height -> set height of the table itself as well. + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pTmp); + aRectFnSet.SetHeight(aFrm, 0); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pTmp); + aRectFnSet.SetTop(aPrt, 0); + aRectFnSet.SetHeight(aPrt, 0); + } + else + bAllLowersCollapsed = false; + } + else + { + pTmp->Shrink(aRectFnSet.GetHeight(pTmp->getFrameArea())); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pTmp); + aRectFnSet.SetTop(aPrt, 0); + aRectFnSet.SetHeight(aPrt, 0); + + if (aRectFnSet.GetHeight(pTmp->getFrameArea()) > 0) + { + bAllLowersCollapsed = false; + } + } + + pTmp = pTmp->GetPrev(); + } + + // all lowers should have the correct position + lcl_ArrangeLowers( &rToAdjust, + aRectFnSet.GetPrtTop(rToAdjust), + false ); + } + + if (bAllLowersCollapsed) + { + // All lower frame of this cell have 0 height -> set height of the cell itself as well. + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pCurrMasterCell); + aRectFnSet.SetHeight(aFrm, 0); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pCurrMasterCell); + aRectFnSet.SetTop(aPrt, 0); + aRectFnSet.SetHeight(aPrt, 0); + } + else + bAllCellsCollapsed = false; + + pCurrMasterCell = static_cast<SwCellFrame*>(pCurrMasterCell->GetNext()); + } + + if (bAllCellsCollapsed) + { + // All cells have 0 height -> set height of row as well. + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(rRow); + aRectFnSet.SetHeight(aFrm, 0); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(rRow); + aRectFnSet.SetTop(aPrt, 0); + aRectFnSet.SetHeight(aPrt, 0); + } +} + +// Local helper function to move the content from rSourceLine to rDestLine +// The content is inserted behind the last content in the corresponding +// cell in rDestLine. +static void lcl_MoveRowContent( SwRowFrame& rSourceLine, SwRowFrame& rDestLine ) +{ + SwCellFrame* pCurrDestCell = static_cast<SwCellFrame*>(rDestLine.Lower()); + SwCellFrame* pCurrSourceCell = static_cast<SwCellFrame*>(rSourceLine.Lower()); + + // Move content of follow cells into master cells + while ( pCurrSourceCell ) + { + if ( pCurrSourceCell->Lower() && pCurrSourceCell->Lower()->IsRowFrame() ) + { + SwRowFrame* pTmpSourceRow = static_cast<SwRowFrame*>(pCurrSourceCell->Lower()); + while ( pTmpSourceRow ) + { + // #125926# Attention! It is possible, + // that pTmpSourceRow->IsFollowFlowRow() but pTmpDestRow + // cannot be found. In this case, we have to move the complete + // row. + SwRowFrame* pTmpDestRow = static_cast<SwRowFrame*>(pCurrDestCell->Lower()); + + if ( pTmpSourceRow->IsFollowFlowRow() && pTmpDestRow ) + { + // move content from follow flow row to pTmpDestRow: + while ( pTmpDestRow->GetNext() ) + pTmpDestRow = static_cast<SwRowFrame*>(pTmpDestRow->GetNext()); + + assert(pTmpDestRow->GetFollowRow() == pTmpSourceRow); + + lcl_MoveRowContent( *pTmpSourceRow, *pTmpDestRow ); + pTmpDestRow->SetFollowRow( pTmpSourceRow->GetFollowRow() ); + pTmpSourceRow->RemoveFromLayout(); + SwFrame::DestroyFrame(pTmpSourceRow); + } + else + { + // move complete row: + pTmpSourceRow->RemoveFromLayout(); + pTmpSourceRow->InsertBefore( pCurrDestCell, nullptr ); + } + + pTmpSourceRow = static_cast<SwRowFrame*>(pCurrSourceCell->Lower()); + } + } + else + { + SwFrame *pTmp = ::SaveContent( pCurrSourceCell ); + if ( pTmp ) + { + // NEW TABLES + SwCellFrame* pDestCell = pCurrDestCell; + if ( pDestCell->GetTabBox()->getRowSpan() < 1 ) + pDestCell = & const_cast<SwCellFrame&>(pDestCell->FindStartEndOfRowSpanCell( true )); + + // Find last content + SwFrame* pFrame = pDestCell->GetLastLower(); + ::RestoreContent( pTmp, pDestCell, pFrame ); + } + } + pCurrDestCell = static_cast<SwCellFrame*>(pCurrDestCell->GetNext()); + pCurrSourceCell = static_cast<SwCellFrame*>(pCurrSourceCell->GetNext()); + } +} + +// Local helper function to move all footnotes in rRowFrame from +// the footnote boss of rSource to the footnote boss of rDest. +static void lcl_MoveFootnotes( SwTabFrame& rSource, SwTabFrame& rDest, SwLayoutFrame& rRowFrame ) +{ + if ( !rSource.GetFormat()->GetDoc()->GetFootnoteIdxs().empty() ) + { + SwFootnoteBossFrame* pOldBoss = rSource.FindFootnoteBossFrame( true ); + SwFootnoteBossFrame* pNewBoss = rDest.FindFootnoteBossFrame( true ); + rRowFrame.MoveLowerFootnotes( nullptr, pOldBoss, pNewBoss, true ); + } +} + +// Local helper function to handle nested table cells before the split process +static void lcl_PreprocessRowsInCells( SwTabFrame& rTab, SwRowFrame& rLastLine, + SwRowFrame& rFollowFlowLine, SwTwips nRemain ) +{ + SwCellFrame* pCurrLastLineCell = static_cast<SwCellFrame*>(rLastLine.Lower()); + SwCellFrame* pCurrFollowFlowLineCell = static_cast<SwCellFrame*>(rFollowFlowLine.Lower()); + + SwRectFnSet aRectFnSet(pCurrLastLineCell); + + // Move content of follow cells into master cells + while ( pCurrLastLineCell ) + { + if ( pCurrLastLineCell->Lower() && pCurrLastLineCell->Lower()->IsRowFrame() ) + { + SwTwips nTmpCut = nRemain; + SwRowFrame* pTmpLastLineRow = static_cast<SwRowFrame*>(pCurrLastLineCell->Lower()); + + // #i26945# + SwTwips nCurrentHeight = + lcl_CalcMinRowHeight( pTmpLastLineRow, + rTab.IsConsiderObjsForMinCellHeight() ); + while ( pTmpLastLineRow->GetNext() && nTmpCut > nCurrentHeight ) + { + nTmpCut -= nCurrentHeight; + pTmpLastLineRow = static_cast<SwRowFrame*>(pTmpLastLineRow->GetNext()); + // #i26945# + nCurrentHeight = + lcl_CalcMinRowHeight( pTmpLastLineRow, + rTab.IsConsiderObjsForMinCellHeight() ); + } + + // pTmpLastLineRow does not fit to the line or it is the last line + // Check if we can move pTmpLastLineRow to the follow table, + // or if we have to split the line: + bool bTableLayoutTooComplex = false; + tools::Long nMinHeight = 0; + + // We have to take into account: + // 1. The fixed height of the row + // 2. The borders of the cells inside the row + // 3. The minimum height of the row + if ( pTmpLastLineRow->HasFixSize() ) + nMinHeight = aRectFnSet.GetHeight(pTmpLastLineRow->getFrameArea()); + else + { + { + const SwFormatFrameSize &rSz = pTmpLastLineRow->GetFormat()->GetFrameSize(); + if ( rSz.GetHeightSizeType() == SwFrameSize::Minimum ) + nMinHeight = rSz.GetHeight() - lcl_calcHeightOfRowBeforeThisFrame(*pTmpLastLineRow); + } + + SwFrame* pCell = pTmpLastLineRow->Lower(); + while ( pCell ) + { + if ( static_cast<SwCellFrame*>(pCell)->Lower() && + static_cast<SwCellFrame*>(pCell)->Lower()->IsRowFrame() ) + { + bTableLayoutTooComplex = true; + break; + } + + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pCell ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + nMinHeight = std::max( nMinHeight, tools::Long(lcl_CalcTopAndBottomMargin( *static_cast<SwLayoutFrame*>(pCell), rAttrs )) ); + pCell = pCell->GetNext(); + } + } + + // 1. Case: + // The line completely fits into the master table. + // Nevertheless, we build a follow (otherwise painting problems + // with empty cell). + + // 2. Case: + // The line has to be split, the minimum height still fits into + // the master table, and the table structure is not too complex. + if ( nTmpCut > nCurrentHeight || + ( pTmpLastLineRow->IsRowSplitAllowed() && + !bTableLayoutTooComplex && nMinHeight < nTmpCut ) ) + { + // The line has to be split: + SwRowFrame* pNewRow = new SwRowFrame( *pTmpLastLineRow->GetTabLine(), &rTab, false ); + pNewRow->SetFollowFlowRow( true ); + pNewRow->SetFollowRow( pTmpLastLineRow->GetFollowRow() ); + pTmpLastLineRow->SetFollowRow( pNewRow ); + pNewRow->InsertBehind( pCurrFollowFlowLineCell, nullptr ); + pTmpLastLineRow = static_cast<SwRowFrame*>(pTmpLastLineRow->GetNext()); + } + + // The following lines have to be moved: + while ( pTmpLastLineRow ) + { + SwRowFrame* pTmp = static_cast<SwRowFrame*>(pTmpLastLineRow->GetNext()); + lcl_MoveFootnotes( rTab, *rTab.GetFollow(), *pTmpLastLineRow ); + pTmpLastLineRow->RemoveFromLayout(); + pTmpLastLineRow->InsertBefore( pCurrFollowFlowLineCell, nullptr ); + pTmpLastLineRow->Shrink( aRectFnSet.GetHeight(pTmpLastLineRow->getFrameArea()) ); + pCurrFollowFlowLineCell->Grow( aRectFnSet.GetHeight(pTmpLastLineRow->getFrameArea()) ); + pTmpLastLineRow = pTmp; + } + } + + pCurrLastLineCell = static_cast<SwCellFrame*>(pCurrLastLineCell->GetNext()); + pCurrFollowFlowLineCell = static_cast<SwCellFrame*>(pCurrFollowFlowLineCell->GetNext()); + } +} + +// Local helper function to handle nested table cells after the split process +static void lcl_PostprocessRowsInCells( SwTabFrame& rTab, SwRowFrame& rLastLine ) +{ + SwCellFrame* pCurrMasterCell = static_cast<SwCellFrame*>(rLastLine.Lower()); + while ( pCurrMasterCell ) + { + if ( pCurrMasterCell->Lower() && + pCurrMasterCell->Lower()->IsRowFrame() ) + { + SwRowFrame* pRowFrame = static_cast<SwRowFrame*>(pCurrMasterCell->GetLastLower()); + + if ( nullptr != pRowFrame->GetPrev() && !pRowFrame->ContainsContent() ) + { + OSL_ENSURE( pRowFrame->GetFollowRow(), "Deleting row frame without follow" ); + + // The footnotes have to be moved: + lcl_MoveFootnotes( rTab, *rTab.GetFollow(), *pRowFrame ); + pRowFrame->Cut(); + SwRowFrame* pFollowRow = pRowFrame->GetFollowRow(); + pRowFrame->Paste( pFollowRow->GetUpper(), pFollowRow ); + pRowFrame->SetFollowRow( pFollowRow->GetFollowRow() ); + lcl_MoveRowContent( *pFollowRow, *pRowFrame ); + pFollowRow->Cut(); + SwFrame::DestroyFrame(pFollowRow); + ::SwInvalidateAll( pCurrMasterCell, LONG_MAX ); + } + } + + pCurrMasterCell = static_cast<SwCellFrame*>(pCurrMasterCell->GetNext()); + } +} + +// Local helper function to re-calculate the split line. +inline void TableSplitRecalcLock( SwFlowFrame *pTab ) { pTab->LockJoin(); } +inline void TableSplitRecalcUnlock( SwFlowFrame *pTab ) { pTab->UnlockJoin(); } + +static bool lcl_RecalcSplitLine( SwRowFrame& rLastLine, SwRowFrame& rFollowLine, + SwTwips nRemainingSpaceForLastRow, SwTwips nAlreadyFree ) +{ + bool bRet = true; + + vcl::RenderContext* pRenderContext = rLastLine.getRootFrame()->GetCurrShell()->GetOut(); + SwTabFrame& rTab = static_cast<SwTabFrame&>(*rLastLine.GetUpper()); + SwRectFnSet aRectFnSet(rTab.GetUpper()); + SwTwips nCurLastLineHeight = aRectFnSet.GetHeight(rLastLine.getFrameArea()); + + // If there are nested cells in rLastLine, the recalculation of the last + // line needs some preprocessing. + lcl_PreprocessRowsInCells( rTab, rLastLine, rFollowLine, nRemainingSpaceForLastRow ); + + // Here the recalculation process starts: + rTab.SetRebuildLastLine( true ); + // #i26945# + rTab.SetDoesObjsFit( true ); + + // #i26945# - invalidate and move floating screen + // objects 'out of range' + ::lcl_InvalidateLowerObjs( rLastLine, true ); + + // manipulate row and cell sizes + + // #i26945# - Do *not* consider floating screen objects + // for the minimal cell height. + rTab.SetConsiderObjsForMinCellHeight( false ); + ::lcl_ShrinkCellsAndAllContent( rLastLine ); + rTab.SetConsiderObjsForMinCellHeight( true ); + + // invalidate last line + ::SwInvalidateAll( &rLastLine, LONG_MAX ); + + // Shrink the table to account for the shrunk last row, as well as lower rows + // that had been moved to follow table in SwTabFrame::Split. + // It will grow later when last line will recalc its height. + rTab.Shrink(nAlreadyFree + nCurLastLineHeight - nRemainingSpaceForLastRow + 1); + + // Lock this tab frame and its follow + bool bUnlockMaster = false; + SwFlowFrame * pFollow = nullptr; + SwTabFrame* pMaster = rTab.IsFollow() ? rTab.FindMaster() : nullptr; + if ( pMaster && !pMaster->IsJoinLocked() ) + { + bUnlockMaster = true; + ::TableSplitRecalcLock( pMaster ); + } + if ( !rTab.GetFollow()->IsJoinLocked() ) + { + pFollow = rTab.GetFollow(); + ::TableSplitRecalcLock( pFollow ); + } + + bool bInSplit = rLastLine.IsInSplit(); + rLastLine.SetInSplit(); + + // Do the recalculation + lcl_RecalcRow( rLastLine, LONG_MAX ); + // #115759# - force a format of the last line in order to + // get the correct height. + rLastLine.InvalidateSize(); + rLastLine.Calc(pRenderContext); + + rLastLine.SetInSplit(bInSplit); + + // Unlock this tab frame and its follow + if ( pFollow ) + ::TableSplitRecalcUnlock( pFollow ); + if ( bUnlockMaster ) + ::TableSplitRecalcUnlock( pMaster ); + + // If there are nested cells in rLastLine, the recalculation of the last + // line needs some postprocessing. + lcl_PostprocessRowsInCells( rTab, rLastLine ); + + // Do a couple of checks on the current situation. + + // If we are not happy with the current situation we return false. + // This will start a new try to split the table, this time we do not + // try to split the table rows. + + // 1. Check if table fits to its upper. + // #i26945# - include check, if objects fit + const SwTwips nDistanceToUpperPrtBottom = + aRectFnSet.BottomDist(rTab.getFrameArea(), aRectFnSet.GetPrtBottom(*rTab.GetUpper())); + // tdf#125685 ignore footnotes that are anchored in follow-table of this + // table - if split is successful they move to the next page/column anyway + assert(rTab.GetFollow() == rFollowLine.GetUpper()); + SwTwips nFollowFootnotes(0); + // actually there should always be a boss frame, except if "this" isn't + // connected to a page yet; not sure if that can happen + if (SwFootnoteBossFrame const*const pBoss = rTab.FindFootnoteBossFrame()) + { + if (SwFootnoteContFrame const*const pCont = pBoss->FindFootnoteCont()) + { + for (SwFootnoteFrame const* pFootnote = static_cast<SwFootnoteFrame const*>(pCont->Lower()); + pFootnote != nullptr; + pFootnote = static_cast<SwFootnoteFrame const*>(pFootnote->GetNext())) + { + SwContentFrame const*const pAnchor = pFootnote->GetRef(); + SwTabFrame const* pTab = pAnchor->FindTabFrame(); + if (pTab) + { + while (pTab->GetUpper()->IsInTab()) + { + pTab = pTab->GetUpper()->FindTabFrame(); + } + // TODO currently do this only for top-level tables? + // otherwise would need to check rTab's follow and any upper table's follow? + if (pTab == rTab.GetFollow()) + { + nFollowFootnotes += aRectFnSet.GetHeight(pFootnote->getFrameArea()); + } + } + } + } + } + if (nDistanceToUpperPrtBottom + nFollowFootnotes < 0 || !rTab.DoesObjsFit()) + bRet = false; + + // 2. Check if each cell in the last line has at least one content frame. + + // Note: a FollowFlowRow may contains empty cells! + if ( bRet ) + { + if ( !rLastLine.IsInFollowFlowRow() ) + { + SwCellFrame* pCurrMasterCell = static_cast<SwCellFrame*>(rLastLine.Lower()); + while ( pCurrMasterCell ) + { + if ( !pCurrMasterCell->ContainsContent() && pCurrMasterCell->GetTabBox()->getRowSpan() >= 1 ) + { + bRet = false; + break; + } + pCurrMasterCell = static_cast<SwCellFrame*>(pCurrMasterCell->GetNext()); + } + } + } + + // 3. Check if last line does not contain any content: + if ( bRet ) + { + if ( !rLastLine.ContainsContent() ) + { + bRet = false; + } + } + + // 4. Check if follow flow line does not contain content: + if ( bRet ) + { + if ( !rFollowLine.IsRowSpanLine() && !rFollowLine.ContainsContent() ) + { + bRet = false; + } + } + + if ( bRet ) + { + // Everything looks fine. Splitting seems to be successful. We invalidate + // rFollowLine to force a new formatting. + ::SwInvalidateAll( &rFollowLine, LONG_MAX ); + } + else + { + // Splitting the table row gave us an unexpected result. + // Everything has to be prepared for a second try to split + // the table, this time without splitting the row. + ::SwInvalidateAll( &rLastLine, LONG_MAX ); + } + + rTab.SetRebuildLastLine( false ); + // #i26945# + rTab.SetDoesObjsFit( true ); + + return bRet; +} + +// Sets the correct height for all spanned cells +static void lcl_AdjustRowSpanCells( SwRowFrame* pRow ) +{ + SwRectFnSet aRectFnSet(pRow); + SwCellFrame* pCellFrame = static_cast<SwCellFrame*>(pRow->GetLower()); + while ( pCellFrame ) + { + const tools::Long nLayoutRowSpan = pCellFrame->GetLayoutRowSpan(); + if ( nLayoutRowSpan > 1 ) + { + // calculate height of cell: + const tools::Long nNewCellHeight = lcl_GetHeightOfRows( pRow, nLayoutRowSpan ); + const tools::Long nDiff = nNewCellHeight - aRectFnSet.GetHeight(pCellFrame->getFrameArea()); + + if ( nDiff ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pCellFrame); + aRectFnSet.AddBottom(aFrm, nDiff); + } + } + + pCellFrame = static_cast<SwCellFrame*>(pCellFrame->GetNext()); + } +} + +// Returns the maximum layout row span of the row +// Looking for the next row that contains no covered cells: +static tools::Long lcl_GetMaximumLayoutRowSpan( const SwRowFrame& rRow ) +{ + tools::Long nRet = 1; + + const SwRowFrame* pCurrentRowFrame = static_cast<const SwRowFrame*>(rRow.GetNext()); + bool bNextRow = false; + + while ( pCurrentRowFrame ) + { + // if there is any covered cell, we proceed to the next row frame + const SwCellFrame* pLower = static_cast<const SwCellFrame*>( pCurrentRowFrame->Lower()); + while ( pLower ) + { + if ( pLower->GetTabBox()->getRowSpan() < 0 ) + { + ++nRet; + bNextRow = true; + break; + } + pLower = static_cast<const SwCellFrame*>(pLower->GetNext()); + } + pCurrentRowFrame = bNextRow ? + static_cast<const SwRowFrame*>(pCurrentRowFrame->GetNext() ) : + nullptr; + } + + return nRet; +} + +// Function to remove the FollowFlowLine of rTab. +// The content of the FollowFlowLine is moved to the associated line in the +// master table. +bool SwTabFrame::RemoveFollowFlowLine() +{ + // find FollowFlowLine + SwTabFrame *pFoll = GetFollow(); + SwRowFrame* pFollowFlowLine = pFoll ? pFoll->GetFirstNonHeadlineRow() : nullptr; + + // find last row in master + SwFrame* pLastLine = GetLastLower(); + + OSL_ENSURE( HasFollowFlowLine() && + pFollowFlowLine && + pLastLine, "There should be a flowline in the follow" ); + + // #140081# Make code robust. + if ( !pFollowFlowLine || !pLastLine ) + return true; + if (pFollowFlowLine->IsDeleteForbidden()) + { + SAL_WARN("sw.layout", "Cannot remove in-use Follow Flow Line"); + return false; + } + + // We have to reset the flag here, because lcl_MoveRowContent + // calls a GrowFrame(), which has a different behavior if + // this flag is set. + SetFollowFlowLine( false ); + + // Move content + lcl_MoveRowContent( *pFollowFlowLine, *static_cast<SwRowFrame*>(pLastLine) ); + + // NEW TABLES + // If a row span follow flow line is removed, we want to move the whole span + // to the master: + tools::Long nRowsToMove = lcl_GetMaximumLayoutRowSpan( *pFollowFlowLine ); + + if ( nRowsToMove > 1 ) + { + SwRectFnSet aRectFnSet(this); + SwFrame* pRow = pFollowFlowLine->GetNext(); + SwFrame* pInsertBehind = GetLastLower(); + SwTwips nGrow = 0; + + while ( pRow && nRowsToMove-- > 1 ) + { + SwFrame* pNxt = pRow->GetNext(); + nGrow += aRectFnSet.GetHeight(pRow->getFrameArea()); + + // The footnotes have to be moved: + lcl_MoveFootnotes( *GetFollow(), *this, static_cast<SwRowFrame&>(*pRow) ); + + pRow->RemoveFromLayout(); + pRow->InsertBehind( this, pInsertBehind ); + pRow->InvalidateAll_(); + pRow->CheckDirChange(); + pInsertBehind = pRow; + pRow = pNxt; + } + + SwFrame* pFirstRow = Lower(); + while ( pFirstRow ) + { + lcl_AdjustRowSpanCells( static_cast<SwRowFrame*>(pFirstRow) ); + pFirstRow = pFirstRow->GetNext(); + } + + Grow( nGrow ); + GetFollow()->Shrink( nGrow ); + } + + bool bJoin = !pFollowFlowLine->GetNext(); + pFollowFlowLine->Cut(); + SwFrame::DestroyFrame(pFollowFlowLine); + + return bJoin; +} + +// #i26945# - Floating screen objects are no longer searched. +static bool lcl_FindSectionsInRow( const SwRowFrame& rRow ) +{ + bool bRet = false; + const SwCellFrame* pLower = static_cast<const SwCellFrame*>(rRow.Lower()); + while ( pLower ) + { + if ( pLower->IsVertical() != rRow.IsVertical() ) + return true; + + const SwFrame* pTmpFrame = pLower->Lower(); + while ( pTmpFrame ) + { + if ( pTmpFrame->IsRowFrame() ) + { + bRet = lcl_FindSectionsInRow( *static_cast<const SwRowFrame*>(pTmpFrame) ); + } + else + { + // #i26945# - search only for sections + if (pTmpFrame->IsSctFrame()) + { + bRet = true; + + if (!rRow.IsInSct()) + { + // This row is not in a section. + if (const SwFrame* pSectionLower = pTmpFrame->GetLower()) + { + if (!pSectionLower->IsColumnFrame()) + { + // Section has a single column only, try to + // split that. + bRet = false; + + for (const SwFrame* pFrame = pSectionLower; pFrame; pFrame = pFrame->GetNext()) + { + if (pFrame->IsTabFrame()) + { + // Section contains a table, no split in that case. + bRet = true; + break; + } + } + } + } + } + } + } + + if ( bRet ) + return true; + pTmpFrame = pTmpFrame->GetNext(); + } + + pLower = static_cast<const SwCellFrame*>(pLower->GetNext()); + } + return bRet; +} + +bool SwTabFrame::Split( const SwTwips nCutPos, bool bTryToSplit, bool bTableRowKeep ) +{ + bool bRet = true; + + SwRectFnSet aRectFnSet(this); + + // #i26745# - format row and cell frames of table + { + Lower()->InvalidatePos_(); + // #i43913# - correction + // call method <lcl_InnerCalcLayout> with first lower. + lcl_InnerCalcLayout( Lower(), LONG_MAX, true ); + } + + //In order to be able to compare the positions of the cells with CutPos, + //they have to be calculated consecutively starting from the table. + //They can definitely be invalid because of position changes of the table. + SwRowFrame *pRow = static_cast<SwRowFrame*>(Lower()); + if( !pRow ) + return bRet; + + const sal_uInt16 nRepeat = GetTable()->GetRowsToRepeat(); + sal_uInt16 nRowCount = 0; // pRow currently points to the first row + + SwTwips nRemainingSpaceForLastRow = + aRectFnSet.YDiff(nCutPos, aRectFnSet.GetTop(getFrameArea())); + nRemainingSpaceForLastRow -= aRectFnSet.GetTopMargin(*this); + + // Make pRow point to the line that does not fit anymore: + while( pRow->GetNext() && + nRemainingSpaceForLastRow >= ( aRectFnSet.GetHeight(pRow->getFrameArea()) + + (IsCollapsingBorders() ? + pRow->GetBottomLineSize() : + 0 ) ) ) + { + if( bTryToSplit || !pRow->IsRowSpanLine() || + 0 != aRectFnSet.GetHeight(pRow->getFrameArea()) ) + ++nRowCount; + nRemainingSpaceForLastRow -= aRectFnSet.GetHeight(pRow->getFrameArea()); + pRow = static_cast<SwRowFrame*>(pRow->GetNext()); + } + + // bSplitRowAllowed: Row may be split according to its attributes. + // bTryToSplit: Row will never be split if bTryToSplit = false. + // This can either be passed as a parameter, indicating + // that we are currently doing the second try to split the + // table, or it will be set to false under certain + // conditions that are not suitable for splitting + // the row. + bool bSplitRowAllowed = true; + if (!pRow->IsRowSplitAllowed()) + { + // A row larger than the entire page ought to be allowed to split regardless of setting, + // otherwise it has hidden content and that makes no sense + if ( pRow->getFrameArea().Height() > FindPageFrame()->getFramePrintArea().Height() ) + pRow->SetForceRowSplitAllowed( true ); + else + bSplitRowAllowed = false; + } + // #i29438# + // #i26945# - Floating screen objects no longer forbid + // a splitting of the table row. + // Special DoNotSplit case 1: + // Search for sections inside pRow: + if ( lcl_FindSectionsInRow( *pRow ) ) + { + bTryToSplit = false; + } + + // #i29771# + // To avoid loops, we do some checks before actually trying to split + // the row. Maybe we should keep the next row in this table. + // Note: This is only done if we are at the beginning of our upper + bool bKeepNextRow = false; + if ( nRowCount < nRepeat ) + { + // First case: One of the repeated headline does not fit to the page anymore. + // tdf#88496 Disable repeated headline (like for #i44910#) to avoid loops and + // to fix interoperability problems (very long tables only with headline) + // tdf#150149 except in multi-column sections, where it's possible to enlarge + // the height of the section frame instead of using this fallback + OSL_ENSURE( !GetIndPrev(), "Table is supposed to be at beginning" ); + if ( !IsInSct() ) + { + m_pTable->SetRowsToRepeat(0); + return false; + } + else + bKeepNextRow = true; + } + else if ( !GetIndPrev() && nRepeat == nRowCount ) + { + // Second case: The first non-headline row does not fit to the page. + // If it is not allowed to be split, or it contains a sub-row that + // is not allowed to be split, we keep the row in this table: + if ( bTryToSplit && bSplitRowAllowed ) + { + // Check if there are (first) rows inside this row, + // which are not allowed to be split. + SwCellFrame* pLowerCell = static_cast<SwCellFrame*>(pRow->Lower()); + while ( pLowerCell ) + { + if ( pLowerCell->Lower() && pLowerCell->Lower()->IsRowFrame() ) + { + const SwRowFrame* pLowerRow = static_cast<SwRowFrame*>(pLowerCell->Lower()); + if ( !pLowerRow->IsRowSplitAllowed() && + aRectFnSet.GetHeight(pLowerRow->getFrameArea()) > nRemainingSpaceForLastRow ) + { + bKeepNextRow = true; + break; + } + } + pLowerCell = static_cast<SwCellFrame*>(pLowerCell->GetNext()); + } + } + else + bKeepNextRow = true; + } + + // Better keep the next row in this table: + if ( bKeepNextRow ) + { + pRow = GetFirstNonHeadlineRow(); + if ( pRow && pRow->IsRowSpanLine() && 0 == aRectFnSet.GetHeight(pRow->getFrameArea()) ) + pRow = static_cast<SwRowFrame*>(pRow->GetNext()); + if ( pRow ) + { + pRow = static_cast<SwRowFrame*>(pRow->GetNext()); + ++nRowCount; + } + } + + // No more row to split or to move to follow table: + if ( !pRow ) + return bRet; + + // We try to split the row if + // - the attributes of the row are set accordingly and + // - we are allowed to do so + // - it should not be kept with the next row + bSplitRowAllowed = bSplitRowAllowed && bTryToSplit && + ( !bTableRowKeep || + !pRow->ShouldRowKeepWithNext() ); + + // Adjust pRow according to the keep-with-next attribute: + if ( !bSplitRowAllowed && bTableRowKeep ) + { + SwRowFrame* pTmpRow = static_cast<SwRowFrame*>(pRow->GetPrev()); + SwRowFrame* pOldRow = pRow; + while ( pTmpRow && pTmpRow->ShouldRowKeepWithNext() && + nRowCount > nRepeat ) + { + pRow = pTmpRow; + --nRowCount; + pTmpRow = static_cast<SwRowFrame*>(pTmpRow->GetPrev()); + } + + // loop prevention + if ( nRowCount == nRepeat && !GetIndPrev()) + { + pRow = pOldRow; + } + } + + // If we do not intend to split pRow, we check if we are + // allowed to move pRow to a follow. Otherwise we return + // false, indicating an error + if ( !bSplitRowAllowed ) + { + SwRowFrame* pFirstNonHeadlineRow = GetFirstNonHeadlineRow(); + if ( pRow == pFirstNonHeadlineRow ) + return false; + + // #i91764# + // Ignore row span lines + SwRowFrame* pTmpRow = pFirstNonHeadlineRow; + while ( pTmpRow && pTmpRow->IsRowSpanLine() ) + { + pTmpRow = static_cast<SwRowFrame*>(pTmpRow->GetNext()); + } + if ( !pTmpRow || pRow == pTmpRow ) + { + return false; + } + } + + // Build follow table if not already done: + bool bNewFollow; + SwTabFrame *pFoll; + if ( GetFollow() ) + { + pFoll = GetFollow(); + bNewFollow = false; + } + else + { + bNewFollow = true; + pFoll = new SwTabFrame( *this ); + + // We give the follow table an initial width. + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFoll); + aRectFnSet.AddWidth(aFrm, aRectFnSet.GetWidth(getFrameArea())); + aRectFnSet.SetLeft(aFrm, aRectFnSet.GetLeft(getFrameArea())); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFoll); + aRectFnSet.AddWidth(aPrt, aRectFnSet.GetWidth(getFramePrintArea())); + } + + // Insert the new follow table + pFoll->InsertBehind( GetUpper(), this ); + + // Repeat the headlines. + auto& rLines = GetTable()->GetTabLines(); + for ( nRowCount = 0; nRowCount < nRepeat; ++nRowCount ) + { + // Insert new headlines: + SwRowFrame* pHeadline = new SwRowFrame(*rLines[nRowCount], this); + { + sw::FlyCreationSuppressor aSuppressor; + pHeadline->SetRepeatedHeadline(true); + } + pHeadline->InsertBefore( pFoll, nullptr ); + + SwPageFrame *pPage = pHeadline->FindPageFrame(); + const SwFrameFormats *pTable = GetFormat()->GetDoc()->GetSpzFrameFormats(); + if( !pTable->empty() ) + { + SwNodeOffset nIndex; + SwContentFrame* pFrame = pHeadline->ContainsContent(); + while( pFrame ) + { + // sw_redlinehide: the implementation of AppendObjs + // takes care of iterating merged SwTextFrame + nIndex = pFrame->IsTextFrame() + ? static_cast<SwTextFrame*>(pFrame)->GetTextNodeFirst()->GetIndex() + : static_cast<SwNoTextFrame*>(pFrame)->GetNode()->GetIndex(); + AppendObjs( pTable, nIndex, pFrame, pPage, GetFormat()->GetDoc()); + pFrame = pFrame->GetNextContentFrame(); + if( !pHeadline->IsAnLower( pFrame ) ) + break; + } + } + } + } + + SwRowFrame* pLastRow = nullptr; // points to the last remaining line in master + SwRowFrame* pFollowRow = nullptr; // points to either the follow flow line or the + // first regular line in the follow + + if ( bSplitRowAllowed ) + { + // If the row that does not fit anymore is allowed + // to be split, the next row has to be moved to the follow table. + pLastRow = pRow; + pRow = static_cast<SwRowFrame*>(pRow->GetNext()); + + // new follow flow line for last row of master table + pFollowRow = lcl_InsertNewFollowFlowLine( *this, *pLastRow, false ); + } + else + { + pFollowRow = pRow; + + // NEW TABLES + // check if we will break a row span by moving pFollowRow to the follow: + // In this case we want to reformat the last line. + const SwCellFrame* pCellFrame = static_cast<const SwCellFrame*>(pFollowRow->GetLower()); + while ( pCellFrame ) + { + if ( pCellFrame->GetTabBox()->getRowSpan() < 1 ) + { + pLastRow = static_cast<SwRowFrame*>(pRow->GetPrev()); + break; + } + + pCellFrame = static_cast<const SwCellFrame*>(pCellFrame->GetNext()); + } + + // new follow flow line for last row of master table + if ( pLastRow ) + pFollowRow = lcl_InsertNewFollowFlowLine( *this, *pLastRow, true ); + } + + SwTwips nShrink = 0; + + //Optimization: There is no paste needed for the new Follow and the + //optimized insert can be used (large numbers of rows luckily only occur in + //such situations). + if ( bNewFollow ) + { + SwFrame* pInsertBehind = pFoll->GetLastLower(); + + while ( pRow ) + { + SwFrame* pNxt = pRow->GetNext(); + nShrink += aRectFnSet.GetHeight(pRow->getFrameArea()); + // The footnotes do not have to be moved, this is done in the + // MoveFwd of the follow table!!! + pRow->RemoveFromLayout(); + pRow->InsertBehind( pFoll, pInsertBehind ); + pRow->InvalidateAll_(); + pInsertBehind = pRow; + pRow = static_cast<SwRowFrame*>(pNxt); + } + } + else + { + SwFrame* pPasteBefore = HasFollowFlowLine() ? + pFollowRow->GetNext() : + pFoll->GetFirstNonHeadlineRow(); + + while ( pRow ) + { + SwFrame* pNxt = pRow->GetNext(); + nShrink += aRectFnSet.GetHeight(pRow->getFrameArea()); + + // The footnotes have to be moved: + lcl_MoveFootnotes( *this, *GetFollow(), *pRow ); + + pRow->RemoveFromLayout(); + pRow->Paste( pFoll, pPasteBefore ); + + pRow->CheckDirChange(); + pRow = static_cast<SwRowFrame*>(pNxt); + } + } + + if ( !pLastRow ) + Shrink( nShrink ); + else + { + // we rebuild the last line to assure that it will be fully formatted + // we also don't shrink here, because we will be doing that in lcl_RecalcSplitLine + + // recalculate the split line + bRet = lcl_RecalcSplitLine( *pLastRow, *pFollowRow, nRemainingSpaceForLastRow, nShrink ); + + // RecalcSplitLine did not work. In this case we conceal the split error: + if (!bRet && !bSplitRowAllowed) + { + bRet = true; + } + + // NEW TABLES + // check if each cell in the row span line has a good height + if ( bRet && pFollowRow->IsRowSpanLine() ) + lcl_AdjustRowSpanCells( pFollowRow ); + } + + return bRet; +} + +namespace +{ + bool CanDeleteFollow(SwTabFrame *pFoll) + { + if (pFoll->IsJoinLocked()) + return false; + + if (pFoll->IsDeleteForbidden()) + { + SAL_WARN("sw.layout", "Delete Forbidden"); + return false; + } + + return true; + } +} + +void SwTabFrame::Join() +{ + OSL_ENSURE( !HasFollowFlowLine(), "Joining follow flow line" ); + + SwTabFrame *pFoll = GetFollow(); + + if (!pFoll || !CanDeleteFollow(pFoll)) + return; + + SwRectFnSet aRectFnSet(this); + pFoll->Cut(); //Cut out first to avoid unnecessary notifications. + + SwFrame *pRow = pFoll->GetFirstNonHeadlineRow(), + *pNxt; + + SwFrame* pPrv = GetLastLower(); + + SwTwips nHeight = 0; //Total height of the inserted rows as return value. + + while ( pRow ) + { + pNxt = pRow->GetNext(); + nHeight += aRectFnSet.GetHeight(pRow->getFrameArea()); + pRow->RemoveFromLayout(); + pRow->InvalidateAll_(); + pRow->InsertBehind( this, pPrv ); + pRow->CheckDirChange(); + pPrv = pRow; + pRow = pNxt; + } + + SetFollow( pFoll->GetFollow() ); + SetFollowFlowLine( pFoll->HasFollowFlowLine() ); + SwFrame::DestroyFrame(pFoll); + + Grow( nHeight ); +} + +static void SwInvalidatePositions( SwFrame *pFrame, tools::Long nBottom ) +{ + // LONG_MAX == nBottom means we have to calculate all + bool bAll = LONG_MAX == nBottom; + SwRectFnSet aRectFnSet(pFrame); + do + { pFrame->InvalidatePos_(); + pFrame->InvalidateSize_(); + if( pFrame->IsLayoutFrame() ) + { + if ( static_cast<SwLayoutFrame*>(pFrame)->Lower() ) + { + ::SwInvalidatePositions( static_cast<SwLayoutFrame*>(pFrame)->Lower(), nBottom); + // #i26945# + ::lcl_InvalidateLowerObjs( *static_cast<SwLayoutFrame*>(pFrame) ); + } + } + else + pFrame->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + pFrame = pFrame->GetNext(); + } while ( pFrame && + ( bAll || + aRectFnSet.YDiff( aRectFnSet.GetTop(pFrame->getFrameArea()), nBottom ) < 0 ) ); +} + +void SwInvalidateAll( SwFrame *pFrame, tools::Long nBottom ) +{ + // LONG_MAX == nBottom means we have to calculate all + bool bAll = LONG_MAX == nBottom; + SwRectFnSet aRectFnSet(pFrame); + do + { + pFrame->InvalidatePos_(); + pFrame->InvalidateSize_(); + pFrame->InvalidatePrt_(); + if( pFrame->IsLayoutFrame() ) + { + // NEW TABLES + SwLayoutFrame* pToInvalidate = static_cast<SwLayoutFrame*>(pFrame); + if (pFrame->IsCellFrame()) + { + SwCellFrame* pThisCell = static_cast<SwCellFrame*>(pFrame); + if ( pThisCell->GetTabBox()->getRowSpan() < 1 ) + { + pToInvalidate = & const_cast<SwCellFrame&>(pThisCell->FindStartEndOfRowSpanCell( true )); + pToInvalidate->InvalidatePos_(); + pToInvalidate->InvalidateSize_(); + pToInvalidate->InvalidatePrt_(); + } + } + if ( pToInvalidate->Lower() ) + ::SwInvalidateAll( pToInvalidate->Lower(), nBottom); + } + else + pFrame->Prepare(); + + pFrame = pFrame->GetNext(); + } while ( pFrame && + ( bAll || + aRectFnSet.YDiff( aRectFnSet.GetTop(pFrame->getFrameArea()), nBottom ) < 0 ) ); +} + +// #i29550# +static void lcl_InvalidateAllLowersPrt( SwLayoutFrame* pLayFrame ) +{ + pLayFrame->InvalidatePrt_(); + pLayFrame->InvalidateSize_(); + pLayFrame->SetCompletePaint(); + + SwFrame* pFrame = pLayFrame->Lower(); + + while ( pFrame ) + { + if ( pFrame->IsLayoutFrame() ) + lcl_InvalidateAllLowersPrt( static_cast<SwLayoutFrame*>(pFrame) ); + else + { + pFrame->InvalidatePrt_(); + pFrame->InvalidateSize_(); + pFrame->SetCompletePaint(); + } + + pFrame = pFrame->GetNext(); + } +} + +bool SwContentFrame::CalcLowers(SwLayoutFrame & rLay, SwLayoutFrame const& rDontLeave, + tools::Long nBottom, bool bSkipRowSpanCells ) +{ + vcl::RenderContext* pRenderContext = rLay.getRootFrame()->GetCurrShell()->GetOut(); + // LONG_MAX == nBottom means we have to calculate all + bool bAll = LONG_MAX == nBottom; + bool bRet = false; + SwContentFrame *pCnt = rLay.ContainsContent(); + SwRectFnSet aRectFnSet(&rLay); + + // FME 2007-08-30 #i81146# new loop control + int nLoopControlRuns = 0; + const int nLoopControlMax = 10; + const sw::BroadcastingModify* pLoopControlCond = nullptr; + + while (pCnt && rDontLeave.IsAnLower(pCnt)) + { + // #115759# - check, if a format of content frame is + // possible. Thus, 'copy' conditions, found at the beginning of + // <SwContentFrame::MakeAll(..)>, and check these. + const bool bFormatPossible = !pCnt->IsJoinLocked() && + ( !pCnt->IsTextFrame() || + !static_cast<SwTextFrame*>(pCnt)->IsLocked() ) && + ( pCnt->IsFollow() || !StackHack::IsLocked() ); + + // NEW TABLES + bool bSkipContent = false; + if ( bSkipRowSpanCells && pCnt->IsInTab() ) + { + const SwFrame* pCell = pCnt->GetUpper(); + while ( pCell && !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + if ( pCell && 1 != static_cast<const SwCellFrame*>( pCell )->GetLayoutRowSpan() ) + bSkipContent = true; + } + + if ( bFormatPossible && !bSkipContent ) + { + bRet |= !pCnt->isFrameAreaDefinitionValid(); + // #i26945# - no extra invalidation of floating + // screen objects needed. + // Thus, delete call of method <SwFrame::InvalidateObjs( true )> + pCnt->Calc(pRenderContext); + // #i46941# - frame has to be valid + // Note: frame could be invalid after calling its format, if it's locked. + OSL_ENSURE( !pCnt->IsTextFrame() || + pCnt->isFrameAreaDefinitionValid() || + static_cast<SwTextFrame*>(pCnt)->IsJoinLocked(), + "<SwContentFrame::CalcLowers(..)> - text frame invalid and not locked." ); + if ( pCnt->IsTextFrame() && pCnt->isFrameAreaDefinitionValid() ) + { + // #i23129#, #i36347# - pass correct page frame to + // the object formatter + if ( !SwObjectFormatter::FormatObjsAtFrame( *pCnt, + *(pCnt->FindPageFrame()) ) ) + { + SwTextNode const*const pTextNode( + static_cast<SwTextFrame*>(pCnt)->GetTextNodeFirst()); + if (pTextNode == pLoopControlCond) + ++nLoopControlRuns; + else + { + nLoopControlRuns = 0; + pLoopControlCond = pTextNode; + } + + if ( nLoopControlRuns < nLoopControlMax ) + { + // restart format with first content + pCnt = rLay.ContainsContent(); + continue; + } + +#if OSL_DEBUG_LEVEL > 1 + OSL_FAIL( "LoopControl in SwContentFrame::CalcLowers" ); +#endif + } + } + if (!rDontLeave.IsAnLower(pCnt)) // moved backward? + { + pCnt = rLay.ContainsContent(); + continue; // avoid formatting new upper on different page + } + pCnt->GetUpper()->Calc(pRenderContext); + } + if( ! bAll && aRectFnSet.YDiff(aRectFnSet.GetTop(pCnt->getFrameArea()), nBottom) > 0 ) + break; + pCnt = pCnt->GetNextContentFrame(); + } + return bRet; +} + +// #i26945# - add parameter <_bOnlyRowsAndCells> to control +// that only row and cell frames are formatted. +static bool lcl_InnerCalcLayout( SwFrame *pFrame, + tools::Long nBottom, + bool _bOnlyRowsAndCells ) +{ + vcl::RenderContext* pRenderContext = pFrame->getRootFrame()->GetCurrShell() ? pFrame->getRootFrame()->GetCurrShell()->GetOut() : nullptr; + // LONG_MAX == nBottom means we have to calculate all + bool bAll = LONG_MAX == nBottom; + bool bRet = false; + const SwFrame* pOldUp = pFrame->GetUpper(); + SwRectFnSet aRectFnSet(pFrame); + do + { + // #i26945# - parameter <_bOnlyRowsAndCells> controls, + // if only row and cell frames are formatted. + if ( pFrame->IsLayoutFrame() && + ( !_bOnlyRowsAndCells || pFrame->IsRowFrame() || pFrame->IsCellFrame() ) ) + { + SwFrameDeleteGuard aDeleteGuard(pFrame); + + // #130744# An invalid locked table frame will + // not be calculated => It will not become valid => + // Loop in lcl_RecalcRow(). Therefore we do not consider them for bRet. + bRet |= !pFrame->isFrameAreaDefinitionValid() && ( !pFrame->IsTabFrame() || !static_cast<SwTabFrame*>(pFrame)->IsJoinLocked() ); + pFrame->Calc(pRenderContext); + if( static_cast<SwLayoutFrame*>(pFrame)->Lower() ) + bRet |= lcl_InnerCalcLayout( static_cast<SwLayoutFrame*>(pFrame)->Lower(), nBottom); + + // NEW TABLES + if (pFrame->IsCellFrame()) + { + SwCellFrame* pThisCell = static_cast<SwCellFrame*>(pFrame); + if ( pThisCell->GetTabBox()->getRowSpan() < 1 ) + { + SwCellFrame& rToCalc = const_cast<SwCellFrame&>(pThisCell->FindStartEndOfRowSpanCell( true )); + bRet |= !rToCalc.isFrameAreaDefinitionValid(); + rToCalc.Calc(pRenderContext); + if ( rToCalc.Lower() ) + bRet |= lcl_InnerCalcLayout( rToCalc.Lower(), nBottom); + } + } + } + pFrame = pFrame->GetNext(); + } while( pFrame && + ( bAll || + aRectFnSet.YDiff(aRectFnSet.GetTop(pFrame->getFrameArea()), nBottom) < 0 ) + && pFrame->GetUpper() == pOldUp ); + return bRet; +} + +static void lcl_RecalcRow(SwRowFrame & rRow, tools::Long const nBottom) +{ + // FME 2007-08-30 #i81146# new loop control + int nLoopControlRuns_1 = 0; + sal_uInt16 nLoopControlStage_1 = 0; + const int nLoopControlMax = 10; + + bool bCheck = true; + do + { + // FME 2007-08-30 #i81146# new loop control + int nLoopControlRuns_2 = 0; + sal_uInt16 nLoopControlStage_2 = 0; + + while (lcl_InnerCalcLayout(&rRow, nBottom)) + { + if ( ++nLoopControlRuns_2 > nLoopControlMax ) + { + SAL_WARN_IF(nLoopControlStage_2 == 0, "sw.layout", "LoopControl_2 in lcl_RecalcRow: Stage 1!"); + SAL_WARN_IF(nLoopControlStage_2 == 1, "sw.layout", "LoopControl_2 in lcl_RecalcRow: Stage 2!!"); + SAL_WARN_IF(nLoopControlStage_2 >= 2, "sw.layout", "LoopControl_2 in lcl_RecalcRow: Stage 3!!!"); + rRow.ValidateThisAndAllLowers( nLoopControlStage_2++ ); + nLoopControlRuns_2 = 0; + if( nLoopControlStage_2 > 2 ) + break; + } + + bCheck = true; + } + + if( bCheck ) + { + // #115759# - force another format of the + // lowers, if at least one of it was invalid. + bCheck = SwContentFrame::CalcLowers(rRow, *rRow.GetUpper(), nBottom, true); + + // NEW TABLES + // First we calculate the cells with row span of < 1, afterwards + // all cells with row span of > 1: + for ( int i = 0; i < 2; ++i ) + { + SwCellFrame* pCellFrame = static_cast<SwCellFrame*>(rRow.Lower()); + while ( pCellFrame ) + { + const bool bCalc = 0 == i ? + pCellFrame->GetLayoutRowSpan() < 1 : + pCellFrame->GetLayoutRowSpan() > 1; + + if ( bCalc ) + { + SwCellFrame& rToRecalc = 0 == i ? + const_cast<SwCellFrame&>(pCellFrame->FindStartEndOfRowSpanCell( true )) : + *pCellFrame; + bCheck |= SwContentFrame::CalcLowers(rToRecalc, rToRecalc, nBottom, false); + } + + pCellFrame = static_cast<SwCellFrame*>(pCellFrame->GetNext()); + } + } + + if ( bCheck ) + { + if ( ++nLoopControlRuns_1 > nLoopControlMax ) + { + SAL_WARN_IF(nLoopControlStage_1 == 0, "sw.layout", "LoopControl_1 in lcl_RecalcRow: Stage 1!"); + SAL_WARN_IF(nLoopControlStage_1 == 1, "sw.layout", "LoopControl_1 in lcl_RecalcRow: Stage 2!!"); + SAL_WARN_IF(nLoopControlStage_1 >= 2, "sw.layout", "LoopControl_1 in lcl_RecalcRow: Stage 3!!!"); + rRow.ValidateThisAndAllLowers( nLoopControlStage_1++ ); + nLoopControlRuns_1 = 0; + if( nLoopControlStage_1 > 2 ) + break; + } + + continue; + } + } + break; + } while( true ); +} + +static void lcl_RecalcTable( SwTabFrame& rTab, + SwLayoutFrame *pFirstRow, + SwLayNotify &rNotify ) +{ + if ( rTab.Lower() ) + { + if ( !pFirstRow ) + { + pFirstRow = static_cast<SwLayoutFrame*>(rTab.Lower()); + rNotify.SetLowersComplete( true ); + } + ::SwInvalidatePositions( pFirstRow, LONG_MAX ); + lcl_RecalcRow( *static_cast<SwRowFrame*>(pFirstRow), LONG_MAX ); + } +} + +// This is a new function to check the first condition whether +// a tab frame may move backward. It replaces the formerly used +// GetIndPrev(), which did not work correctly for #i5947# +static bool lcl_NoPrev( const SwFrame& rFrame ) +{ + // #i79774# + // skip empty sections on investigation of direct previous frame. + // use information, that at least one empty section is skipped in the following code. + bool bSkippedDirectPrevEmptySection( false ); + if ( rFrame.GetPrev() ) + { + const SwFrame* pPrev( rFrame.GetPrev() ); + while ( pPrev && + pPrev->IsSctFrame() && + !dynamic_cast<const SwSectionFrame&>(*pPrev).GetSection() ) + { + pPrev = pPrev->GetPrev(); + bSkippedDirectPrevEmptySection = true; + } + if ( pPrev ) + { + return false; + } + } + + if ( ( !bSkippedDirectPrevEmptySection && !rFrame.GetIndPrev() ) || + ( bSkippedDirectPrevEmptySection && + ( !rFrame.IsInSct() || !rFrame.GetIndPrev_() ) ) ) + { + return true; + } + + // I do not have a direct prev, but I have an indirect prev. + // In section frames I have to check if I'm located inside + // the first column: + if ( rFrame.IsInSct() ) + { + const SwFrame* pSct = rFrame.GetUpper(); + if ( pSct && pSct->IsColBodyFrame() && + pSct->GetUpper()->GetUpper()->IsSctFrame() ) + { + const SwFrame* pPrevCol = rFrame.GetUpper()->GetUpper()->GetPrev(); + if ( pPrevCol ) + // I'm not inside the first column and do not have a direct + // prev. I can try to go backward. + return true; + } + } + + return false; +} + +#define KEEPTAB ( !GetFollow() && !IsFollow() ) + +// - helper method to find next content frame of +// a table frame and format it to assure keep attribute. +// method return true, if a next content frame is formatted. +// Precondition: The given table frame hasn't a follow and isn't a follow. +SwFrame* sw_FormatNextContentForKeep( SwTabFrame* pTabFrame ) +{ + vcl::RenderContext* pRenderContext = pTabFrame->getRootFrame()->GetCurrShell()->GetOut(); + // find next content, table or section + SwFrame* pNxt = pTabFrame->FindNext(); + + // skip empty sections + while ( pNxt && pNxt->IsSctFrame() && + !static_cast<SwSectionFrame*>(pNxt)->GetSection() ) + { + pNxt = pNxt->FindNext(); + } + + // if found next frame is a section, get its first content. + if ( pNxt && pNxt->IsSctFrame() ) + { + pNxt = static_cast<SwSectionFrame*>(pNxt)->ContainsAny(); + } + + // format found next frame. + // if table frame is inside another table, method <SwFrame::MakeAll()> is + // called to avoid that the superior table frame is formatted. + if ( pNxt ) + { + if ( pTabFrame->GetUpper()->IsInTab() ) + pNxt->MakeAll(pNxt->getRootFrame()->GetCurrShell()->GetOut()); + else + pNxt->Calc(pRenderContext); + } + + return pNxt; +} + +namespace { + bool AreAllRowsKeepWithNext( const SwRowFrame* pFirstRowFrame, const bool bCheckParents = true ) + { + bool bRet = pFirstRowFrame != nullptr && + pFirstRowFrame->ShouldRowKeepWithNext( bCheckParents ); + + while ( bRet && pFirstRowFrame->GetNext() != nullptr ) + { + pFirstRowFrame = dynamic_cast<const SwRowFrame*>(pFirstRowFrame->GetNext()); + bRet = pFirstRowFrame != nullptr && + pFirstRowFrame->ShouldRowKeepWithNext( bCheckParents ); + } + + return bRet; + } +} + +// extern because static can't be friend +void FriendHackInvalidateRowFrame(SwFrameAreaDefinition & rRowFrame) +{ + // hilariously static_cast<SwTabFrame*>(GetLower()) would not require friend declaration, but it's UB... + rRowFrame.setFrameAreaPositionValid(false); +} + +static void InvalidateFramePositions(SwFrame * pFrame) +{ + while (pFrame) + { + if (pFrame->IsLayoutFrame()) + { + InvalidateFramePositions(pFrame->GetLower()); + } + else if (pFrame->IsTextFrame()) + { + pFrame->Prepare(PrepareHint::FramePositionChanged); + } + pFrame = pFrame->GetNext(); + } +} + +void SwTabFrame::MakeAll(vcl::RenderContext* pRenderContext) +{ + if ( IsJoinLocked() || StackHack::IsLocked() || StackHack::Count() > 50 ) + return; + + if ( HasFollow() ) + { + SwTabFrame* pFollowFrame = GetFollow(); + OSL_ENSURE( !pFollowFrame->IsJoinLocked() || !pFollowFrame->IsRebuildLastLine(), + "SwTabFrame::MakeAll for master while follow is in RebuildLastLine()" ); + if ( pFollowFrame->IsJoinLocked() && pFollowFrame->IsRebuildLastLine() ) + return; + } + + PROTOCOL_ENTER( this, PROT::MakeAll, DbgAction::NONE, nullptr ) + + LockJoin(); //I don't want to be destroyed on the way. + SwLayNotify aNotify( this ); //does the notification in the DTor + // If pos is invalid, we have to call a SetInvaKeep at aNotify. + // Otherwise the keep attribute would not work in front of a table. + const bool bOldValidPos = isFrameAreaPositionValid(); + + //If my neighbour is my Follow at the same time, I'll swallow it up. + // OD 09.04.2003 #108698# - join all follows, which are placed on the + // same page/column. + // OD 29.04.2003 #109213# - join follow, only if join for the follow + // is not locked. Otherwise, join will not be performed and this loop + // will be endless. + while ( GetNext() && GetNext() == GetFollow() && + CanDeleteFollow(GetFollow()) + ) + { + if ( HasFollowFlowLine() ) + RemoveFollowFlowLine(); + Join(); + } + + // The bRemoveFollowFlowLinePending is set if the split attribute of the + // last line is set: + if ( IsRemoveFollowFlowLinePending() && HasFollowFlowLine() ) + { + if ( RemoveFollowFlowLine() ) + Join(); + SetRemoveFollowFlowLinePending( false ); + } + + if (m_bResizeHTMLTable) //Optimized interplay with grow/shrink of the content + { + m_bResizeHTMLTable = false; + SwHTMLTableLayout *pLayout = GetTable()->GetHTMLTableLayout(); + if ( pLayout ) + m_bCalcLowers = pLayout->Resize( + pLayout->GetBrowseWidthByTabFrame( *this ) ); + } + + // as long as bMakePage is true, a new page can be created (exactly once) + bool bMakePage = true; + // bMovedBwd gets set to true when the frame flows backwards + bool bMovedBwd = false; + // as long as bMovedFwd is false, the Frame may flow backwards (until + // it has been moved forward once) + bool bMovedFwd = false; + // gets set to true when the Frame is split + bool bSplit = false; + const bool bFootnotesInDoc = !GetFormat()->GetDoc()->GetFootnoteIdxs().empty(); + const bool bFly = IsInFly(); + + std::optional<SwBorderAttrAccess> oAccess(std::in_place, SwFrame::GetCache(), this); + const SwBorderAttrs *pAttrs = oAccess->Get(); + + // All rows should keep together + const bool bDontSplit = !IsFollow() && + ( !GetFormat()->GetLayoutSplit().GetValue() ); + + // The number of repeated headlines + const sal_uInt16 nRepeat = GetTable()->GetRowsToRepeat(); + + // This flag indicates that we are allowed to try to split the + // table rows. + bool bTryToSplit = true; + + // Indicates that two individual rows may keep together, based on the keep + // attribute set at the first paragraph in the first cell. + const bool bTableRowKeep = !bDontSplit && GetFormat()->GetDoc()->GetDocumentSettingManager().get(DocumentSettingId::TABLE_ROW_KEEP); + + // The Magic Move: Used for the table row keep feature. + // If only the last row of the table wants to keep (implicitly by setting + // keep for the first paragraph in the first cell), and this table does + // not have a next, the last line will be cut. Loop prevention: Only + // one try. + // WHAT IS THIS??? It "magically" hides last line (paragraph) in a table, + // if first is set to keep with next??? + bool bLastRowHasToMoveToFollow = false; + bool bLastRowMoveNoMoreTries = false; + + const bool bLargeTable = GetTable()->GetTabLines().size() > 64; //arbitrary value, virtually guaranteed to be larger than one page. + const bool bEmulateTableKeep = !bLargeTable && bTableRowKeep + && !pAttrs->GetAttrSet().GetKeep().GetValue() + && AreAllRowsKeepWithNext(GetFirstNonHeadlineRow(), /*bCheckParents=*/false); + // The beloved keep attribute + const bool bKeep = IsKeep(pAttrs->GetAttrSet().GetKeep(), GetBreakItem(), bEmulateTableKeep); + + // Join follow table, if this table is not allowed to split: + if ( bDontSplit ) + { + while ( GetFollow() && !GetFollow()->IsJoinLocked() ) + { + if ( HasFollowFlowLine() ) + RemoveFollowFlowLine(); + Join(); + } + } + + // Join follow table, if this does not have enough (repeated) lines: + if ( nRepeat ) + { + if( GetFollow() && !GetFollow()->IsJoinLocked() && + nullptr == GetFirstNonHeadlineRow() ) + { + if ( HasFollowFlowLine() ) + RemoveFollowFlowLine(); + Join(); + } + } + + // Join follow table, if last row of this table should keep: + if ( bTableRowKeep && GetFollow() && !GetFollow()->IsJoinLocked() ) + { + const SwRowFrame* pTmpRow = static_cast<const SwRowFrame*>(GetLastLower()); + if ( pTmpRow && pTmpRow->ShouldRowKeepWithNext() ) + { + if ( HasFollowFlowLine() ) + RemoveFollowFlowLine(); + Join(); + } + } + + // a new one is moved forwards immediately + if ( !getFrameArea().Top() && IsFollow() ) + { + SwFrame *pPre = GetPrev(); + if ( pPre && pPre->IsTabFrame() && static_cast<SwTabFrame*>(pPre)->GetFollow() == this) + { + // don't make the effort to move fwd if its known + // conditions that are known not to work + if (IsInFootnote() && ForbiddenForFootnoteCntFwd()) + bMakePage = false; + else if (!MoveFwd(bMakePage, false)) + bMakePage = false; + bMovedFwd = true; + } + } + + int nUnSplitted = 5; // Just another loop control :-( + int nThrowAwayValidLayoutLimit = 5; // And another one :-( + SwRectFnSet aRectFnSet(this); + while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + const bool bMoveable = IsMoveable(); + if (bMoveable && + !(bMovedFwd && bEmulateTableKeep) ) + if ( CheckMoveFwd( bMakePage, bKeep && KEEPTAB, bEmulateTableKeep ) ) + { + bMovedFwd = true; + m_bCalcLowers = true; + // #i99267# + // reset <bSplit> after forward move to assure that follows + // can be joined, if further space is available. + bSplit = false; + } + + Point aOldPos( aRectFnSet.GetPos(getFrameArea()) ); + MakePos(); + + if ( aOldPos != aRectFnSet.GetPos(getFrameArea()) ) + { + if ( aOldPos.Y() != aRectFnSet.GetTop(getFrameArea()) ) + { + SwHTMLTableLayout *pLayout = GetTable()->GetHTMLTableLayout(); + if( pLayout ) + { + oAccess.reset(); + m_bCalcLowers |= pLayout->Resize( + pLayout->GetBrowseWidthByTabFrame( *this ) ); + } + + setFramePrintAreaValid(false); + aNotify.SetLowersComplete( false ); + } + SwFrame *pPre; + if ( bKeep || (nullptr != (pPre = FindPrev()) && + pPre->GetAttrSet()->GetKeep().GetValue()) ) + { + m_bCalcLowers = true; + } + if (GetLower()) + { // it's possible that the rows already have valid pos - but it is surely wrong if the table's pos changed! + FriendHackInvalidateRowFrame(*GetLower()); + // invalidate text frames to get rid of their SwFlyPortions + InvalidateFramePositions(GetLower()); + } + } + + //We need to know the height of the first row, because the master needs + //to be invalidated if it shrinks and then absorb the row if possible. + tools::Long n1StLineHeight = 0; + if ( IsFollow() ) + { + SwFrame* pFrame = GetFirstNonHeadlineRow(); + if ( pFrame ) + n1StLineHeight = aRectFnSet.GetHeight(pFrame->getFrameArea()); + } + + if ( !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + { + const tools::Long nOldPrtWidth = aRectFnSet.GetWidth(getFramePrintArea()); + const tools::Long nOldFrameWidth = aRectFnSet.GetWidth(getFrameArea()); + const Point aOldPrtPos = aRectFnSet.GetPos(getFramePrintArea()); + + if (!oAccess) + { + oAccess.emplace(SwFrame::GetCache(), this); + pAttrs = oAccess->Get(); + } + Format( getRootFrame()->GetCurrShell()->GetOut(), pAttrs ); + + SwHTMLTableLayout *pLayout = GetTable()->GetHTMLTableLayout(); + if ( pLayout && + (aRectFnSet.GetWidth(getFramePrintArea()) != nOldPrtWidth || + aRectFnSet.GetWidth(getFrameArea()) != nOldFrameWidth) ) + { + oAccess.reset(); + m_bCalcLowers |= pLayout->Resize( + pLayout->GetBrowseWidthByTabFrame( *this ) ); + } + if ( aOldPrtPos != aRectFnSet.GetPos(getFramePrintArea()) ) + aNotify.SetLowersComplete( false ); + } + + // If this is the first one in a chain, check if this can flow + // backwards (if this is movable at all). + // To prevent oscillations/loops, check that this has not just + // flowed forwards. + if ( !bMovedFwd && (bMoveable || bFly) && lcl_NoPrev( *this ) ) + { + // for Follows notify Master. + // only move Follow if it has to skip empty pages. + if ( IsFollow() ) + { + // Only if the height of the first line got smaller. + SwFrame *pFrame = GetFirstNonHeadlineRow(); + if( pFrame && n1StLineHeight >aRectFnSet.GetHeight(pFrame->getFrameArea()) ) + { + SwTabFrame *pMaster = FindMaster(); + bool bDummy; + if ( ShouldBwdMoved( pMaster->GetUpper(), bDummy ) ) + pMaster->InvalidatePos(); + } + } + SwFootnoteBossFrame *pOldBoss = bFootnotesInDoc ? FindFootnoteBossFrame( true ) : nullptr; + bool bReformat; + std::optional<SfxDeleteListener> oDeleteListener; + if (pOldBoss) + oDeleteListener.emplace(*pOldBoss); + SwFrameDeleteGuard g(this); + if ( MoveBwd( bReformat ) ) + { + SAL_WARN_IF(oDeleteListener && oDeleteListener->WasDeleted(), "sw.layout", "SwFootnoteBossFrame unexpectedly deleted"); + + aRectFnSet.Refresh(this); + bMovedBwd = true; + aNotify.SetLowersComplete( false ); + if (bFootnotesInDoc && !oDeleteListener->WasDeleted()) + MoveLowerFootnotes( nullptr, pOldBoss, nullptr, true ); + if ( bReformat || bKeep ) + { + tools::Long nOldTop = aRectFnSet.GetTop(getFrameArea()); + MakePos(); + if( nOldTop != aRectFnSet.GetTop(getFrameArea()) ) + { + SwHTMLTableLayout *pHTMLLayout = + GetTable()->GetHTMLTableLayout(); + if( pHTMLLayout ) + { + oAccess.reset(); + m_bCalcLowers |= pHTMLLayout->Resize( + pHTMLLayout->GetBrowseWidthByTabFrame( *this ) ); + } + + setFramePrintAreaValid(false); + + if (!oAccess) + { + oAccess.emplace(SwFrame::GetCache(), this); + pAttrs = oAccess->Get(); + } + Format( getRootFrame()->GetCurrShell()->GetOut(), pAttrs ); + } + + oAccess.reset(); + + lcl_RecalcTable( *this, nullptr, aNotify ); + + m_bLowersFormatted = true; + if ( bKeep && KEEPTAB ) + { + + // Consider case that table is inside another table, + // because it has to be avoided, that superior table + // is formatted. + // Thus, find next content, table or section + // and, if a section is found, get its first + // content. + if ( nullptr != sw_FormatNextContentForKeep( this ) && !GetNext() ) + { + setFrameAreaPositionValid(false); + } + } + } + } + } + + //Again an invalid value? - do it again... + if ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + continue; + + // check, if calculation of table frame is ready. + + // Local variable <nDistanceToUpperPrtBottom> + // Introduce local variable and init it with the distance from the + // table frame bottom to the bottom of the upper printing area. + // Note: negative values denotes the situation that table frame doesn't fit in its upper. + SwTwips nDistanceToUpperPrtBottom = + aRectFnSet.BottomDist(getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper())); + + /// In online layout try to grow upper of table frame, if table frame doesn't fit in its upper. + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + if ( nDistanceToUpperPrtBottom < 0 && bBrowseMode ) + { + if ( GetUpper()->Grow( -nDistanceToUpperPrtBottom ) ) + { + // upper is grown --> recalculate <nDistanceToUpperPrtBottom> + nDistanceToUpperPrtBottom = aRectFnSet.BottomDist(getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper())); + } + } + + // If there is still some space left in the upper, we check if we + // can join some rows of the follow. + // Setting bLastRowHasToMoveToFollow to true means we want to force + // the table to be split! Only skip this if condition once. + if( nDistanceToUpperPrtBottom >= 0 && !bLastRowHasToMoveToFollow ) + { + // If there is space left in the upper printing area, join as for trial + // at least one further row of an existing follow. + if ( !bSplit && GetFollow() ) + { + bool bDummy; + if ( GetFollow()->ShouldBwdMoved( GetUpper(), bDummy ) ) + { + SwFrame *pTmp = GetUpper(); + SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*pTmp); + if ( bBrowseMode ) + nDeadLine += pTmp->Grow( LONG_MAX, true ); + bool bFits = aRectFnSet.BottomDist(getFrameArea(), nDeadLine) > 0; + if (!bFits && aRectFnSet.GetHeight(GetFollow()->getFrameArea()) == 0) + // The follow should move backwards, so allow the case + // when the upper has no space, but the follow is + // empty. + bFits = aRectFnSet.BottomDist(getFrameArea(), nDeadLine) >= 0; + if (bFits) + { + // First, we remove an existing follow flow line. + if ( HasFollowFlowLine() ) + { + SwFrame* pLastLine = GetLastLower(); + RemoveFollowFlowLine(); + // invalidate and rebuild last row + if ( pLastLine ) + { + ::SwInvalidateAll( pLastLine, LONG_MAX ); + SetRebuildLastLine( true ); + lcl_RecalcRow(*static_cast<SwRowFrame*>(pLastLine), LONG_MAX); + SetRebuildLastLine( false ); + } + + SwFrame* pRow = GetFollow()->GetFirstNonHeadlineRow(); + + if ( !pRow || !pRow->GetNext() ) + // The follow became empty and hence useless + Join(); + + continue; + } + + // If there is no follow flow line, we move the first + // row in the follow table to the master table. + SwRowFrame *pRow = GetFollow()->GetFirstNonHeadlineRow(); + + // The follow became empty and hence useless + if ( !pRow ) + { + Join(); + continue; + } + + const SwTwips nOld = aRectFnSet.GetHeight(getFrameArea()); + tools::Long nRowsToMove = lcl_GetMaximumLayoutRowSpan( *pRow ); + SwFrame* pRowToMove = pRow; + + while ( pRowToMove && nRowsToMove-- > 0 ) + { + const bool bMoveFootnotes = bFootnotesInDoc && !GetFollow()->IsJoinLocked(); + + SwFootnoteBossFrame *pOldBoss = nullptr; + if ( bMoveFootnotes ) + pOldBoss = pRowToMove->FindFootnoteBossFrame( true ); + + SwFrame* pNextRow = pRowToMove->GetNext(); + + if ( !pNextRow ) + { + // The follow became empty and hence useless + Join(); + } + else + { + pRowToMove->Cut(); + pRowToMove->Paste( this ); + } + + // Move the footnotes! + if ( bMoveFootnotes ) + if ( static_cast<SwLayoutFrame*>(pRowToMove)->MoveLowerFootnotes( nullptr, pOldBoss, FindFootnoteBossFrame( true ), true ) ) + GetUpper()->Calc(pRenderContext); + + pRowToMove = pNextRow; + } + + if ( nOld != aRectFnSet.GetHeight(getFrameArea()) ) + lcl_RecalcTable( *this, static_cast<SwLayoutFrame*>(pRow), aNotify ); + + continue; + } + } + } + else if ( KEEPTAB ) + { + bool bFormat = false; + if ( bKeep ) + bFormat = true; + else if ( bTableRowKeep && !bLastRowMoveNoMoreTries ) + { + // We only want to give the last row one chance to move + // to the follow table. Set the flag as early as possible: + bLastRowMoveNoMoreTries = true; + + // The last line of the table has to be cut off if: + // 1. The table does not want to keep with its next + // 2. The compatibility option is set and the table is allowed to split + // 3. We did not already cut off the last row + // 4. There is not break after attribute set at the table + // 5. There is no break before attribute set behind the table + // 6. There is no section change behind the table (see IsKeep) + // 7. The last table row wants to keep with its next. + const SwRowFrame* pLastRow = static_cast<const SwRowFrame*>(GetLastLower()); + if (pLastRow) + { + if (!oAccess) + { + oAccess.emplace(SwFrame::GetCache(), this); + pAttrs = oAccess->Get(); + } + if (IsKeep(pAttrs->GetAttrSet().GetKeep(), GetBreakItem(), true) + && pLastRow->ShouldRowKeepWithNext()) + { + bFormat = true; + } + } + } + + if ( bFormat ) + { + oAccess.reset(); + + // Consider case that table is inside another table, because + // it has to be avoided, that superior table is formatted. + // Thus, find next content, table or section and, if a section + // is found, get its first content. + const SwFrame* pTmpNxt = sw_FormatNextContentForKeep( this ); + + // The last row wants to keep with the frame behind the table. + // Check if the next frame is on a different page and valid. + // In this case we do a magic trick: + if ( !bKeep && !GetNext() && pTmpNxt && pTmpNxt->isFrameAreaDefinitionValid() ) + { + setFrameAreaPositionValid(false); + bLastRowHasToMoveToFollow = true; + } + } + } + + if ( isFrameAreaDefinitionValid() ) + { + if (m_bCalcLowers) + { + lcl_RecalcTable( *this, nullptr, aNotify ); + m_bLowersFormatted = true; + m_bCalcLowers = false; + } + else if (m_bONECalcLowers) + { + lcl_RecalcRow(*static_cast<SwRowFrame*>(Lower()), LONG_MAX); + m_bONECalcLowers = false; + } + } + continue; + } + + // I don't fit in the upper Frame anymore, therefore it's the + // right moment to do some preferably constructive changes. + + // If I'm NOT allowed to leave the upper Frame, I've got a problem. + // Following Arthur Dent, we do the only thing that you can do with + // an unsolvable problem: We ignore it with all our power. + if ( !bMoveable ) + { + if (m_bCalcLowers && isFrameAreaDefinitionValid()) + { + lcl_RecalcTable( *this, nullptr, aNotify ); + m_bLowersFormatted = true; + m_bCalcLowers = false; + } + else if (m_bONECalcLowers) + { + lcl_RecalcRow(*static_cast<SwRowFrame*>(Lower()), LONG_MAX); + m_bONECalcLowers = false; + } + + // It does not make sense to cut off the last line if we are + // not moveable: + bLastRowHasToMoveToFollow = false; + + continue; + } + + if (m_bCalcLowers && isFrameAreaDefinitionValid()) + { + lcl_RecalcTable( *this, nullptr, aNotify ); + m_bLowersFormatted = true; + m_bCalcLowers = false; + if( !isFrameAreaDefinitionValid() ) + continue; + } + + // First try to split the table. Condition: + // 1. We have at least one non headline row + // 2. If this row wants to keep, we need an additional row + // 3. The table is allowed to split or we do not have a pIndPrev: + SwFrame* pIndPrev = GetIndPrev(); + const SwRowFrame* pFirstNonHeadlineRow = GetFirstNonHeadlineRow(); + // #i120016# if this row wants to keep, allow split in case that all rows want to keep with next, + // the table can not move forward as it is the first one and a split is in general allowed. + const bool bAllowSplitOfRow = bTableRowKeep && !pIndPrev && AreAllRowsKeepWithNext(pFirstNonHeadlineRow); + // tdf91083 MSCompat: this extends bAllowSplitOfRow (and perhaps should just replace it). + // If the kept-together items cannot move to a new page, a table split is in general allowed. + const bool bEmulateTableKeepSplitAllowed = bEmulateTableKeep && !IsKeepFwdMoveAllowed(/*IgnoreMyOwnKeepValue=*/true); + + if ( pFirstNonHeadlineRow && nUnSplitted > 0 && + ( bEmulateTableKeepSplitAllowed || bAllowSplitOfRow || + ( ( !bTableRowKeep || pFirstNonHeadlineRow->GetNext() || + !pFirstNonHeadlineRow->ShouldRowKeepWithNext() + ) && ( !bDontSplit || !pIndPrev ) + ) ) ) + { + // #i29438# + // Special DoNotSplit cases: + // We better avoid splitting of a row frame if we are inside a columned + // section which has a height of 0, because this is not growable and thus + // all kinds of unexpected things could happen. + if ( IsInSct() && FindSctFrame()->Lower()->IsColumnFrame() && + 0 == aRectFnSet.GetHeight(GetUpper()->getFrameArea()) + ) + { + bTryToSplit = false; + } + + // 1. Try: bTryToSplit = true => Try to split the row. + // 2. Try: bTryToSplit = false => Split the table between the rows. + if ( pFirstNonHeadlineRow->GetNext() || bTryToSplit ) + { + SwTwips nDeadLine = aRectFnSet.GetPrtBottom(*GetUpper()); + if( IsInSct() || GetUpper()->IsInTab() ) // TABLE IN TABLE) + nDeadLine = aRectFnSet.YInc( nDeadLine, + GetUpper()->Grow( LONG_MAX, true ) ); + + { + SwFrameDeleteGuard g(Lower()); // tdf#134965 prevent RemoveFollowFlowLine() + SetInRecalcLowerRow( true ); + ::lcl_RecalcRow(*static_cast<SwRowFrame*>(Lower()), nDeadLine); + SetInRecalcLowerRow( false ); + } + m_bLowersFormatted = true; + aNotify.SetLowersComplete( true ); + + // One more check if it's really necessary to split the table. + // 1. The table either has to exceed the deadline or + // 2. We explicitly want to cut off the last row. + if( aRectFnSet.BottomDist( getFrameArea(), nDeadLine ) > 0 && !bLastRowHasToMoveToFollow ) + { + continue; + } + + // Set to false again as early as possible. + bLastRowHasToMoveToFollow = false; + + // #i52781# + // YaSC - Yet another special case: + // If our upper is inside a table cell which is not allowed + // to split, we do not try to split: + if ( GetUpper()->IsInTab() ) + { + const SwFrame* pTmpRow = GetUpper(); + while ( pTmpRow && !pTmpRow->IsRowFrame() ) + pTmpRow = pTmpRow->GetUpper(); + if ( pTmpRow && !static_cast<const SwRowFrame*>(pTmpRow)->IsRowSplitAllowed() ) + continue; + } + + sal_uInt16 nMinNumOfLines = nRepeat; + + if ( bTableRowKeep ) + { + const SwRowFrame* pTmpRow = GetFirstNonHeadlineRow(); + while ( pTmpRow && pTmpRow->ShouldRowKeepWithNext() ) + { + ++nMinNumOfLines; + pTmpRow = static_cast<const SwRowFrame*>(pTmpRow->GetNext()); + } + } + + if ( !bTryToSplit ) + ++nMinNumOfLines; + + const SwTwips nBreakLine = aRectFnSet.YInc( + aRectFnSet.GetTop(getFrameArea()), + aRectFnSet.GetTopMargin(*this) + + lcl_GetHeightOfRows( GetLower(), nMinNumOfLines ) ); + + // Some more checks if we want to call the split algorithm or not: + // The repeating lines / keeping lines still fit into the upper or + // if we do not have an (in)direct Prev, we split anyway. + if( aRectFnSet.YDiff(nDeadLine, nBreakLine) >=0 + || !pIndPrev || bEmulateTableKeepSplitAllowed ) + { + aNotify.SetLowersComplete( false ); + bSplit = true; + + // An existing follow flow line has to be removed. + if ( HasFollowFlowLine() ) + { + if (!nThrowAwayValidLayoutLimit) + continue; + const bool bInitialLoopEndCondition(isFrameAreaDefinitionValid()); + RemoveFollowFlowLine(); + const bool bFinalLoopEndCondition(isFrameAreaDefinitionValid()); + + if (bInitialLoopEndCondition && !bFinalLoopEndCondition) + { + --nThrowAwayValidLayoutLimit; + } + } + + oAccess.reset(); + const bool bSplitError = !Split( nDeadLine, bTryToSplit, ( bTableRowKeep && !(bAllowSplitOfRow || bEmulateTableKeepSplitAllowed) ) ); + + // tdf#130639 don't start table on a new page after the fallback "switch off repeating header" + if (bSplitError && nRepeat > GetTable()->GetRowsToRepeat()) + { + setFrameAreaPositionValid(false); + break; + } + + if (!bTryToSplit && !bSplitError) + { + --nUnSplitted; + } + + // #i29771# Two tries to split the table + // If an error occurred during splitting. We start a second + // try, this time without splitting of table rows. + if ( bSplitError && HasFollowFlowLine() ) + RemoveFollowFlowLine(); + + // If splitting the table was successful or not, + // we do not want to have 'empty' follow tables. + if ( GetFollow() && !GetFollow()->GetFirstNonHeadlineRow() ) + Join(); + + // We want to restore the situation before the failed + // split operation as good as possible. Therefore we + // do some more calculations. Note: Restricting this + // to nDeadLine may not be enough. + if ( bSplitError && bTryToSplit ) // no restart if we did not try to split: i72847, i79426 + { + lcl_RecalcRow(*static_cast<SwRowFrame*>(Lower()), LONG_MAX); + setFrameAreaPositionValid(false); + bTryToSplit = false; + continue; + } + + bTryToSplit = !bSplitError; + + //To avoid oscillations the Follow must become valid now + if ( GetFollow() ) + { + // #i80924# + // After a successful split assure that the first row + // is invalid. When graphics are present, this isn't hold. + // Note: defect i80924 could also be fixed, if it is + // assured, that <SwLayNotify::bLowersComplete> is only + // set, if all lower are valid *and* are correct laid out. + if ( !bSplitError && GetFollow()->GetLower() ) + { + GetFollow()->GetLower()->InvalidatePos(); + } + SwRectFnSet fnRectX(GetFollow()); + + static sal_uInt8 nStack = 0; + if ( !StackHack::IsLocked() && nStack < 4 ) + { + ++nStack; + StackHack aHack; + oAccess.reset(); + + GetFollow()->MakeAll(pRenderContext); + + GetFollow()->SetLowersFormatted(false); + // #i43913# - lock follow table + // to avoid its formatting during the format of + // its content. + const bool bOldJoinLock = GetFollow()->IsJoinLocked(); + GetFollow()->LockJoin(); + ::lcl_RecalcRow(*static_cast<SwRowFrame*>(GetFollow()->Lower()), + fnRectX.GetBottom(GetFollow()->GetUpper()->getFrameArea()) ); + // #i43913# + // #i63632# Do not unlock the + // follow if it wasn't locked before. + if ( !bOldJoinLock ) + GetFollow()->UnlockJoin(); + + if ( !GetFollow()->GetFollow() ) + { + SwFrame* pNxt = static_cast<SwFrame*>(GetFollow())->FindNext(); + if ( pNxt ) + { + // #i18103# - no formatting of found next + // frame, if it's a follow section of the + // 'ColLocked' section, the follow table is + // in. + bool bCalcNxt = true; + if ( GetFollow()->IsInSct() && pNxt->IsSctFrame() ) + { + SwSectionFrame* pSct = GetFollow()->FindSctFrame(); + if ( pSct->IsColLocked() && + pSct->GetFollow() == pNxt ) + { + bCalcNxt = false; + } + } + if ( bCalcNxt ) + { + // tdf#119109 follow was just formatted, + // don't do it again now + FlowFrameJoinLockGuard g(GetFollow()); + pNxt->Calc(pRenderContext); + } + } + } + + --nStack; + } + else if ( GetFollow() == GetNext() ) + GetFollow()->MoveFwd( true, false ); + } + continue; + } + } + } + + // Set to false again as early as possible. + bLastRowHasToMoveToFollow = false; + + if( IsInSct() && bMovedFwd && bMakePage && GetUpper()->IsColBodyFrame() && + GetUpper()->GetUpper()->GetUpper()->IsSctFrame() && + ( GetUpper()->GetUpper()->GetPrev() || GetIndPrev() ) && + static_cast<SwSectionFrame*>(GetUpper()->GetUpper()->GetUpper())->MoveAllowed(this) ) + { + bMovedFwd = false; + } + + // #i29771# Reset bTryToSplit flag on change of upper + const SwFrame* pOldUpper = GetUpper(); + + //Let's see if we find some place anywhere... + if (!bMovedFwd) + { + // don't make the effort to move fwd if its known + // conditions that are known not to work + if (IsInFootnote() && ForbiddenForFootnoteCntFwd()) + bMakePage = false; + else if (!MoveFwd(bMakePage, false)) + bMakePage = false; + } + + // #i29771# Reset bSplitError flag on change of upper + if ( GetUpper() != pOldUpper ) + { + bTryToSplit = true; + nUnSplitted = 5; + } + + aRectFnSet.Refresh(this); + m_bCalcLowers = true; + bMovedFwd = true; + aNotify.SetLowersComplete( false ); + if ( IsFollow() ) + { + // To avoid oscillations, master should not remain invalid + SwTabFrame *pTab = FindMaster(); + if ( pTab->GetUpper() ) + pTab->GetUpper()->Calc(pRenderContext); + pTab->Calc(pRenderContext); + pTab->SetLowersFormatted( false ); + } + + //If my neighbour is my Follow at the same time, I'll swallow it up. + if ( ( GetNext() && GetNext() == GetFollow() ) || !GetLower() ) + { + if ( HasFollowFlowLine() ) + RemoveFollowFlowLine(); + if ( GetFollow() ) + Join(); + } + + if ( bMovedBwd && GetUpper() ) + { + //During flowing back the upper was animated to do a full repaint, + //we can now skip this after the whole flowing back and forth. + GetUpper()->ResetCompletePaint(); + } + + if (m_bCalcLowers && isFrameAreaDefinitionValid()) + { + // #i44910# - format of lower frames unnecessary + // and can cause layout loops, if table doesn't fit and isn't + // allowed to split. + SwTwips nDistToUpperPrtBottom = + aRectFnSet.BottomDist( getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper())); + if ( nDistToUpperPrtBottom >= 0 || bTryToSplit ) + { + lcl_RecalcTable( *this, nullptr, aNotify ); + m_bLowersFormatted = true; + m_bCalcLowers = false; + if (!isFramePrintAreaValid()) + m_pTable->SetRowsToRepeat(1); + } +#if OSL_DEBUG_LEVEL > 0 + else + { + OSL_FAIL( "debug assertion: <SwTabFrame::MakeAll()> - format of table lowers suppressed by fix i44910" ); + } +#endif + } + + } //while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() ) + + //If my direct predecessor is my master now, it can destroy me during the + //next best opportunity. + if ( IsFollow() ) + { + SwFrame *pPre = GetPrev(); + if ( pPre && pPre->IsTabFrame() && static_cast<SwTabFrame*>(pPre)->GetFollow() == this) + pPre->InvalidatePos(); + } + + m_bCalcLowers = m_bONECalcLowers = false; + oAccess.reset(); + UnlockJoin(); + if ( bMovedFwd || bMovedBwd || !bOldValidPos ) + aNotify.SetInvaKeep(); +} + +static bool IsNextOnSamePage(SwPageFrame const& rPage, + SwTabFrame const& rTabFrame, SwTextFrame const& rAnchorFrame) +{ + for (SwContentFrame const* pContentFrame = rTabFrame.FindNextCnt(); + pContentFrame && pContentFrame->FindPageFrame() == &rPage; + pContentFrame = pContentFrame->FindNextCnt()) + { + if (pContentFrame == &rAnchorFrame) + { + return true; + } + } + return false; +} + +/// Calculate the offsets arising because of FlyFrames +bool SwTabFrame::CalcFlyOffsets( SwTwips& rUpper, + tools::Long& rLeftOffset, + tools::Long& rRightOffset, + SwTwips *const pSpaceBelowBottom) const +{ + bool bInvalidatePrtArea = false; + const SwPageFrame *pPage = FindPageFrame(); + const SwFlyFrame* pMyFly = FindFlyFrame(); + + // --> #108724# Page header/footer content doesn't have to wrap around + // floating screen objects + + const IDocumentSettingAccess& rIDSA = GetFormat()->getIDocumentSettingAccess(); + const bool bWrapAllowed = rIDSA.get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) || + ( !IsInFootnote() && nullptr == FindFooterOrHeader() ); + + if ( pPage->GetSortedObjs() && bWrapAllowed ) + { + SwRectFnSet aRectFnSet(this); + const bool bConsiderWrapOnObjPos = rIDSA.get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION); + tools::Long nPrtPos = aRectFnSet.GetTop(getFrameArea()); + nPrtPos = aRectFnSet.YInc( nPrtPos, rUpper ); + SwRect aRect( getFrameArea() ); + if (pSpaceBelowBottom) + { // set to space below table frame + aRectFnSet.SetTopAndHeight(aRect, aRectFnSet.GetBottom(aRect), *pSpaceBelowBottom); + } + else + { + tools::Long nYDiff = aRectFnSet.YDiff( aRectFnSet.GetTop(getFramePrintArea()), rUpper ); + if (nYDiff > 0) + aRectFnSet.AddBottom( aRect, -nYDiff ); + } + + bool bAddVerticalFlyOffsets = rIDSA.get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS); + + for ( size_t i = 0; i < pPage->GetSortedObjs()->size(); ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pPage->GetSortedObjs())[i]; + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + const SwRect aFlyRect = pFly->GetObjRectWithSpaces(); + // #i26945# - correction of conditions, + // if Writer fly frame has to be considered: + // - no need to check, if top of Writer fly frame differs + // from FAR_AWAY, because it's also checked, if the Writer + // fly frame rectangle overlaps with <aRect> + // - no check, if bottom of anchor frame is prior the top of + // the table, because Writer fly frames can be negative positioned. + // - correct check, if the Writer fly frame is a lower of the + // table, because table lines/rows can split and an at-character + // anchored Writer fly frame could be positioned in the follow + // flow line. + // - add condition, that an existing anchor character text frame + // has to be on the same page as the table. + // E.g., it could happen, that the fly frame is still registered + // at the page frame, the table is on, but it's anchor character + // text frame has already changed its page. + const SwTextFrame* pAnchorCharFrame = pFly->FindAnchorCharFrame(); + bool bConsiderFly = + // #i46807# - do not consider invalid + // Writer fly frames. + (pFly->isFrameAreaDefinitionValid() || bAddVerticalFlyOffsets) && + // fly anchored at character or at paragraph + pFly->IsFlyAtContentFrame() && + // fly overlaps with corresponding table rectangle + aFlyRect.Overlaps( aRect ) && + // fly isn't lower of table and + // anchor character frame of fly isn't lower of table + (pSpaceBelowBottom // not if in ShouldBwdMoved + || (!IsAnLower( pFly ) && + (!pAnchorCharFrame || !IsAnLower(pAnchorCharFrame)))) && + // table isn't lower of fly + !pFly->IsAnLower( this ) && + // fly is lower of fly, the table is in + // #123274# - correction + // assure that fly isn't a lower of a fly, the table isn't in. + // E.g., a table in the body doesn't wrap around a graphic, + // which is inside a frame. + ( ( !pMyFly || + pMyFly->IsAnLower( pFly ) ) && + pMyFly == pFly->GetAnchorFrameContainingAnchPos()->FindFlyFrame() ) && + // anchor frame not on following page + pPage->GetPhyPageNum() >= + pFly->GetAnchorFrame()->FindPageFrame()->GetPhyPageNum() && + // anchor character text frame on same page + ( !pAnchorCharFrame || + pAnchorCharFrame->FindPageFrame()->GetPhyPageNum() == + pPage->GetPhyPageNum() ); + + if ( bConsiderFly ) + { + const SwFrame* pFlyHeaderFooterFrame = pFly->GetAnchorFrame()->FindFooterOrHeader(); + const SwFrame* pThisHeaderFooterFrame = FindFooterOrHeader(); + + if ( pFlyHeaderFooterFrame != pThisHeaderFooterFrame && + // #148493# If bConsiderWrapOnObjPos is set, + // we want to consider the fly if it is located in the header and + // the table is located in the body: + ( !bConsiderWrapOnObjPos || nullptr != pThisHeaderFooterFrame || !pFlyHeaderFooterFrame->IsHeaderFrame() ) ) + bConsiderFly = false; + } + + if ( bConsiderFly ) + { + const SwFormatSurround &rSur = pFly->GetFormat()->GetSurround(); + const SwFormatHoriOrient &rHori= pFly->GetFormat()->GetHoriOrient(); + bool bShiftDown = css::text::WrapTextMode_NONE == rSur.GetSurround(); + if (!bShiftDown && bAddVerticalFlyOffsets) + { + if (rSur.GetSurround() == text::WrapTextMode_PARALLEL + && rHori.GetHoriOrient() == text::HoriOrientation::NONE) + { + // We know that wrapping was requested and the table frame overlaps with + // the fly frame. Check if the print area overlaps with the fly frame as + // well (in case the table does not use all the available width). + basegfx::B1DRange aTabRange( + aRectFnSet.GetLeft(aRect) + aRectFnSet.GetLeft(getFramePrintArea()), + aRectFnSet.GetLeft(aRect) + aRectFnSet.GetLeft(getFramePrintArea()) + + aRectFnSet.GetWidth(getFramePrintArea())); + + // Ignore spacing when determining the left/right edge of the fly, like + // Word does. + const SwRect aFlyRectWithoutSpaces = pFly->GetObjRect(); + basegfx::B1DRange aFlyRange(aRectFnSet.GetLeft(aFlyRectWithoutSpaces), + aRectFnSet.GetRight(aFlyRectWithoutSpaces)); + + // If it does, shift the table down. Do this only in the compat case, + // normally an SwFlyPortion is created instead that increases the height + // of the first table row. + bShiftDown = aTabRange.overlaps(aFlyRange); + } + } + + if (bShiftDown) + { + // possible cases: + // both in body + // both in same fly + // any comb. of body, footnote, header/footer + // to keep it safe, check only in doc body vs page margin for now + tools::Long nBottom = aRectFnSet.GetBottom(aFlyRect); + // tdf#138039 don't grow beyond the page body + // if the fly is anchored below the table; the fly + // must move with its anchor frame to the next page + SwRectFnSet fnPage(pPage); + if (!IsInDocBody() // TODO + || fnPage.YDiff(fnPage.GetBottom(aFlyRect), fnPage.GetPrtBottom(*pPage)) <= 0 + || !IsNextOnSamePage(*pPage, *this, + *static_cast<SwTextFrame*>(pFly->GetAnchorFrameContainingAnchPos()))) + { + if (aRectFnSet.YDiff( nPrtPos, nBottom ) < 0) + nPrtPos = nBottom; + // tdf#116501 subtract flys blocking space from below + // TODO this may not work ideally for multiple flys + if (pSpaceBelowBottom + && aRectFnSet.YDiff(aRectFnSet.GetBottom(aRect), nBottom) < 0) + { + if (aRectFnSet.YDiff(aRectFnSet.GetTop(aRect), aRectFnSet.GetTop(aFlyRect)) < 0) + { + aRectFnSet.SetBottom(aRect, aRectFnSet.GetTop(aFlyRect)); + } + else + { + aRectFnSet.SetHeight(aRect, 0); + } + } + bInvalidatePrtArea = true; + } + } + if ( (css::text::WrapTextMode_RIGHT == rSur.GetSurround() || + css::text::WrapTextMode_PARALLEL == rSur.GetSurround())&& + text::HoriOrientation::LEFT == rHori.GetHoriOrient() ) + { + const tools::Long nWidth = aRectFnSet.XDiff( + aRectFnSet.GetRight(aFlyRect), + aRectFnSet.GetLeft(pFly->GetAnchorFrame()->getFrameArea()) ); + rLeftOffset = std::max( rLeftOffset, nWidth ); + bInvalidatePrtArea = true; + } + if ( (css::text::WrapTextMode_LEFT == rSur.GetSurround() || + css::text::WrapTextMode_PARALLEL == rSur.GetSurround())&& + text::HoriOrientation::RIGHT == rHori.GetHoriOrient() ) + { + const tools::Long nWidth = aRectFnSet.XDiff( + aRectFnSet.GetRight(pFly->GetAnchorFrame()->getFrameArea()), + aRectFnSet.GetLeft(aFlyRect) ); + rRightOffset = std::max( rRightOffset, nWidth ); + bInvalidatePrtArea = true; + } + } + } + } + rUpper = aRectFnSet.YDiff( nPrtPos, aRectFnSet.GetTop(getFrameArea()) ); + if (pSpaceBelowBottom) + { + *pSpaceBelowBottom = aRectFnSet.GetHeight(aRect); + } + } + + return bInvalidatePrtArea; +} + +/// "Formats" the frame; Frame and PrtArea. +/// The fixed size is not adjusted here. +void SwTabFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs *pAttrs ) +{ + OSL_ENSURE( pAttrs, "TabFrame::Format, pAttrs is 0." ); + + SwRectFnSet aRectFnSet(this); + if ( !isFrameAreaSizeValid() ) + { + tools::Long nDiff = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()) - + aRectFnSet.GetWidth(getFrameArea()); + if( nDiff ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddRight( aFrm, nDiff ); + } + } + + //VarSize is always the height. + //For the upper/lower margins the same rules apply as for ContentFrames (see + //MakePrtArea() of those). + + SwTwips nUpper = CalcUpperSpace( pAttrs ); + + // We want to dodge the flys. Two possibilities: + // 1. There are flys with SurroundNone, dodge them completely + // 2. There are flys which only wrap on the right or the left side and + // those are right or left aligned, those set the minimum for the margins + tools::Long nTmpRight = -1000000, + nLeftOffset = 0; + if (CalcFlyOffsets(nUpper, nLeftOffset, nTmpRight, nullptr)) + { + setFramePrintAreaValid(false); + } + + tools::Long nRightOffset = std::max( tools::Long(0), nTmpRight ); + + SwTwips nLower = pAttrs->CalcBottomLine(); + // #i29550# + if ( IsCollapsingBorders() ) + nLower += GetBottomLineSize(); + + if ( !isFramePrintAreaValid() ) + { + setFramePrintAreaValid(true); + + // The width of the PrintArea is given by the FrameFormat, the margins + // have to be set accordingly. + // Minimum margins are determined depending on borders and shadows. + // The margins are set so that the PrintArea is aligned into the + // Frame according to the adjustment. + // If the adjustment is 0, the margins are set according to the border + // attributes. + + const SwTwips nOldHeight = aRectFnSet.GetHeight(getFramePrintArea()); + const SwTwips nMax = aRectFnSet.GetWidth(getFrameArea()); + + // OD 14.03.2003 #i9040# - adjust variable names. + const SwTwips nLeftLine = pAttrs->CalcLeftLine(); + const SwTwips nRightLine = pAttrs->CalcRightLine(); + + // The width possibly is a percentage value. If the table is inside + // something else, the value refers to the environment. If it's in the + // body then in the BrowseView the value refers to the screen width. + const SwFormatFrameSize &rSz = GetFormat()->GetFrameSize(); + // OD 14.03.2003 #i9040# - adjust variable name. + const SwTwips nWishedTableWidth = CalcRel( rSz ); + + bool bCheckBrowseWidth = false; + + // OD 14.03.2003 #i9040# - insert new variables for left/right spacing. + SwTwips nLeftSpacing = 0; + SwTwips nRightSpacing = 0; + switch ( GetFormat()->GetHoriOrient().GetHoriOrient() ) + { + case text::HoriOrientation::LEFT: + { + // left indent: + nLeftSpacing = nLeftLine + nLeftOffset; + // OD 06.03.2003 #i9040# - correct calculation of right indent: + // - Consider right indent given by right line attributes. + // - Consider negative right indent. + // wished right indent determined by wished table width and + // left offset given by surround fly frames on the left: + const SwTwips nWishRight = nMax - nWishedTableWidth - nLeftOffset; + if ( nRightOffset > 0 ) + { + // surrounding fly frames on the right + // -> right indent is maximum of given right offset + // and wished right offset. + nRightSpacing = nRightLine + std::max( SwTwips(nRightOffset), nWishRight ); + } + else + { + // no surrounding fly frames on the right + // If intrinsic right indent (intrinsic means not considering + // determined left indent) is negative, + // then hold this intrinsic indent, + // otherwise non negative wished right indent is hold. + nRightSpacing = nRightLine + + ( ( (nWishRight+nLeftOffset) < 0 ) ? + (nWishRight+nLeftOffset) : + std::max( SwTwips(0), nWishRight ) ); + } + } + break; + case text::HoriOrientation::RIGHT: + { + // right indent: + nRightSpacing = nRightLine + nRightOffset; + // OD 06.03.2003 #i9040# - correct calculation of left indent: + // - Consider left indent given by left line attributes. + // - Consider negative left indent. + // wished left indent determined by wished table width and + // right offset given by surrounding fly frames on the right: + const SwTwips nWishLeft = nMax - nWishedTableWidth - nRightOffset; + if ( nLeftOffset > 0 ) + { + // surrounding fly frames on the left + // -> right indent is maximum of given left offset + // and wished left offset. + nLeftSpacing = nLeftLine + std::max( SwTwips(nLeftOffset), nWishLeft ); + } + else + { + // no surrounding fly frames on the left + // If intrinsic left indent (intrinsic = not considering + // determined right indent) is negative, + // then hold this intrinsic indent, + // otherwise non negative wished left indent is hold. + nLeftSpacing = nLeftLine + + ( ( (nWishLeft+nRightOffset) < 0 ) ? + (nWishLeft+nRightOffset) : + std::max( SwTwips(0), nWishLeft ) ); + } + } + break; + case text::HoriOrientation::CENTER: + { + // OD 07.03.2003 #i9040# - consider left/right line attribute. + const SwTwips nCenterSpacing = ( nMax - nWishedTableWidth ) / 2; + nLeftSpacing = nLeftLine + + ( (nLeftOffset > 0) ? + std::max( nCenterSpacing, SwTwips(nLeftOffset) ) : + nCenterSpacing ); + nRightSpacing = nRightLine + + ( (nRightOffset > 0) ? + std::max( nCenterSpacing, SwTwips(nRightOffset) ) : + nCenterSpacing ); + } + break; + case text::HoriOrientation::FULL: + //This things grows over the whole width. + //Only the free space needed for the border is taken into + //account. The attribute values of LRSpace are ignored + //intentionally. + bCheckBrowseWidth = true; + nLeftSpacing = nLeftLine + nLeftOffset; + nRightSpacing = nRightLine + nRightOffset; + break; + case text::HoriOrientation::NONE: + { + // The margins are defined by the LRSpace attribute. + nLeftSpacing = pAttrs->CalcLeft( this ); + if( nLeftOffset ) + { + // OD 07.03.2003 #i9040# - surround fly frames only, if + // they overlap with the table. + // Thus, take maximum of left spacing and left offset. + // OD 10.03.2003 #i9040# - consider left line attribute. + nLeftSpacing = std::max( nLeftSpacing, SwTwips( nLeftOffset + nLeftLine ) ); + } + // OD 23.01.2003 #106895# - add 1st param to <SwBorderAttrs::CalcRight(..)> + nRightSpacing = pAttrs->CalcRight( this ); + if( nRightOffset ) + { + // OD 07.03.2003 #i9040# - surround fly frames only, if + // they overlap with the table. + // Thus, take maximum of right spacing and right offset. + // OD 10.03.2003 #i9040# - consider right line attribute. + nRightSpacing = std::max( nRightSpacing, SwTwips( nRightOffset + nRightLine ) ); + } + } + break; + case text::HoriOrientation::LEFT_AND_WIDTH: + { + // count left border and width (Word specialty) + // OD 10.03.2003 #i9040# - no width alignment in online mode. + //bCheckBrowseWidth = true; + nLeftSpacing = pAttrs->CalcLeft( this ); + if( nLeftOffset ) + { + // OD 10.03.2003 #i9040# - surround fly frames only, if + // they overlap with the table. + // Thus, take maximum of right spacing and right offset. + // OD 10.03.2003 #i9040# - consider left line attribute. + nLeftSpacing = std::max( nLeftSpacing, SwTwips( pAttrs->CalcLeftLine() + nLeftOffset ) ); + } + // OD 10.03.2003 #i9040# - consider right and left line attribute. + const SwTwips nWishRight = + nMax - (nLeftSpacing-pAttrs->CalcLeftLine()) - nWishedTableWidth; + nRightSpacing = nRightLine + + ( (nRightOffset > 0) ? + std::max( nWishRight, SwTwips(nRightOffset) ) : + nWishRight ); + } + break; + default: + OSL_FAIL( "Invalid orientation for table." ); + } + + // #i26250# - extend bottom printing area, if table + // is last content inside a table cell. + if ( GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS) && + GetUpper()->IsInTab() && !GetIndNext() ) + { + nLower += pAttrs->GetULSpace().GetLower(); + } + aRectFnSet.SetYMargins( *this, nUpper, nLower ); + if( (nMax - MINLAY) < (nLeftSpacing + nRightSpacing) ) + aRectFnSet.SetXMargins( *this, 0, 0 ); + else + aRectFnSet.SetXMargins( *this, nLeftSpacing, nRightSpacing ); + + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if ( bCheckBrowseWidth && + pSh && pSh->GetViewOptions()->getBrowseMode() && + GetUpper()->IsPageBodyFrame() && // only PageBodyFrames and not ColBodyFrames + pSh->VisArea().Width() ) + { + //Don't go beyond the edge of the visible area. + //The page width can be bigger because objects with + //"over-size" are possible (RootFrame::ImplCalcBrowseWidth()) + tools::Long nWidth = pSh->GetBrowseWidth(); + nWidth -= getFramePrintArea().Left(); + nWidth -= pAttrs->CalcRightLine(); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( std::min( nWidth, aPrt.Width() ) ); + } + + if ( nOldHeight != aRectFnSet.GetHeight(getFramePrintArea()) ) + { + setFrameAreaSizeValid(false); + } + } + + if ( isFrameAreaSizeValid() ) + return; + + setFrameAreaSizeValid(true); + + // The size is defined by the content plus the margins. + SwTwips nRemaining = 0, nDiff; + SwFrame *pFrame = m_pLower; + while ( pFrame ) + { + nRemaining += aRectFnSet.GetHeight(pFrame->getFrameArea()); + pFrame = pFrame->GetNext(); + } + // And now add the margins + nRemaining += nUpper + nLower; + + nDiff = aRectFnSet.GetHeight(getFrameArea()) - nRemaining; + if ( nDiff > 0 ) + Shrink( nDiff ); + else if ( nDiff < 0 ) + Grow( -nDiff ); +} + +SwTwips SwTabFrame::GrowFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + SwRectFnSet aRectFnSet(this); + SwTwips nHeight = aRectFnSet.GetHeight(getFrameArea()); + if( nHeight > 0 && nDist > ( LONG_MAX - nHeight ) ) + nDist = LONG_MAX - nHeight; + + if ( bTst && !IsRestrictTableGrowth() ) + return nDist; + + if ( GetUpper() ) + { + //The upper only grows as far as needed. nReal provides the distance + //which is already available. + SwTwips nReal = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()); + SwFrame *pFrame = GetUpper()->Lower(); + while ( pFrame && GetFollow() != pFrame ) + { + nReal -= aRectFnSet.GetHeight(pFrame->getFrameArea()); + pFrame = pFrame->GetNext(); + } + + if ( nReal < nDist ) + { + tools::Long nTmp = GetUpper()->Grow( nDist - std::max<tools::Long>(nReal, 0), bTst, bInfo ); + + if ( IsRestrictTableGrowth() ) + { + nTmp = std::min( tools::Long(nDist), nReal + nTmp ); + nDist = nTmp < 0 ? 0 : nTmp; + } + } + + if ( !bTst ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nDist ); + } + +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && + pRootFrame->GetCurrShell() ) + { + SwRect aOldFrame( getFrameArea() ); + pRootFrame->GetCurrShell()->Imp()->MoveAccessibleFrame( this, aOldFrame ); + } +#endif + } + } + + if ( !bTst && ( nDist || IsRestrictTableGrowth() ) ) + { + SwPageFrame *pPage = FindPageFrame(); + if ( GetNext() ) + { + GetNext()->InvalidatePos_(); + if ( GetNext()->IsContentFrame() ) + GetNext()->InvalidatePage( pPage ); + } + // #i28701# - Due to the new object positioning the + // frame on the next page/column can flow backward (e.g. it was moved + // forward due to the positioning of its objects ). Thus, invalivate this + // next frame, if document compatibility option 'Consider wrapping style + // influence on object positioning' is ON. + else if ( GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) + { + InvalidateNextPos(); + } + InvalidateAll_(); + InvalidatePage( pPage ); + SetComplete(); + + std::unique_ptr<SvxBrushItem> aBack = GetFormat()->makeBackgroundBrushItem(); + const SvxGraphicPosition ePos = aBack->GetGraphicPos(); + if ( GPOS_NONE != ePos && GPOS_TILED != ePos ) + SetCompletePaint(); + } + + return nDist; +} + +void SwTabFrame::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + SwTabFrameInvFlags eInvFlags = SwTabFrameInvFlags::NONE; + bool bAttrSetChg = pLegacy->m_pNew && RES_ATTRSET_CHG == pLegacy->m_pNew->Which(); + + if(bAttrSetChg) + { + auto& rOldSetChg = *static_cast<const SwAttrSetChg*>(pLegacy->m_pOld); + auto& rNewSetChg = *static_cast<const SwAttrSetChg*>(pLegacy->m_pNew); + SfxItemIter aOIter(*rOldSetChg.GetChgSet()); + SfxItemIter aNIter(*rNewSetChg.GetChgSet()); + const SfxPoolItem* pOItem = aOIter.GetCurItem(); + const SfxPoolItem* pNItem = aNIter.GetCurItem(); + SwAttrSetChg aOldSet(rOldSetChg); + SwAttrSetChg aNewSet(rNewSetChg); + do + { + UpdateAttr_(pOItem, pNItem, eInvFlags, &aOldSet, &aNewSet); + pNItem = aNIter.NextItem(); + pOItem = aOIter.NextItem(); + } while(pNItem); + if(aOldSet.Count() || aNewSet.Count()) + SwLayoutFrame::SwClientNotify(rMod, sw::LegacyModifyHint(&aOldSet, &aNewSet)); + } + else + UpdateAttr_(pLegacy->m_pOld, pLegacy->m_pNew, eInvFlags); + + if(eInvFlags == SwTabFrameInvFlags::NONE) + return; + + SwPageFrame* pPage = FindPageFrame(); + InvalidatePage(pPage); + if(eInvFlags & SwTabFrameInvFlags::InvalidatePrt) + InvalidatePrt_(); + if(eInvFlags & SwTabFrameInvFlags::InvalidatePos) + InvalidatePos_(); + SwFrame* pTmp = GetIndNext(); + if(nullptr != pTmp) + { + if(eInvFlags & SwTabFrameInvFlags::InvalidateIndNextPrt) + { + pTmp->InvalidatePrt_(); + if(pTmp->IsContentFrame()) + pTmp->InvalidatePage(pPage); + } + if(eInvFlags & SwTabFrameInvFlags::SetIndNextCompletePaint) + pTmp->SetCompletePaint(); + } + if(eInvFlags & SwTabFrameInvFlags::InvalidatePrevPrt && nullptr != (pTmp = GetPrev())) + { + pTmp->InvalidatePrt_(); + if(pTmp->IsContentFrame()) + pTmp->InvalidatePage( pPage ); + } + if(eInvFlags & SwTabFrameInvFlags::InvalidateBrowseWidth) + { + if(pPage && pPage->GetUpper() && !IsFollow()) + static_cast<SwRootFrame*>(pPage->GetUpper())->InvalidateBrowseWidth(); + } + if(eInvFlags & SwTabFrameInvFlags::InvalidateNextPos) + InvalidateNextPos(); +} + +void SwTabFrame::UpdateAttr_( const SfxPoolItem *pOld, const SfxPoolItem *pNew, + SwTabFrameInvFlags &rInvFlags, + SwAttrSetChg *pOldSet, SwAttrSetChg *pNewSet ) +{ + bool bClear = true; + const sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + switch( nWhich ) + { + case RES_TBLHEADLINECHG: + if ( IsFollow() ) + { + // Delete remaining headlines: + SwRowFrame* pLowerRow = nullptr; + while ( nullptr != ( pLowerRow = static_cast<SwRowFrame*>(Lower()) ) && pLowerRow->IsRepeatedHeadline() ) + { + pLowerRow->Cut(); + SwFrame::DestroyFrame(pLowerRow); + } + + // insert new headlines + const sal_uInt16 nNewRepeat = GetTable()->GetRowsToRepeat(); + auto& rLines = GetTable()->GetTabLines(); + for ( sal_uInt16 nIdx = 0; nIdx < nNewRepeat; ++nIdx ) + { + SwRowFrame* pHeadline = new SwRowFrame(*rLines[nIdx], this); + { + sw::FlyCreationSuppressor aSuppressor; + pHeadline->SetRepeatedHeadline(true); + } + pHeadline->Paste( this, pLowerRow ); + } + } + rInvFlags |= SwTabFrameInvFlags::InvalidatePrt; + break; + + case RES_FRM_SIZE: + case RES_HORI_ORIENT: + rInvFlags |= SwTabFrameInvFlags::InvalidatePrt | SwTabFrameInvFlags::InvalidateBrowseWidth; + break; + + case RES_PAGEDESC: //Attribute changes (on/off) + if ( IsInDocBody() ) + { + rInvFlags |= SwTabFrameInvFlags::InvalidatePos; + SwPageFrame *pPage = FindPageFrame(); + if (pPage) + { + if ( !GetPrev() ) + CheckPageDescs( pPage ); + if (GetFormat()->GetPageDesc().GetNumOffset()) + static_cast<SwRootFrame*>(pPage->GetUpper())->SetVirtPageNum( true ); + SwDocPosUpdate aMsgHint( pPage->getFrameArea().Top() ); + GetFormat()->GetDoc()->getIDocumentFieldsAccess().UpdatePageFields( &aMsgHint ); + } + } + break; + + case RES_BREAK: + rInvFlags |= SwTabFrameInvFlags::InvalidatePos | SwTabFrameInvFlags::InvalidateNextPos; + break; + + case RES_LAYOUT_SPLIT: + if ( !IsFollow() ) + rInvFlags |= SwTabFrameInvFlags::InvalidatePos; + break; + case RES_FRAMEDIR : + SetDerivedR2L( false ); + CheckDirChange(); + break; + case RES_COLLAPSING_BORDERS : + rInvFlags |= SwTabFrameInvFlags::InvalidatePrt; + lcl_InvalidateAllLowersPrt( this ); + break; + case RES_UL_SPACE: + rInvFlags |= SwTabFrameInvFlags::InvalidateIndNextPrt | SwTabFrameInvFlags::InvalidatePrevPrt | SwTabFrameInvFlags::SetIndNextCompletePaint; + [[fallthrough]]; + + default: + bClear = false; + } + if ( !bClear ) + return; + + if ( pOldSet || pNewSet ) + { + if ( pOldSet ) + pOldSet->ClearItem( nWhich ); + if ( pNewSet ) + pNewSet->ClearItem( nWhich ); + } + else + { + SwModify aMod; + SwLayoutFrame::SwClientNotify(aMod, sw::LegacyModifyHint(pOld, pNew)); + } +} + +bool SwTabFrame::GetInfo( SfxPoolItem &rHint ) const +{ + if ( RES_VIRTPAGENUM_INFO == rHint.Which() && IsInDocBody() && !IsFollow() ) + { + SwVirtPageNumInfo &rInfo = static_cast<SwVirtPageNumInfo&>(rHint); + const SwPageFrame *pPage = FindPageFrame(); + if ( pPage ) + { + if ( pPage == rInfo.GetOrigPage() && !GetPrev() ) + { + // Should be the one (can temporarily be different, should we be + // concerned about this possibility?) + rInfo.SetInfo( pPage, this ); + return false; + } + if ( pPage->GetPhyPageNum() < rInfo.GetOrigPage()->GetPhyPageNum() && + (!rInfo.GetPage() || pPage->GetPhyPageNum() > rInfo.GetPage()->GetPhyPageNum())) + { + //This could be the one. + rInfo.SetInfo( pPage, this ); + } + } + } + return true; +} + +SwFrame *SwTabFrame::FindLastContentOrTable() +{ + SwFrame *pRet = m_pLower; + + while ( pRet && !pRet->IsContentFrame() ) + { + SwFrame *pOld = pRet; + + SwFrame *pTmp = pRet; // To skip empty section frames + while ( pRet->GetNext() ) + { + pRet = pRet->GetNext(); + if( !pRet->IsSctFrame() || static_cast<SwSectionFrame*>(pRet)->GetSection() ) + pTmp = pRet; + } + pRet = pTmp; + + if ( pRet->GetLower() ) + pRet = pRet->GetLower(); + if ( pRet == pOld ) + { + // Check all other columns if there is a column based section with + // an empty last column at the end of the last cell - this is done + // by SwSectionFrame::FindLastContent + if( pRet->IsColBodyFrame() ) + { +#if OSL_DEBUG_LEVEL > 0 + SwSectionFrame* pSect = pRet->FindSctFrame(); + OSL_ENSURE( pSect, "Where does this column come from?"); + OSL_ENSURE( IsAnLower( pSect ), "Split cell?" ); +#endif + return pRet->FindSctFrame()->FindLastContent(); + } + + // pRet may be a cell frame without a lower (cell has been split). + // We have to find the last content the hard way: + + OSL_ENSURE( pRet->IsCellFrame(), "SwTabFrame::FindLastContent failed" ); + const SwFrame* pRow = pRet->GetUpper(); + while ( pRow && !pRow->GetUpper()->IsTabFrame() ) + pRow = pRow->GetUpper(); + const SwContentFrame* pContentFrame = pRow ? static_cast<const SwLayoutFrame*>(pRow)->ContainsContent() : nullptr; + pRet = nullptr; + + while ( pContentFrame && static_cast<const SwLayoutFrame*>(pRow)->IsAnLower( pContentFrame ) ) + { + pRet = const_cast<SwContentFrame*>(pContentFrame); + pContentFrame = pContentFrame->GetNextContentFrame(); + } + } + } + + // #112929# There actually is a situation, which results in pRet = 0: + // Insert frame, insert table via text <-> table. This gives you a frame + // containing a table without any other content frames. Split the table + // and undo the splitting. This operation gives us a table frame without + // a lower. + if ( pRet ) + { + while ( pRet->GetNext() ) + pRet = pRet->GetNext(); + + if (pRet->IsSctFrame()) + pRet = static_cast<SwSectionFrame*>(pRet)->FindLastContent(); + } + + assert(pRet == nullptr || dynamic_cast<SwContentFrame*>(pRet) || dynamic_cast<SwTabFrame*>(pRet)); + return pRet; +} + +SwContentFrame *SwTabFrame::FindLastContent() +{ + SwFrame * pRet(FindLastContentOrTable()); + + while (pRet && pRet->IsTabFrame()) // possibly there's only tables here! + { // tdf#126138 skip table, don't look inside + pRet = pRet->GetPrev(); + } + + assert(pRet == nullptr || dynamic_cast<SwContentFrame*>(pRet)); + return static_cast<SwContentFrame*>(pRet); +} + +/// Return value defines if the frm needs to be relocated +bool SwTabFrame::ShouldBwdMoved( SwLayoutFrame *pNewUpper, bool &rReformat ) +{ + rReformat = false; + if ( SwFlowFrame::IsMoveBwdJump() || !IsPrevObjMove() ) + { + //Flowing back Frames is quite time consuming unfortunately. + //Most often the location where the Frame wants to flow to has the same + //FixSize as the Frame itself. In such a situation it's easy to check if + //the Frame will find enough space for its VarSize, if this is not the + //case, the relocation can be skipped. + //Checking if the Frame will find enough space is done by the Frame itself, + //this also takes the possibility of splitting the Frame into account. + //If the FixSize is different or Flys are involved (at the old or the + //new position) the checks are pointless, the Frame then + //needs to be relocated tentatively (if a bit of space is available). + + //The FixSize of the environments which contain tables is always the + //width. + + SwPageFrame *pOldPage = FindPageFrame(), + *pNewPage = pNewUpper->FindPageFrame(); + bool bMoveAnyway = false; + SwTwips nSpace = 0; + + SwRectFnSet aRectFnSet(this); + if ( !SwFlowFrame::IsMoveBwdJump() ) + { + + tools::Long nOldWidth = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()); + SwRectFnSet fnRectX(pNewUpper); + tools::Long nNewWidth = fnRectX.GetWidth(pNewUpper->getFramePrintArea()); + if( std::abs( nNewWidth - nOldWidth ) < 2 ) + { + bMoveAnyway = BwdMoveNecessary( pOldPage, getFrameArea() ) > 1; + if( !bMoveAnyway ) + { + SwRect aRect( pNewUpper->getFramePrintArea() ); + aRect.Pos() += pNewUpper->getFrameArea().Pos(); + const SwFrame *pPrevFrame = pNewUpper->Lower(); + while ( pPrevFrame && pPrevFrame != this ) + { + fnRectX.SetTop( aRect, fnRectX.GetBottom(pPrevFrame->getFrameArea()) ); + pPrevFrame = pPrevFrame->GetNext(); + } + bMoveAnyway = BwdMoveNecessary( pNewPage, aRect) > 1; + + // #i54861# Due to changes made in PrepareMake, + // the tabfrm may not have a correct position. Therefore + // it is possible that pNewUpper->getFramePrintArea().Height == 0. In this + // case the above calculation of nSpace might give wrong + // results and we really do not want to MoveBackward into a + // 0 height frame. If nTmpSpace is already <= 0, we take this + // value: + const SwTwips nTmpSpace = fnRectX.GetHeight(aRect); + if ( fnRectX.GetHeight(pNewUpper->getFramePrintArea()) > 0 || nTmpSpace <= 0 ) + nSpace = nTmpSpace; + + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh && pSh->GetViewOptions()->getBrowseMode() ) + nSpace += pNewUpper->Grow( LONG_MAX, true ); + if (0 < nSpace && GetPrecede()) + { + SwTwips nUpperDummy(0); + tools::Long nLeftOffsetDummy(0), nRightOffsetDummy(0); + // tdf#116501 check for no-wrap fly overlap + static_cast<const SwTabFrame*>(GetPrecede())->CalcFlyOffsets( + nUpperDummy, nLeftOffsetDummy, nRightOffsetDummy, &nSpace); + } + } + } + else if (!m_bLockBackMove) + bMoveAnyway = true; + } + else if (!m_bLockBackMove) + bMoveAnyway = true; + + if ( bMoveAnyway ) + { + rReformat = true; + return true; + } + + bool bFits = nSpace > 0; + if (!bFits && aRectFnSet.GetHeight(getFrameArea()) == 0) + // This frame fits into pNewUpper in case it has no space, but this + // frame is empty. + bFits = nSpace >= 0; + if (!m_bLockBackMove && bFits) + { + // #i26945# - check, if follow flow line + // contains frame, which are moved forward due to its object + // positioning. + const SwRowFrame* pFirstRow = GetFirstNonHeadlineRow(); + if ( pFirstRow && pFirstRow->IsInFollowFlowRow() && + SwLayouter::DoesRowContainMovedFwdFrame( + *(pFirstRow->GetFormat()->GetDoc()), + *pFirstRow ) ) + { + return false; + } + SwTwips nTmpHeight = CalcHeightOfFirstContentLine(); + + // For some mysterious reason, I changed the good old + // 'return nHeight <= nSpace' to 'return nTmpHeight < nSpace'. + // This obviously results in problems with table frames in + // sections. Remember: Every twip is sacred. + return nTmpHeight <= nSpace; + } + } + return false; +} + +void SwTabFrame::Cut() +{ + OSL_ENSURE( GetUpper(), "Cut without Upper()." ); + + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + SwFrame *pFrame = GetNext(); + if( pFrame ) + { + // Possibly the old follow calculated a spacing to the predecessor + // which is obsolete now when it becomes the first frame + pFrame->InvalidatePrt_(); + pFrame->InvalidatePos_(); + if ( pFrame->IsContentFrame() ) + pFrame->InvalidatePage( pPage ); + if( IsInSct() && !GetPrev() ) + { + SwSectionFrame* pSct = FindSctFrame(); + if( !pSct->IsFollow() ) + { + pSct->InvalidatePrt_(); + pSct->InvalidatePage( pPage ); + } + } + } + else + { + InvalidateNextPos(); + //Someone has to do the retouch: predecessor or upper + pFrame = GetPrev(); + if ( nullptr != pFrame ) + { + pFrame->SetRetouche(); + pFrame->Prepare( PrepareHint::WidowsOrphans ); + pFrame->InvalidatePos_(); + if ( pFrame->IsContentFrame() ) + pFrame->InvalidatePage( pPage ); + } + //If I am (was) the only FlowFrame in my own upper, it has to do + //the retouch. Moreover a new empty page might be created. + else + { SwRootFrame *pRoot = static_cast<SwRootFrame*>(pPage->GetUpper()); + pRoot->SetSuperfluous(); + GetUpper()->SetCompletePaint(); + if( IsInSct() ) + { + SwSectionFrame* pSct = FindSctFrame(); + if( !pSct->IsFollow() ) + { + pSct->InvalidatePrt_(); + pSct->InvalidatePage( pPage ); + } + } + } + } + + //First remove, then shrink the upper. + SwLayoutFrame *pUp = GetUpper(); + SwRectFnSet aRectFnSet(this); + RemoveFromLayout(); + if ( pUp ) + { + OSL_ENSURE( !pUp->IsFootnoteFrame(), "Table in Footnote." ); + SwSectionFrame *pSct = nullptr; + // #126020# - adjust check for empty section + // #130797# - correct fix #126020# + if ( !pUp->Lower() && pUp->IsInSct() && + !(pSct = pUp->FindSctFrame())->ContainsContent() && + !pSct->ContainsAny( true ) ) + { + if ( pUp->GetUpper() ) + { + pSct->DelEmpty( false ); + pSct->InvalidateSize_(); + } + } + // table-in-footnote: delete empty footnote frames (like SwContentFrame::Cut) + else if (!pUp->Lower() && pUp->IsFootnoteFrame() && !pUp->IsColLocked()) + { + if (pUp->GetNext() && !pUp->GetPrev()) + { + if (SwFrame *const pTmp = static_cast<SwLayoutFrame*>(pUp->GetNext())->ContainsAny()) + { + pTmp->InvalidatePrt_(); + } + } + if (!pUp->IsDeleteForbidden()) + { + pUp->Cut(); + SwFrame::DestroyFrame(pUp); + } + } + else if( aRectFnSet.GetHeight(getFrameArea()) ) + { + // OD 26.08.2003 #i18103# - *no* 'ColUnlock' of section - + // undo changes of fix for #104992# + pUp->Shrink( getFrameArea().Height() ); + } + } + + + if ( pPage && !IsFollow() && pPage->GetUpper() ) + static_cast<SwRootFrame*>(pPage->GetUpper())->InvalidateBrowseWidth(); +} + +void SwTabFrame::Paste( SwFrame* pParent, SwFrame* pSibling ) +{ + OSL_ENSURE( pParent, "No parent for pasting." ); + OSL_ENSURE( pParent->IsLayoutFrame(), "Parent is ContentFrame." ); + OSL_ENSURE( pParent != this, "I'm the parent myself." ); + OSL_ENSURE( pSibling != this, "I'm my own neighbour." ); + OSL_ENSURE( !GetPrev() && !GetNext() && !GetUpper(), + "I'm still registered somewhere." ); + + //Insert in the tree. + InsertBefore( static_cast<SwLayoutFrame*>(pParent), pSibling ); + + InvalidateAll_(); + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + + if ( GetNext() ) + { + GetNext()->InvalidatePos_(); + GetNext()->InvalidatePrt_(); + if ( GetNext()->IsContentFrame() ) + GetNext()->InvalidatePage( pPage ); + } + + SwRectFnSet aRectFnSet(this); + if( aRectFnSet.GetHeight(getFrameArea()) ) + pParent->Grow( aRectFnSet.GetHeight(getFrameArea()) ); + + if( aRectFnSet.GetWidth(getFrameArea()) != aRectFnSet.GetWidth(pParent->getFramePrintArea()) ) + Prepare( PrepareHint::FixSizeChanged ); + if ( GetPrev() ) + { + if ( !IsFollow() ) + { + GetPrev()->InvalidateSize(); + if ( GetPrev()->IsContentFrame() ) + GetPrev()->InvalidatePage( pPage ); + } + } + else if ( GetNext() ) + // Take the spacing into account when dealing with ContentFrames. + // There are two situations (both always happen at the same time): + // a) The Content becomes the first in a chain + // b) The new follower was previously the first in a chain + GetNext()->InvalidatePrt_(); + + if ( !pPage || IsFollow() ) + return; + + if ( pPage->GetUpper() ) + static_cast<SwRootFrame*>(pPage->GetUpper())->InvalidateBrowseWidth(); + + if ( !GetPrev() )//At least needed for HTML with a table at the beginning. + { + const SwPageDesc *pDesc = GetFormat()->GetPageDesc().GetPageDesc(); + if ( (pDesc && pDesc != pPage->GetPageDesc()) || + (!pDesc && pPage->GetPageDesc() != &GetFormat()->GetDoc()->GetPageDesc(0)) ) + CheckPageDescs( pPage ); + } +} + +bool SwTabFrame::Prepare( const PrepareHint eHint, const void *, bool ) +{ + if( PrepareHint::BossChanged == eHint ) + CheckDirChange(); + return false; +} + +SwRowFrame::SwRowFrame(const SwTableLine &rLine, SwFrame* pSib, bool bInsertContent) + : SwLayoutFrame( rLine.GetFrameFormat(), pSib ) + , m_pTabLine( &rLine ) + , m_pFollowRow( nullptr ) + // #i29550# + , mnTopMarginForLowers( 0 ) + , mnBottomMarginForLowers( 0 ) + , mnBottomLineSize( 0 ) + // --> split table rows + , m_bIsFollowFlowRow( false ) + // <-- split table rows + , m_bIsRepeatedHeadline( false ) + , m_bIsRowSpanLine( false ) + , m_bForceRowSplitAllowed( false ) + , m_bIsInSplit( false ) +{ + mnFrameType = SwFrameType::Row; + + //Create the boxes and insert them. + const SwTableBoxes &rBoxes = rLine.GetTabBoxes(); + SwFrame *pTmpPrev = nullptr; + for ( size_t i = 0; i < rBoxes.size(); ++i ) + { + SwCellFrame *pNew = new SwCellFrame( *rBoxes[i], this, bInsertContent ); + pNew->InsertBehind( this, pTmpPrev ); + pTmpPrev = pNew; + } +} + +void SwRowFrame::DestroyImpl() +{ + sw::BroadcastingModify* pMod = GetFormat(); + if( pMod ) + { + pMod->Remove( this ); + if( !pMod->HasWriterListeners() ) + delete pMod; + } + + SwLayoutFrame::DestroyImpl(); +} + +SwRowFrame::~SwRowFrame() +{ +} + +void SwRowFrame::RegistFlys( SwPageFrame *pPage ) +{ + ::RegistFlys( pPage ? pPage : FindPageFrame(), this ); +} + +void SwRowFrame::OnFrameSize(const SfxPoolItem& rSize) +{ + SwTabFrame* pTab = FindTabFrame(); + if(pTab) + { + const bool bInFirstNonHeadlineRow = pTab->IsFollow() && this == pTab->GetFirstNonHeadlineRow(); + // #i35063# + // Invalidation required is pRow is last row + if(bInFirstNonHeadlineRow) + pTab = pTab->FindMaster(); + if(bInFirstNonHeadlineRow || !GetNext()) + pTab->InvalidatePos(); + } + const sw::BroadcastingModify aMod; + SwLayoutFrame::SwClientNotify(aMod, sw::LegacyModifyHint(nullptr, &rSize)); +} + +void SwRowFrame::SwClientNotify(const SwModify& rModify, const SfxHint& rHint) +{ + if(auto pNewFormatHint = dynamic_cast<const sw::TableLineFormatChanged*>(&rHint)) + { + if(GetTabLine() != &pNewFormatHint->m_rTabLine) + return; + RegisterToFormat(const_cast<SwTableLineFormat&>(pNewFormatHint->m_rNewFormat)); + InvalidateSize(); + InvalidatePrt_(); + SetCompletePaint(); + ReinitializeFrameSizeAttrFlags(); + + // #i35063# + // consider 'split row allowed' attribute + SwTabFrame* pTab = FindTabFrame(); + bool bInFollowFlowRow = false; + const bool bInFirstNonHeadlineRow = pTab->IsFollow() && this == pTab->GetFirstNonHeadlineRow(); + if(bInFirstNonHeadlineRow || + !GetNext() || + (bInFollowFlowRow = IsInFollowFlowRow()) || + nullptr != IsInSplitTableRow() ) + { + if(bInFirstNonHeadlineRow || bInFollowFlowRow) + pTab = pTab->FindMaster(); + + pTab->SetRemoveFollowFlowLinePending(true); + pTab->InvalidatePos(); + } + } + else if(auto pMoveTableLineHint = dynamic_cast<const sw::MoveTableLineHint*>(&rHint)) + { + + if(GetTabLine() != &pMoveTableLineHint->m_rTableLine) + return; + const_cast<SwFrameFormat*>(&pMoveTableLineHint->m_rNewFormat)->Add(this); + InvalidateAll(); + ReinitializeFrameSizeAttrFlags(); + return; + } + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + if(!pLegacy->m_pNew) + { + // possibly not needed? + SwLayoutFrame::SwClientNotify(rModify, rHint); + return; + } + switch(pLegacy->m_pNew->Which()) + { + case RES_ATTRSET_CHG: + { + const SwAttrSet* pChgSet = static_cast<const SwAttrSetChg*>(pLegacy->m_pNew)->GetChgSet(); + const SfxPoolItem* pItem = nullptr; + pChgSet->GetItemState(RES_FRM_SIZE, false, &pItem); + if(!pItem) + pChgSet->GetItemState(RES_ROW_SPLIT, false, &pItem); + if(pItem) + OnFrameSize(*pItem); + else + SwLayoutFrame::SwClientNotify(rModify, rHint); // possibly not needed? + return; + } + case RES_FRM_SIZE: + case RES_ROW_SPLIT: + OnFrameSize(*static_cast<const SwFormatFrameSize*>(pLegacy->m_pNew)); + return; + } +} + +void SwRowFrame::MakeAll(vcl::RenderContext* pRenderContext) +{ + if ( !GetNext() ) + { + setFrameAreaSizeValid(false); + } + + SwLayoutFrame::MakeAll(pRenderContext); +} + +tools::Long CalcHeightWithFlys( const SwFrame *pFrame ) +{ + SwRectFnSet aRectFnSet(pFrame); + tools::Long nHeight = 0; + const SwFrame* pTmp = pFrame->IsSctFrame() ? + static_cast<const SwSectionFrame*>(pFrame)->ContainsContent() : pFrame; + while( pTmp ) + { + // #i26945# - consider follow text frames + const SwSortedObjs* pObjs( nullptr ); + bool bIsFollow( false ); + if ( pTmp->IsTextFrame() && static_cast<const SwTextFrame*>(pTmp)->IsFollow() ) + { + const SwFrame* pMaster; + // #i46450# Master does not necessarily have + // to exist if this function is called from JoinFrame() -> + // Cut() -> Shrink() + const SwTextFrame* pTmpFrame = static_cast<const SwTextFrame*>(pTmp); + if ( pTmpFrame->GetPrev() && pTmpFrame->GetPrev()->IsTextFrame() && + static_cast<const SwTextFrame*>(pTmpFrame->GetPrev())->GetFollow() && + static_cast<const SwTextFrame*>(pTmpFrame->GetPrev())->GetFollow() != pTmp ) + pMaster = nullptr; + else + pMaster = pTmpFrame->FindMaster(); + + if ( pMaster ) + { + pObjs = static_cast<const SwTextFrame*>(pTmp)->FindMaster()->GetDrawObjs(); + bIsFollow = true; + } + } + else + { + pObjs = pTmp->GetDrawObjs(); + } + if ( pObjs ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + // #i26945# - if <pTmp> is follow, the + // anchor character frame has to be <pTmp>. + if ( bIsFollow && + pAnchoredObj->FindAnchorCharFrame() != pTmp ) + { + continue; + } + // #i26945# - consider also drawing objects + { + // OD 30.09.2003 #i18732# - only objects, which follow + // the text flow have to be considered. + const SwFrameFormat& rFrameFormat = pAnchoredObj->GetFrameFormat(); + bool bFollowTextFlow = rFrameFormat.GetFollowTextFlow().GetValue(); + bool bIsFarAway = pAnchoredObj->GetObjRect().Top() != FAR_AWAY; + const SwPageFrame* pPageFrm = pTmp->FindPageFrame(); + bool bIsAnchoredToTmpFrm = false; + if ( pPageFrm && pPageFrm->IsPageFrame() && pAnchoredObj->GetPageFrame()) + bIsAnchoredToTmpFrm = pAnchoredObj->GetPageFrame() == pPageFrm || + (pPageFrm->GetFormatPage().GetPhyPageNum() == pAnchoredObj->GetPageFrame()->GetFormatPage().GetPhyPageNum() + 1); + const bool bConsiderObj = + (rFrameFormat.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) && + bIsFarAway && + bFollowTextFlow && bIsAnchoredToTmpFrm; + bool bWrapThrough = rFrameFormat.GetSurround().GetValue() == text::WrapTextMode_THROUGH; + bool bInBackground = !rFrameFormat.GetOpaque().GetValue(); + if (pFrame->IsInTab() && bFollowTextFlow && bWrapThrough && bInBackground) + { + // Ignore wrap-through objects when determining the cell height. + // Normally FollowTextFlow requires a resize of the cell, but not in case of + // wrap-through. + continue; + } + + if ( bConsiderObj ) + { + const SwFormatFrameSize &rSz = rFrameFormat.GetFrameSize(); + if( !rSz.GetHeightPercent() ) + { + const SwTwips nDistOfFlyBottomToAnchorTop = + aRectFnSet.GetHeight(pAnchoredObj->GetObjRect()) + + ( aRectFnSet.IsVert() ? + pAnchoredObj->GetCurrRelPos().X() : + pAnchoredObj->GetCurrRelPos().Y() ); + + const SwTwips nFrameDiff = + aRectFnSet.YDiff( + aRectFnSet.GetTop(pTmp->getFrameArea()), + aRectFnSet.GetTop(pFrame->getFrameArea()) ); + + nHeight = std::max( nHeight, nDistOfFlyBottomToAnchorTop + nFrameDiff - + aRectFnSet.GetHeight(pFrame->getFrameArea()) ); + + // #i56115# The first height calculation + // gives wrong results if pFrame->getFramePrintArea().Y() > 0. We do + // a second calculation based on the actual rectangles of + // pFrame and pAnchoredObj, and use the maximum of the results. + // I do not want to remove the first calculation because + // if clipping has been applied, using the GetCurrRelPos + // might be the better option to calculate nHeight. + const SwTwips nDistOfFlyBottomToAnchorTop2 = aRectFnSet.YDiff( + aRectFnSet.GetBottom(pAnchoredObj->GetObjRect()), + aRectFnSet.GetBottom(pFrame->getFrameArea()) ); + + nHeight = std::max( nHeight, tools::Long(nDistOfFlyBottomToAnchorTop2 )); + } + } + } + } + } + if( !pFrame->IsSctFrame() ) + break; + pTmp = pTmp->FindNextCnt(); + if( !static_cast<const SwSectionFrame*>(pFrame)->IsAnLower( pTmp ) ) + break; + } + return nHeight; +} + +static SwTwips lcl_CalcTopAndBottomMargin( const SwLayoutFrame& rCell, const SwBorderAttrs& rAttrs ) +{ + const SwTabFrame* pTab = rCell.FindTabFrame(); + SwTwips nTopSpace = 0; + SwTwips nBottomSpace = 0; + + // #i29550# + if ( pTab->IsCollapsingBorders() && rCell.Lower() && !rCell.Lower()->IsRowFrame() ) + { + nTopSpace = static_cast<const SwRowFrame*>(rCell.GetUpper())->GetTopMarginForLowers(); + nBottomSpace = static_cast<const SwRowFrame*>(rCell.GetUpper())->GetBottomMarginForLowers(); + } + else + { + if ( pTab->IsVertical() != rCell.IsVertical() ) + { + nTopSpace = rAttrs.CalcLeft( &rCell ); + nBottomSpace = rAttrs.CalcRight( &rCell ); + } + else + { + nTopSpace = rAttrs.CalcTop(); + nBottomSpace = rAttrs.CalcBottom(); + } + } + + return nTopSpace + nBottomSpace; +} + +// #i26945# - add parameter <_bConsiderObjs> in order to +// control, if floating screen objects have to be considered for the minimal +// cell height. +static SwTwips lcl_CalcMinCellHeight( const SwLayoutFrame *_pCell, + const bool _bConsiderObjs, + const SwBorderAttrs *pAttrs = nullptr ) +{ + SwRectFnSet aRectFnSet(_pCell); + SwTwips nHeight = 0; + const SwFrame* pLow = _pCell->Lower(); + if ( pLow ) + { + tools::Long nFlyAdd = 0; + while ( pLow ) + { + if ( pLow->IsRowFrame() ) + { + // #i26945# + nHeight += ::lcl_CalcMinRowHeight( static_cast<const SwRowFrame*>(pLow), + _bConsiderObjs ); + } + else + { + tools::Long nLowHeight = aRectFnSet.GetHeight(pLow->getFrameArea()); + nHeight += nLowHeight; + // #i26945# + if ( _bConsiderObjs ) + { + nFlyAdd = std::max( tools::Long(0), nFlyAdd - nLowHeight ); + nFlyAdd = std::max( nFlyAdd, ::CalcHeightWithFlys( pLow ) ); + } + } + + pLow = pLow->GetNext(); + } + if ( nFlyAdd ) + nHeight += nFlyAdd; + } + // The border/margin needs to be considered too, unfortunately it can't be + // calculated using PrintArea and FrameArea because any or all of those + // may be invalid. + if ( _pCell->Lower() ) + { + if ( pAttrs ) + nHeight += lcl_CalcTopAndBottomMargin( *_pCell, *pAttrs ); + else + { + SwBorderAttrAccess aAccess( SwFrame::GetCache(), _pCell ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + nHeight += lcl_CalcTopAndBottomMargin( *_pCell, rAttrs ); + } + } + return nHeight; +} + +// #i26945# - add parameter <_bConsiderObjs> in order to control, +// if floating screen objects have to be considered for the minimal cell height +static SwTwips lcl_CalcMinRowHeight( const SwRowFrame* _pRow, + const bool _bConsiderObjs ) +{ + SwTwips nHeight = 0; + if ( !_pRow->IsRowSpanLine() ) + { + const SwFormatFrameSize &rSz = _pRow->GetFormat()->GetFrameSize(); + if ( _pRow->HasFixSize() ) + { + OSL_ENSURE(SwFrameSize::Fixed == rSz.GetHeightSizeType(), "pRow claims to have fixed size"); + return rSz.GetHeight(); + } + // If this row frame is being split, then row's minimal height shouldn't restrict + // this frame's minimal height, because the rest will go to follow frame. + else if ( !_pRow->IsInSplit() && rSz.GetHeightSizeType() == SwFrameSize::Minimum ) + { + nHeight = rSz.GetHeight() - lcl_calcHeightOfRowBeforeThisFrame(*_pRow); + } + } + + SwRectFnSet aRectFnSet(_pRow); + const SwCellFrame* pLow = static_cast<const SwCellFrame*>(_pRow->Lower()); + while ( pLow ) + { + SwTwips nTmp = 0; + const tools::Long nRowSpan = pLow->GetLayoutRowSpan(); + // --> NEW TABLES + // Consider height of + // 1. current cell if RowSpan == 1 + // 2. current cell if cell is "follow" cell of a cell with RowSpan == -1 + // 3. master cell if RowSpan == -1 + if ( 1 == nRowSpan ) + { + nTmp = ::lcl_CalcMinCellHeight( pLow, _bConsiderObjs ); + } + else if ( -1 == nRowSpan ) + { + // Height of the last cell of a row span is height of master cell + // minus the height of the other rows which are covered by the master + // cell: + const SwCellFrame& rMaster = pLow->FindStartEndOfRowSpanCell( true ); + nTmp = ::lcl_CalcMinCellHeight( &rMaster, _bConsiderObjs ); + const SwFrame* pMasterRow = rMaster.GetUpper(); + while ( pMasterRow && pMasterRow != _pRow ) + { + nTmp -= aRectFnSet.GetHeight(pMasterRow->getFrameArea()); + pMasterRow = pMasterRow->GetNext(); + } + } + // <-- NEW TABLES + + // Do not consider rotated cells: + if ( pLow->IsVertical() == aRectFnSet.IsVert() && nTmp > nHeight ) + nHeight = nTmp; + + pLow = static_cast<const SwCellFrame*>(pLow->GetNext()); + } + + return nHeight; +} + +// #i29550# + +// Calculate the maximum of (TopLineSize + TopLineDist) over all lowers: +static sal_uInt16 lcl_GetTopSpace( const SwRowFrame& rRow ) +{ + sal_uInt16 nTopSpace = 0; + for ( const SwCellFrame* pCurrLower = static_cast<const SwCellFrame*>(rRow.Lower()); pCurrLower; + pCurrLower = static_cast<const SwCellFrame*>(pCurrLower->GetNext()) ) + { + sal_uInt16 nTmpTopSpace = 0; + if ( pCurrLower->Lower() && pCurrLower->Lower()->IsRowFrame() ) + nTmpTopSpace = lcl_GetTopSpace( *static_cast<const SwRowFrame*>(pCurrLower->Lower()) ); + else + { + const SwAttrSet& rSet = const_cast<SwCellFrame*>(pCurrLower)->GetFormat()->GetAttrSet(); + const SvxBoxItem& rBoxItem = rSet.GetBox(); + nTmpTopSpace = rBoxItem.CalcLineSpace( SvxBoxItemLine::TOP, true ); + } + nTopSpace = std::max( nTopSpace, nTmpTopSpace ); + } + return nTopSpace; +} + +// Calculate the maximum of TopLineDist over all lowers: +static sal_uInt16 lcl_GetTopLineDist( const SwRowFrame& rRow ) +{ + sal_uInt16 nTopLineDist = 0; + for ( const SwCellFrame* pCurrLower = static_cast<const SwCellFrame*>(rRow.Lower()); pCurrLower; + pCurrLower = static_cast<const SwCellFrame*>(pCurrLower->GetNext()) ) + { + sal_uInt16 nTmpTopLineDist = 0; + if ( pCurrLower->Lower() && pCurrLower->Lower()->IsRowFrame() ) + nTmpTopLineDist = lcl_GetTopLineDist( *static_cast<const SwRowFrame*>(pCurrLower->Lower()) ); + else + { + const SwAttrSet& rSet = const_cast<SwCellFrame*>(pCurrLower)->GetFormat()->GetAttrSet(); + const SvxBoxItem& rBoxItem = rSet.GetBox(); + nTmpTopLineDist = rBoxItem.GetDistance( SvxBoxItemLine::TOP ); + } + nTopLineDist = std::max( nTopLineDist, nTmpTopLineDist ); + } + return nTopLineDist; +} + +// Calculate the maximum of BottomLineSize over all lowers: +static sal_uInt16 lcl_GetBottomLineSize( const SwRowFrame& rRow ) +{ + sal_uInt16 nBottomLineSize = 0; + for ( const SwCellFrame* pCurrLower = static_cast<const SwCellFrame*>(rRow.Lower()); pCurrLower; + pCurrLower = static_cast<const SwCellFrame*>(pCurrLower->GetNext()) ) + { + sal_uInt16 nTmpBottomLineSize = 0; + if ( pCurrLower->Lower() && pCurrLower->Lower()->IsRowFrame() ) + { + const SwFrame* pRow = pCurrLower->GetLastLower(); + nTmpBottomLineSize = lcl_GetBottomLineSize( *static_cast<const SwRowFrame*>(pRow) ); + } + else + { + const SwAttrSet& rSet = const_cast<SwCellFrame*>(pCurrLower)->GetFormat()->GetAttrSet(); + const SvxBoxItem& rBoxItem = rSet.GetBox(); + nTmpBottomLineSize = rBoxItem.CalcLineSpace( SvxBoxItemLine::BOTTOM, true ) - + rBoxItem.GetDistance( SvxBoxItemLine::BOTTOM ); + } + nBottomLineSize = std::max( nBottomLineSize, nTmpBottomLineSize ); + } + return nBottomLineSize; +} + +// Calculate the maximum of BottomLineDist over all lowers: +static sal_uInt16 lcl_GetBottomLineDist( const SwRowFrame& rRow ) +{ + sal_uInt16 nBottomLineDist = 0; + for ( const SwCellFrame* pCurrLower = static_cast<const SwCellFrame*>(rRow.Lower()); pCurrLower; + pCurrLower = static_cast<const SwCellFrame*>(pCurrLower->GetNext()) ) + { + sal_uInt16 nTmpBottomLineDist = 0; + if ( pCurrLower->Lower() && pCurrLower->Lower()->IsRowFrame() ) + { + const SwFrame* pRow = pCurrLower->GetLastLower(); + nTmpBottomLineDist = lcl_GetBottomLineDist( *static_cast<const SwRowFrame*>(pRow) ); + } + else + { + const SwAttrSet& rSet = const_cast<SwCellFrame*>(pCurrLower)->GetFormat()->GetAttrSet(); + const SvxBoxItem& rBoxItem = rSet.GetBox(); + nTmpBottomLineDist = rBoxItem.GetDistance( SvxBoxItemLine::BOTTOM ); + } + nBottomLineDist = std::max( nBottomLineDist, nTmpBottomLineDist ); + } + return nBottomLineDist; +} + +// tdf#104425: calculate the height of all row frames, +// for which this frame is a follow. +// When a row has fixed/minimum height, it may span over +// several pages. The minimal height on this page should +// take into account the sum of all the heights of previous +// frames that constitute the table row on previous pages. +// Otherwise, trying to split a too high row frame will +// result in loop trying to create that too high row +// on each following page +static SwTwips lcl_calcHeightOfRowBeforeThisFrame(const SwRowFrame& rRow) +{ + // We don't need to account for previous instances of repeated headlines + if (rRow.IsRepeatedHeadline()) + return 0; + SwRectFnSet aRectFnSet(&rRow); + const SwTableLine* pLine = rRow.GetTabLine(); + const SwTabFrame* pTab = rRow.FindTabFrame(); + if (!pLine || !pTab || !pTab->IsFollow()) + return 0; + SwTwips nResult = 0; + SwIterator<SwRowFrame, SwFormat> aIter(*pLine->GetFrameFormat()); + for (const SwRowFrame* pCurRow = aIter.First(); pCurRow; pCurRow = aIter.Next()) + { + if (pCurRow != &rRow && pCurRow->GetTabLine() == pLine) + { + // We've found another row frame that is part of the same table row + const SwTabFrame* pCurTab = pCurRow->FindTabFrame(); + // A row frame may not belong to a table frame, when it is being cut, e.g., in + // lcl_PostprocessRowsInCells(). + // Its SwRowFrame::Cut() has been called; it in turn called SwLayoutFrame::Cut(), + // which nullified row's upper in RemoveFromLayout(), and then called Shrink() + // for its former upper. + // Regardless of whether it will be pasted back, or destroyed, currently it's not + // part of layout, and its height does not count + if (pCurTab && pCurTab->IsAnFollow(pTab)) + { + // The found row frame belongs to a table frame that precedes + // (above) this one in chain. So, include it in the sum + nResult += aRectFnSet.GetHeight(pCurRow->getFrameArea()); + } + } + } + return nResult; +} + +void SwRowFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs *pAttrs ) +{ + SwRectFnSet aRectFnSet(this); + OSL_ENSURE( pAttrs, "SwRowFrame::Format without Attrs." ); + + const bool bFix = mbFixSize; + + if ( !isFramePrintAreaValid() ) + { + // RowFrames don't have borders/margins therefore the PrintArea always + // matches the FrameArea. + setFramePrintAreaValid(true); + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Left( 0 ); + aPrt.Top( 0 ); + aPrt.Width ( getFrameArea().Width() ); + aPrt.Height( getFrameArea().Height() ); + } + + // #i29550# + // Here we calculate the top-printing area for the lower cell frames + SwTabFrame* pTabFrame = FindTabFrame(); + if ( pTabFrame->IsCollapsingBorders() ) + { + const sal_uInt16 nTopSpace = lcl_GetTopSpace( *this ); + const sal_uInt16 nTopLineDist = lcl_GetTopLineDist( *this ); + const sal_uInt16 nBottomLineSize = lcl_GetBottomLineSize( *this ); + const sal_uInt16 nBottomLineDist = lcl_GetBottomLineDist( *this ); + + const SwRowFrame* pPreviousRow = nullptr; + + // #i32456# + // In order to calculate the top printing area for the lower cell + // frames, we have to find the 'previous' row frame and compare + // the bottom values of the 'previous' row with the 'top' values + // of this row. The best way to find the 'previous' row is to + // use the table structure: + const SwTable* pTable = pTabFrame->GetTable(); + const SwTableLine* pPrevTabLine = nullptr; + const SwRowFrame* pTmpRow = this; + + while ( pTmpRow && !pPrevTabLine ) + { + size_t nIdx = 0; + const SwTableLines& rLines = pTmpRow->GetTabLine()->GetUpper() ? + pTmpRow->GetTabLine()->GetUpper()->GetTabLines() : + pTable->GetTabLines(); + + while ( rLines[ nIdx ] != pTmpRow->GetTabLine() ) + ++nIdx; + + if ( nIdx > 0 ) + { + // pTmpRow has a 'previous' row in the table structure: + pPrevTabLine = rLines[ nIdx - 1 ]; + } + else + { + // pTmpRow is a first row in the table structure. + // We go up in the table structure: + pTmpRow = pTmpRow->GetUpper()->GetUpper() && + pTmpRow->GetUpper()->GetUpper()->IsRowFrame() ? + static_cast<const SwRowFrame*>( pTmpRow->GetUpper()->GetUpper() ) : + nullptr; + } + } + + // If we found a 'previous' row, we look for the appropriate row frame: + if ( pPrevTabLine ) + { + SwIterator<SwRowFrame,SwFormat> aIter( *pPrevTabLine->GetFrameFormat() ); + for ( SwRowFrame* pRow = aIter.First(); pRow; pRow = aIter.Next() ) + { + // #115759# - do *not* take repeated + // headlines, because during split of table it can be + // invalid and thus can't provide correct border values. + if ( pRow->GetTabLine() == pPrevTabLine && + !pRow->IsRepeatedHeadline() ) + { + pPreviousRow = pRow; + break; + } + } + } + + sal_uInt16 nTopPrtMargin = nTopSpace; + if ( pPreviousRow ) + { + const sal_uInt16 nTmpPrtMargin = pPreviousRow->GetBottomLineSize() + nTopLineDist; + if ( nTmpPrtMargin > nTopPrtMargin ) + nTopPrtMargin = nTmpPrtMargin; + } + + // table has to be notified if it has to change its lower + // margin due to changes of nBottomLineSize: + if ( !GetNext() && nBottomLineSize != GetBottomLineSize() ) + pTabFrame->InvalidatePrt_(); + + // If there are rows nested inside this row, the nested rows + // may not have been calculated yet. Therefore the + // ::lcl_CalcMinRowHeight( this ) operation later in this + // function cannot consider the correct border values. We + // have to trigger the invalidation of the outer row frame + // manually: + // Note: If any further invalidations should be necessary, we + // should consider moving the invalidation stuff to the + // appropriate SwNotify object. + if ( GetUpper()->GetUpper()->IsRowFrame() && + ( nBottomLineDist != GetBottomMarginForLowers() || + nTopPrtMargin != GetTopMarginForLowers() ) ) + GetUpper()->GetUpper()->InvalidateSize_(); + + SetBottomMarginForLowers( nBottomLineDist ); // 3. + SetBottomLineSize( nBottomLineSize ); // 4. + SetTopMarginForLowers( nTopPrtMargin ); // 5. + + } + } + + while ( !isFrameAreaSizeValid() ) + { + setFrameAreaSizeValid(true); + +#if OSL_DEBUG_LEVEL > 0 + if ( HasFixSize() ) + { + const SwFormatFrameSize &rFrameSize = GetFormat()->GetFrameSize(); + OSL_ENSURE( rFrameSize.GetSize().Height() > 0, "Has it" ); + } +#endif + const SwTwips nDiff = aRectFnSet.GetHeight(getFrameArea()) - + ( HasFixSize() && !IsRowSpanLine() + ? pAttrs->GetSize().Height() + // #i26945# + : ::lcl_CalcMinRowHeight( this, + FindTabFrame()->IsConsiderObjsForMinCellHeight() ) ); + if ( nDiff ) + { + mbFixSize = false; + if ( nDiff > 0 ) + Shrink( nDiff, false, true ); + else if ( nDiff < 0 ) + Grow( -nDiff ); + mbFixSize = bFix; + } + } + + // last row will fill the space in its upper. + if ( GetNext() ) + return; + + //The last fills the remaining space in the upper. + SwTwips nDiff = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()); + SwFrame *pSibling = GetUpper()->Lower(); + do + { nDiff -= aRectFnSet.GetHeight(pSibling->getFrameArea()); + pSibling = pSibling->GetNext(); + } while ( pSibling ); + if ( nDiff > 0 ) + { + mbFixSize = false; + Grow( nDiff ); + mbFixSize = bFix; + setFrameAreaSizeValid(true); + } +} + +void SwRowFrame::AdjustCells( const SwTwips nHeight, const bool bHeight ) +{ + SwFrame *pFrame = Lower(); + if ( bHeight ) + { + SwRectFnSet aRectFnSet(this); +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + SwRect aOldFrame; +#endif + + while ( pFrame ) + { + SwFrame* pNotify = nullptr; + + SwCellFrame* pCellFrame = static_cast<SwCellFrame*>(pFrame); + + // NEW TABLES + // Which cells need to be adjusted if the current row changes + // its height? + + // Current frame is a covered frame: + // Set new height for covered cell and adjust master cell: + if ( pCellFrame->GetTabBox()->getRowSpan() < 1 ) + { + // Set height of current (covered) cell to new line height. + const tools::Long nDiff = nHeight - aRectFnSet.GetHeight(pCellFrame->getFrameArea()); + if ( nDiff ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pCellFrame); + aRectFnSet.AddBottom( aFrm, nDiff ); + } + + pCellFrame->InvalidatePrt_(); + } + } + + SwCellFrame* pToAdjust = nullptr; + SwFrame* pToAdjustRow = nullptr; + + // If current frame is covered frame, we still want to adjust the + // height of the cell starting the row span + if ( pCellFrame->GetLayoutRowSpan() < 1 ) + { + pToAdjust = const_cast< SwCellFrame*>(&pCellFrame->FindStartEndOfRowSpanCell( true )); + pToAdjustRow = pToAdjust->GetUpper(); + } + else + { + pToAdjust = pCellFrame; + pToAdjustRow = this; + } + + // Set height of master cell to height of all lines spanned by this line. + tools::Long nRowSpan = pToAdjust->GetLayoutRowSpan(); + SwTwips nSumRowHeight = 0; + while ( pToAdjustRow ) + { + // Use new height for the current row: + nSumRowHeight += pToAdjustRow == this ? + nHeight : + aRectFnSet.GetHeight(pToAdjustRow->getFrameArea()); + + if ( nRowSpan-- == 1 ) + break; + + pToAdjustRow = pToAdjustRow->GetNext(); + } + + if ( pToAdjustRow && pToAdjustRow != this ) + pToAdjustRow->InvalidateSize_(); + + const tools::Long nDiff = nSumRowHeight - aRectFnSet.GetHeight(pToAdjust->getFrameArea()); + if ( nDiff ) + { +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + aOldFrame = pToAdjust->getFrameArea(); +#endif + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pToAdjust); + aRectFnSet.AddBottom( aFrm, nDiff ); + pNotify = pToAdjust; + } + + if ( pNotify ) + { +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && pRootFrame->GetCurrShell() ) + pRootFrame->GetCurrShell()->Imp()->MoveAccessibleFrame( pNotify, aOldFrame ); +#endif + + pNotify->InvalidatePrt_(); + } + + pFrame = pFrame->GetNext(); + } + } + else + { while ( pFrame ) + { + pFrame->InvalidateAll_(); + pFrame = pFrame->GetNext(); + } + } + InvalidatePage(); +} + +void SwRowFrame::Cut() +{ + SwTabFrame *pTab = FindTabFrame(); + if ( pTab && pTab->IsFollow() && this == pTab->GetFirstNonHeadlineRow() ) + { + pTab->FindMaster()->InvalidatePos(); + } + + SwLayoutFrame::Cut(); +} + +SwTwips SwRowFrame::GrowFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + SwTwips nReal = 0; + + SwTabFrame* pTab = FindTabFrame(); + SwRectFnSet aRectFnSet(pTab); + + bool bRestrictTableGrowth; + bool bHasFollowFlowLine = pTab->HasFollowFlowLine(); + + if ( GetUpper()->IsTabFrame() ) + { + const SwRowFrame* pFollowFlowRow = IsInSplitTableRow(); + bRestrictTableGrowth = pFollowFlowRow && !pFollowFlowRow->IsRowSpanLine(); + } + else + { + OSL_ENSURE( GetUpper()->IsCellFrame(), "RowFrame->GetUpper neither table nor cell" ); + bRestrictTableGrowth = GetFollowRow() && bHasFollowFlowLine; + OSL_ENSURE( !bRestrictTableGrowth || !GetNext(), + "GetFollowRow for row frame that has a Next" ); + + // There may still be some space left in my direct upper: + const SwTwips nAdditionalSpace = + aRectFnSet.BottomDist( getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper()->GetUpper()) ); + if ( bRestrictTableGrowth && nAdditionalSpace > 0 ) + { + nReal = std::min( nAdditionalSpace, nDist ); + nDist -= nReal; + if ( !bTst ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nReal ); + } + } + } + + if ( bRestrictTableGrowth ) + pTab->SetRestrictTableGrowth( true ); + else + { + // Ok, this looks like a hack, indeed, it is a hack. + // If the current row frame is inside another cell frame, + // and the current row frame has no follow, it should not + // be allowed to grow. In fact, setting bRestrictTableGrowth + // to 'false' does not work, because the surrounding RowFrame + // would set this to 'true'. + pTab->SetFollowFlowLine( false ); + } + + nReal += SwLayoutFrame::GrowFrame( nDist, bTst, bInfo); + + pTab->SetRestrictTableGrowth( false ); + pTab->SetFollowFlowLine( bHasFollowFlowLine ); + + //Update the height of the cells to the newest value. + if ( !bTst ) + { + SwRectFnSet fnRectX(this); + AdjustCells( fnRectX.GetHeight(getFramePrintArea()) + nReal, true ); + if ( nReal ) + SetCompletePaint(); + } + + return nReal; +} + +SwTwips SwRowFrame::ShrinkFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + SwRectFnSet aRectFnSet(this); + if( HasFixSize() ) + { + AdjustCells( aRectFnSet.GetHeight(getFramePrintArea()), true ); + return 0; + } + + // bInfo may be set to true by SwRowFrame::Format; we need to handle this + // here accordingly + const bool bShrinkAnyway = bInfo; + + //Only shrink as much as the content of the biggest cell allows. + SwTwips nRealDist = nDist; + SwFormat* pMod = GetFormat(); + if (pMod) + { + const SwFormatFrameSize &rSz = pMod->GetFrameSize(); + SwTwips nMinHeight = 0; + if (rSz.GetHeightSizeType() == SwFrameSize::Minimum) + nMinHeight = std::max(rSz.GetHeight() - lcl_calcHeightOfRowBeforeThisFrame(*this), + tools::Long(0)); + + // Only necessary to calculate minimal row height if height + // of pRow is at least nMinHeight. Otherwise nMinHeight is the + // minimum height. + if( nMinHeight < aRectFnSet.GetHeight(getFrameArea()) ) + { + // #i26945# + OSL_ENSURE( FindTabFrame(), "<SwRowFrame::ShrinkFrame(..)> - no table frame -> crash." ); + const bool bConsiderObjs( FindTabFrame()->IsConsiderObjsForMinCellHeight() ); + nMinHeight = lcl_CalcMinRowHeight( this, bConsiderObjs ); + } + + if ( (aRectFnSet.GetHeight(getFrameArea()) - nRealDist) < nMinHeight ) + nRealDist = aRectFnSet.GetHeight(getFrameArea()) - nMinHeight; + } + if ( nRealDist < 0 ) + nRealDist = 0; + + SwTwips nReal = nRealDist; + if ( nReal ) + { + if ( !bTst ) + { + SwTwips nHeight = aRectFnSet.GetHeight(getFrameArea()); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nHeight - nReal ); + + if( IsVertical() && !IsVertLR() ) + { + aFrm.Pos().AdjustX(nReal ); + } + } + + SwLayoutFrame* pFrame = GetUpper(); + SwTwips nTmp = pFrame ? pFrame->Shrink(nReal, bTst) : 0; + if ( !bShrinkAnyway && !GetNext() && nTmp != nReal ) + { + //The last one gets the leftover in the upper and therefore takes + //care (otherwise: endless loop) + if ( !bTst ) + { + nReal -= nTmp; + SwTwips nHeight = aRectFnSet.GetHeight(getFrameArea()); + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nHeight + nReal ); + + if( IsVertical() && !IsVertLR() ) + { + aFrm.Pos().AdjustX( -nReal ); + } + } + nReal = nTmp; + } + } + + // Invalidate appropriately and update the height to the newest value. + if ( !bTst ) + { + if ( nReal ) + { + if ( GetNext() ) + GetNext()->InvalidatePos_(); + InvalidateAll_(); + SetCompletePaint(); + + SwTabFrame *pTab = FindTabFrame(); + if ( !pTab->IsRebuildLastLine() + && pTab->IsFollow() + && this == pTab->GetFirstNonHeadlineRow() + && !pTab->IsInRecalcLowerRow() ) + { + SwTabFrame* pMasterTab = pTab->FindMaster(); + pMasterTab->InvalidatePos(); + } + } + AdjustCells( aRectFnSet.GetHeight(getFramePrintArea()) - nReal, true ); + } + return nReal; +} + +bool SwRowFrame::IsRowSplitAllowed() const +{ + // Fixed size rows are never allowed to split: + if ( HasFixSize() ) + { + OSL_ENSURE( SwFrameSize::Fixed == GetFormat()->GetFrameSize().GetHeightSizeType(), "pRow claims to have fixed size" ); + return false; + } + + // Repeated headlines are never allowed to split: + const SwTabFrame* pTabFrame = FindTabFrame(); + if ( pTabFrame->GetTable()->GetRowsToRepeat() > 0 && + pTabFrame->IsInHeadline( *this ) ) + return false; + + if ( IsForceRowSplitAllowed() ) + return true; + + const SwTableLineFormat* pFrameFormat = static_cast<SwTableLineFormat*>(GetTabLine()->GetFrameFormat()); + const SwFormatRowSplit& rLP = pFrameFormat->GetRowSplit(); + return rLP.GetValue(); +} + +bool SwRowFrame::ShouldRowKeepWithNext( const bool bCheckParents ) const +{ + // No KeepWithNext if nested in another table + if ( GetUpper()->GetUpper()->IsCellFrame() ) + return false; + + const SwCellFrame* pCell = static_cast<const SwCellFrame*>(Lower()); + const SwFrame* pText = pCell->Lower(); + + return pText && pText->IsTextFrame() && + static_cast<const SwTextFrame*>(pText)->GetTextNodeForParaProps()->GetSwAttrSet().GetKeep(bCheckParents).GetValue(); +} + +SwCellFrame::SwCellFrame(const SwTableBox &rBox, SwFrame* pSib, bool bInsertContent) + : SwLayoutFrame( rBox.GetFrameFormat(), pSib ) + , m_pTabBox( &rBox ) +{ + mnFrameType = SwFrameType::Cell; + + if ( !bInsertContent ) + return; + + //If a StartIdx is available, ContentFrames are added in the cell, otherwise + //Rows have to be present and those are added. + if ( rBox.GetSttIdx() ) + { + SwNodeOffset nIndex = rBox.GetSttIdx(); + ::InsertCnt_( this, rBox.GetFrameFormat()->GetDoc(), ++nIndex ); + } + else + { + const SwTableLines &rLines = rBox.GetTabLines(); + SwFrame *pTmpPrev = nullptr; + for ( size_t i = 0; i < rLines.size(); ++i ) + { + SwRowFrame *pNew = new SwRowFrame( *rLines[i], this, bInsertContent ); + pNew->InsertBehind( this, pTmpPrev ); + pTmpPrev = pNew; + } + } +} + +void SwCellFrame::DestroyImpl() +{ + sw::BroadcastingModify* pMod = GetFormat(); + if( pMod ) + { + // At this stage the lower frames aren't destroyed already, + // therefore we have to do a recursive dispose. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && + pRootFrame->GetCurrShell() ) + { + pRootFrame->GetCurrShell()->Imp()->DisposeAccessibleFrame( this, true ); + } +#endif + + pMod->Remove( this ); + if( !pMod->HasWriterListeners() ) + delete pMod; + } + + SwLayoutFrame::DestroyImpl(); +} + +SwCellFrame::~SwCellFrame() +{ +} + +static bool lcl_ArrangeLowers( SwLayoutFrame *pLay, tools::Long lYStart, bool bInva ) +{ + bool bRet = false; + SwFrame *pFrame = pLay->Lower(); + SwRectFnSet aRectFnSet(pLay); + while ( pFrame ) + { + tools::Long nFrameTop = aRectFnSet.GetTop(pFrame->getFrameArea()); + if( nFrameTop != lYStart ) + { + bRet = true; + const tools::Long lDiff = aRectFnSet.YDiff( lYStart, nFrameTop ); + const tools::Long lDiffX = lYStart - nFrameTop; + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame); + aRectFnSet.SubTop( aFrm, -lDiff ); + aRectFnSet.AddBottom( aFrm, lDiff ); + } + + pFrame->SetCompletePaint(); + + if ( !pFrame->GetNext() ) + pFrame->SetRetouche(); + if( bInva ) + pFrame->Prepare( PrepareHint::FramePositionChanged ); + if ( pFrame->IsLayoutFrame() && static_cast<SwLayoutFrame*>(pFrame)->Lower() ) + lcl_ArrangeLowers( static_cast<SwLayoutFrame*>(pFrame), + aRectFnSet.GetTop(static_cast<SwLayoutFrame*>(pFrame)->Lower()->getFrameArea()) + + lDiffX, bInva ); + if ( pFrame->GetDrawObjs() ) + { + for ( size_t i = 0; i < pFrame->GetDrawObjs()->size(); ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pFrame->GetDrawObjs())[i]; + // #i26945# - check, if anchored object + // is lower of layout frame by checking, if the anchor + // frame, which contains the anchor position, is a lower + // of the layout frame. + if ( !pLay->IsAnLower( pAnchoredObj->GetAnchorFrameContainingAnchPos() ) ) + { + continue; + } + // #i52904# - distinguish between anchored + // objects, whose vertical position depends on its anchor + // frame and whose vertical position is independent + // from its anchor frame. + bool bVertPosDepOnAnchor( true ); + { + SwFormatVertOrient aVert( pAnchoredObj->GetFrameFormat().GetVertOrient() ); + switch ( aVert.GetRelationOrient() ) + { + case text::RelOrientation::PAGE_FRAME: + case text::RelOrientation::PAGE_PRINT_AREA: + bVertPosDepOnAnchor = false; + break; + default: break; + } + } + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + + // OD 2004-05-18 #i28701# - no direct move of objects, + // which are anchored to-paragraph/to-character, if + // the wrapping style influence has to be considered + // on the object positioning. + // #i52904# - no direct move of objects, + // whose vertical position doesn't depend on anchor frame. + const bool bDirectMove = + FAR_AWAY != pFly->getFrameArea().Top() && + bVertPosDepOnAnchor && + !pFly->ConsiderObjWrapInfluenceOnObjPos(); + if ( bDirectMove ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFly); + aRectFnSet.SubTop( aFrm, -lDiff ); + aRectFnSet.AddBottom( aFrm, lDiff ); + } + + pFly->GetVirtDrawObj()->SetBoundAndSnapRectsDirty(); + // --> OD 2004-08-17 - also notify view of <SdrObject> + // instance, which represents the Writer fly frame in + // the drawing layer + pFly->GetVirtDrawObj()->SetChanged(); + // #i58280# + pFly->InvalidateObjRectWithSpaces(); + } + + if ( pFly->IsFlyInContentFrame() ) + { + static_cast<SwFlyInContentFrame*>(pFly)->AddRefOfst( lDiff ); + // #115759# - reset current relative + // position to get re-positioned, if not directly moved. + if ( !bDirectMove ) + { + pAnchoredObj->SetCurrRelPos( Point( 0, 0 ) ); + } + } + else if( pFly->IsAutoPos() ) + { + pFly->AddLastCharY( lDiff ); + // OD 2004-05-18 #i28701# - follow-up of #i22341# + // <mnLastTopOfLine> has also been adjusted. + pFly->AddLastTopOfLineY( lDiff ); + } + // #i26945# - re-registration at + // page frame of anchor frame, if table frame isn't + // a follow table and table frame isn't in its + // rebuild of last line. + const SwTabFrame* pTabFrame = pLay->FindTabFrame(); + // - save: check, if table frame is found. + if ( pTabFrame && + !( pTabFrame->IsFollow() && + pTabFrame->FindMaster()->IsRebuildLastLine() ) && + pFly->IsFlyFreeFrame() ) + { + SwPageFrame* pPageFrame = pFly->GetPageFrame(); + SwPageFrame* pPageOfAnchor = pFrame->FindPageFrame(); + if ( pPageFrame != pPageOfAnchor ) + { + pFly->InvalidatePos(); + if ( pPageFrame ) + pPageFrame->MoveFly( pFly, pPageOfAnchor ); + else + pPageOfAnchor->AppendFlyToPage( pFly ); + } + } + // OD 2004-05-11 #i28701# - Because of the introduction + // of new positionings and alignments (e.g. aligned at + // page area, but anchored at-character), the position + // of the Writer fly frame has to be invalidated. + pFly->InvalidatePos(); + + // #i26945# - follow-up of #i3317# + // No arrangement of lowers, if Writer fly frame isn't + // moved + if ( bDirectMove && + ::lcl_ArrangeLowers( pFly, + aRectFnSet.GetPrtTop(*pFly), + bInva ) ) + { + pFly->SetCompletePaint(); + } + } + else if ( dynamic_cast< const SwAnchoredDrawObject *>( pAnchoredObj ) != nullptr ) + { + // #i26945# + const SwTabFrame* pTabFrame = pLay->FindTabFrame(); + if ( pTabFrame && + !( pTabFrame->IsFollow() && + pTabFrame->FindMaster()->IsRebuildLastLine() ) && + (pAnchoredObj->GetFrameFormat().GetAnchor().GetAnchorId() + != RndStdIds::FLY_AS_CHAR)) + { + SwPageFrame* pPageFrame = pAnchoredObj->GetPageFrame(); + SwPageFrame* pPageOfAnchor = pFrame->FindPageFrame(); + if ( pPageFrame != pPageOfAnchor ) + { + pAnchoredObj->InvalidateObjPos(); + if ( pPageFrame ) + { + pPageFrame->RemoveDrawObjFromPage( *pAnchoredObj ); + } + pPageOfAnchor->AppendDrawObjToPage( *pAnchoredObj ); + } + } + // #i28701# - adjust last character + // rectangle and last top of line. + pAnchoredObj->AddLastCharY( lDiff ); + pAnchoredObj->AddLastTopOfLineY( lDiff ); + // #i52904# - re-introduce direct move + // of drawing objects + const bool bDirectMove = + static_cast<const SwDrawFrameFormat&>(pAnchoredObj->GetFrameFormat()).IsPosAttrSet() && + bVertPosDepOnAnchor && + !pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos(); + if ( bDirectMove ) + { + SwObjPositioningInProgress aObjPosInProgress( *pAnchoredObj ); + if ( aRectFnSet.IsVert() ) + { + pAnchoredObj->DrawObj()->Move( Size( lDiff, 0 ) ); + } + else + { + pAnchoredObj->DrawObj()->Move( Size( 0, lDiff ) ); + } + // #i58280# + pAnchoredObj->InvalidateObjRectWithSpaces(); + } + pAnchoredObj->InvalidateObjPos(); + } + else + { + OSL_FAIL( "<lcl_ArrangeLowers(..)> - unknown type of anchored object!" ); + } + } + } + } + // Columns and cells are ordered horizontal, not vertical + if( !pFrame->IsColumnFrame() && !pFrame->IsCellFrame() ) + lYStart = aRectFnSet.YInc( lYStart, + aRectFnSet.GetHeight(pFrame->getFrameArea()) ); + + // Nowadays, the content inside a cell can flow into the follow table. + // Thus, the cell may only grow up to the end of the environment. + // So the content may have grown, but the cell could not grow. + // Therefore we have to trigger a formatting for the frames, which do + // not fit into the cell anymore: + SwTwips nDistanceToUpperPrtBottom = + aRectFnSet.BottomDist( pFrame->getFrameArea(), aRectFnSet.GetPrtBottom(*pLay) ); + // #i56146# - Revise fix of issue #i26945# + // do *not* consider content inside fly frames, if it's an undersized paragraph. + // #i26945# - consider content inside fly frames + if ( nDistanceToUpperPrtBottom < 0 && + ( ( pFrame->IsInFly() && + ( !pFrame->IsTextFrame() || + !static_cast<SwTextFrame*>(pFrame)->IsUndersized() ) ) || + pFrame->IsInSplitTableRow() ) ) + { + pFrame->InvalidatePos(); + } + + pFrame = pFrame->GetNext(); + } + return bRet; +} + +void SwCellFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs *pAttrs ) +{ + OSL_ENSURE( pAttrs, "CellFrame::Format, pAttrs is 0." ); + const SwTabFrame* pTab = FindTabFrame(); + SwRectFnSet aRectFnSet(pTab); + + if ( !isFramePrintAreaValid() ) + { + setFramePrintAreaValid(true); + + //Adjust position. + if ( Lower() ) + { + SwTwips nTopSpace, nBottomSpace, nLeftSpace, nRightSpace; + // #i29550# + if ( pTab->IsCollapsingBorders() && !Lower()->IsRowFrame() ) + { + const SvxBoxItem& rBoxItem = pAttrs->GetBox(); + nLeftSpace = rBoxItem.GetDistance( SvxBoxItemLine::LEFT ); + nRightSpace = rBoxItem.GetDistance( SvxBoxItemLine::RIGHT ); + nTopSpace = static_cast<SwRowFrame*>(GetUpper())->GetTopMarginForLowers(); + nBottomSpace = static_cast<SwRowFrame*>(GetUpper())->GetBottomMarginForLowers(); + } + else + { + // OD 23.01.2003 #106895# - add 1st param to <SwBorderAttrs::CalcRight(..)> + nLeftSpace = pAttrs->CalcLeft( this ); + nRightSpace = pAttrs->CalcRight( this ); + nTopSpace = pAttrs->CalcTop(); + nBottomSpace = pAttrs->CalcBottom(); + } + aRectFnSet.SetXMargins( *this, nLeftSpace, nRightSpace ); + aRectFnSet.SetYMargins( *this, nTopSpace, nBottomSpace ); + } + } + // #i26945# + tools::Long nRemaining = GetTabBox()->getRowSpan() >= 1 ? + ::lcl_CalcMinCellHeight( this, pTab->IsConsiderObjsForMinCellHeight(), pAttrs ) : + 0; + if ( !isFrameAreaSizeValid() ) + { + setFrameAreaSizeValid(true); + + //The VarSize of the CellFrames is always the width. + //The width is not variable though, it is defined by the format. + //This predefined value however does not necessary match the actual + //width. The width is calculated based on the attribute, the value in + //the attribute matches the desired value of the TabFrame. Changes which + //were done there are taken into account here proportionately. + //If the cell doesn't have a neighbour anymore, it does not take the + //attribute into account and takes the rest of the upper instead. + SwTwips nWidth; + if ( GetNext() ) + { + const SwTwips nWish = pTab->GetFormat()->GetFrameSize().GetWidth(); + nWidth = pAttrs->GetSize().Width(); + + OSL_ENSURE( nWish, "Table without width?" ); + OSL_ENSURE( nWidth <= nWish, "Width of cell larger than table." ); + OSL_ENSURE( nWidth > 0, "Box without width" ); + + const tools::Long nPrtWidth = aRectFnSet.GetWidth(pTab->getFramePrintArea()); + if ( nWish != nPrtWidth ) + { + // Avoid rounding problems, at least for the new table model + if ( pTab->GetTable()->IsNewModel() ) + { + // 1. sum of widths of cells up to this cell (in model) + const SwTableLine* pTabLine = GetTabBox()->GetUpper(); + const SwTableBoxes& rBoxes = pTabLine->GetTabBoxes(); + const SwTableBox* pTmpBox = nullptr; + + SwTwips nSumWidth = 0; + size_t i = 0; + do + { + pTmpBox = rBoxes[ i++ ]; + nSumWidth += pTmpBox->GetFrameFormat()->GetFrameSize().GetWidth(); + } + while ( pTmpBox != GetTabBox() ); + + // 2. calculate actual width of cells up to this one + double nTmpWidth = nSumWidth; + nTmpWidth *= nPrtWidth; + nTmpWidth /= nWish; + nWidth = static_cast<SwTwips>(nTmpWidth); + + // 3. calculate frame widths of cells up to this one: + const SwFrame* pTmpCell = static_cast<const SwLayoutFrame*>(GetUpper())->Lower(); + SwTwips nSumFrameWidths = 0; + while ( pTmpCell != this ) + { + nSumFrameWidths += aRectFnSet.GetWidth(pTmpCell->getFrameArea()); + pTmpCell = pTmpCell->GetNext(); + } + + nWidth = nWidth - nSumFrameWidths; + } + else + { + // #i12092# use double instead of long, + // otherwise this could lead to overflows + double nTmpWidth = nWidth; + nTmpWidth *= nPrtWidth; + nTmpWidth /= nWish; + nWidth = static_cast<SwTwips>(nTmpWidth); + } + } + } + else + { + OSL_ENSURE( pAttrs->GetSize().Width() > 0, "Box without width" ); + nWidth = aRectFnSet.GetWidth(GetUpper()->getFramePrintArea()); + SwFrame *pPre = GetUpper()->Lower(); + while ( pPre != this ) + { + nWidth -= aRectFnSet.GetWidth(pPre->getFrameArea()); + pPre = pPre->GetNext(); + } + } + + const tools::Long nDiff = nWidth - aRectFnSet.GetWidth(getFrameArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + if( IsNeighbourFrame() && IsRightToLeft() ) + { + aRectFnSet.SubLeft( aFrm, nDiff ); + } + else + { + aRectFnSet.AddRight( aFrm, nDiff ); + } + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.AddRight( aPrt, nDiff ); + } + + //Adjust the height, it's defined through the content and the margins. + const tools::Long nDiffHeight = nRemaining - aRectFnSet.GetHeight(getFrameArea()); + if ( nDiffHeight ) + { + if ( nDiffHeight > 0 ) + { + //Validate again if no growth happened. Invalidation is done + //through AdjustCells of the row. + if ( !Grow( nDiffHeight ) ) + { + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + } + } + else + { + // Only keep invalidated if shrinking was actually done; the + // attempt can be ignored because all horizontally adjoined + // cells have to be the same height. + if ( !Shrink( -nDiffHeight ) ) + { + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + } + } + } + } + const SwFormatVertOrient &rOri = pAttrs->GetAttrSet().GetVertOrient(); + + if ( !Lower() ) + return; + + // From now on, all operations are related to the table cell. + aRectFnSet.Refresh(this); + + SwPageFrame* pPg = nullptr; + if ( !FindTabFrame()->IsRebuildLastLine() && text::VertOrientation::NONE != rOri.GetVertOrient() && + // #158225# no vertical alignment of covered cells + !IsCoveredCell() && + (pPg = FindPageFrame())!=nullptr ) + { + if ( !Lower()->IsContentFrame() && !Lower()->IsSctFrame() && !Lower()->IsTabFrame() ) + { + // OSL_ENSURE(for HTML-import! + OSL_ENSURE( false, "VAlign to cell without content" ); + return; + } + bool bVertDir = true; + // #i43913# - no vertical alignment, if wrapping + // style influence is considered on object positioning and + // an object is anchored inside the cell. + const bool bConsiderWrapOnObjPos( GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ); + // No alignment if fly with wrap overlaps the cell. + if ( pPg->GetSortedObjs() ) + { + SwRect aRect( getFramePrintArea() ); aRect += getFrameArea().Pos(); + for (SwAnchoredObject* pAnchoredObj : *pPg->GetSortedObjs()) + { + SwRect aTmp( pAnchoredObj->GetObjRect() ); + const SwFrame* pAnch = pAnchoredObj->GetAnchorFrame(); + if ( (bConsiderWrapOnObjPos && IsAnLower( pAnch )) || (!bConsiderWrapOnObjPos && aTmp.Overlaps( aRect )) ) + { + const SwFrameFormat& rAnchoredObjFrameFormat = pAnchoredObj->GetFrameFormat(); + const SwFormatSurround &rSur = rAnchoredObjFrameFormat.GetSurround(); + + if ( bConsiderWrapOnObjPos || css::text::WrapTextMode_THROUGH != rSur.GetSurround() ) + { + // frames, which the cell is a lower of, aren't relevant + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + if ( pFly->IsAnLower( this ) ) + continue; + } + + // #i43913# + // #i52904# - no vertical alignment, + // if object, anchored inside cell, has temporarily + // consider its wrapping style on object positioning. + // #i58806# - no vertical alignment + // if object does not follow the text flow. + if ( bConsiderWrapOnObjPos || + !IsAnLower( pAnch ) || + pAnchoredObj->IsTmpConsiderWrapInfluence() || + !rAnchoredObjFrameFormat.GetFollowTextFlow().GetValue() ) + { + bVertDir = false; + break; + } + } + } + } + } + + tools::Long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + if( ( bVertDir && ( nRemaining -= lcl_CalcTopAndBottomMargin( *this, *pAttrs ) ) < nPrtHeight ) || + aRectFnSet.GetTop(Lower()->getFrameArea()) != aRectFnSet.GetPrtTop(*this) ) + { + tools::Long nDiff = aRectFnSet.GetHeight(getFramePrintArea()) - nRemaining; + if ( nDiff >= 0 ) + { + tools::Long lTopOfst = 0; + if ( bVertDir ) + { + switch ( rOri.GetVertOrient() ) + { + case text::VertOrientation::CENTER: lTopOfst = nDiff / 2; break; + case text::VertOrientation::BOTTOM: lTopOfst = nDiff; break; + default: break; + } + } + tools::Long nTmp = aRectFnSet.YInc( + aRectFnSet.GetPrtTop(*this), lTopOfst ); + if ( lcl_ArrangeLowers( this, nTmp, !bVertDir ) ) + SetCompletePaint(); + } + } + } + else + { + //Was an old alignment taken into account? + if ( Lower()->IsContentFrame() ) + { + const tools::Long lYStart = aRectFnSet.GetPrtTop(*this); + lcl_ArrangeLowers( this, lYStart, true ); + } + } + + // Handle rotated portions of lowers: it's possible that we have changed amount of vertical + // space since the last format, and this affects how many rotated portions we need. So throw + // away the current portions to build them using the new line width. + for (SwFrame* pFrame = Lower(); pFrame; pFrame = pFrame->GetNext()) + { + if (!pFrame->IsTextFrame()) + { + continue; + } + + auto pTextFrame = static_cast<SwTextFrame*>(pFrame); + if (!pTextFrame->GetHasRotatedPortions()) + { + continue; + } + + pTextFrame->Prepare(); + } +} + +void SwCellFrame::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) +{ + if(auto pNewFormatHint = dynamic_cast<const sw::TableBoxFormatChanged*>(&rHint)) + { + if(GetTabBox() != &pNewFormatHint->m_rTableBox) + return; + RegisterToFormat(const_cast<SwTableBoxFormat&>(pNewFormatHint->m_rNewFormat)); + InvalidateSize(); + InvalidatePrt_(); + SetCompletePaint(); + SetDerivedVert(false); + CheckDirChange(); + + // #i47489# + // make sure that the row will be formatted, in order + // to have the correct Get(Top|Bottom)MarginForLowers values + // set at the row. + const SwTabFrame* pTab = FindTabFrame(); + if(pTab && pTab->IsCollapsingBorders()) + { + SwFrame* pRow = GetUpper(); + pRow->InvalidateSize_(); + pRow->InvalidatePrt_(); + } + } + else if(auto pMoveTableBoxHint = dynamic_cast<const sw::MoveTableBoxHint*>(&rHint)) + { + if(GetTabBox() != &pMoveTableBoxHint->m_rTableBox) + return; + const_cast<SwFrameFormat*>(&pMoveTableBoxHint->m_rNewFormat)->Add(this); + InvalidateAll(); + ReinitializeFrameSizeAttrFlags(); + SetDerivedVert(false); + CheckDirChange(); + return; + } + else if (rHint.GetId() == SfxHintId::SwLegacyModify) + { + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + const SfxPoolItem* pVertOrientItem = nullptr; + const SfxPoolItem* pProtectItem = nullptr; + const SfxPoolItem* pFrameDirItem = nullptr; + const SfxPoolItem* pBoxItem = nullptr; + const auto nWhich = pLegacy->m_pNew ? pLegacy->m_pNew->Which() : 0; + switch(nWhich) + { + case RES_ATTRSET_CHG: + { + auto& rChgSet = *static_cast<const SwAttrSetChg*>(pLegacy->m_pNew)->GetChgSet(); + pVertOrientItem = rChgSet.GetItemIfSet(RES_VERT_ORIENT, false); + pProtectItem = rChgSet.GetItemIfSet(RES_PROTECT, false); + pFrameDirItem = rChgSet.GetItemIfSet(RES_FRAMEDIR, false); + pBoxItem = rChgSet.GetItemIfSet(RES_BOX, false); + break; + } + case RES_VERT_ORIENT: + pVertOrientItem = pLegacy->m_pNew; + break; + case RES_PROTECT: + pProtectItem = pLegacy->m_pNew; + break; + case RES_FRAMEDIR: + pFrameDirItem = pLegacy->m_pNew; + break; + case RES_BOX: + pBoxItem = pLegacy->m_pNew; + break; + } + if(pVertOrientItem) + { + bool bInva = true; + const auto eVertOrient = static_cast<const SwFormatVertOrient*>(pVertOrientItem)->GetVertOrient(); + if(text::VertOrientation::NONE == eVertOrient && Lower() && Lower()->IsContentFrame()) + { + SwRectFnSet aRectFnSet(this); + const tools::Long lYStart = aRectFnSet.GetPrtTop(*this); + bInva = lcl_ArrangeLowers(this, lYStart, false); + } + if (bInva) + { + SetCompletePaint(); + InvalidatePrt(); + } + } +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if(pProtectItem) + { + SwViewShell* pSh = getRootFrame()->GetCurrShell(); + if(pSh && pSh->GetLayout()->IsAnyShellAccessible()) + pSh->Imp()->InvalidateAccessibleEditableState(true, this); + } +#endif + if(pFrameDirItem) + { + SetDerivedVert(false); + CheckDirChange(); + } + // #i29550# + if(pBoxItem) + { + SwFrame* pTmpUpper = GetUpper(); + while(pTmpUpper->GetUpper() && !pTmpUpper->GetUpper()->IsTabFrame()) + pTmpUpper = pTmpUpper->GetUpper(); + + SwTabFrame* pTabFrame = static_cast<SwTabFrame*>(pTmpUpper->GetUpper()); + if(pTabFrame->IsCollapsingBorders()) + { + // Invalidate lowers of this and next row: + lcl_InvalidateAllLowersPrt(static_cast<SwRowFrame*>(pTmpUpper)); + pTmpUpper = pTmpUpper->GetNext(); + if(pTmpUpper) + lcl_InvalidateAllLowersPrt(static_cast<SwRowFrame*>(pTmpUpper)); + else + pTabFrame->InvalidatePrt(); + } + } + SwLayoutFrame::SwClientNotify(rMod, rHint); + } +} + +tools::Long SwCellFrame::GetLayoutRowSpan() const +{ + const SwTableBox *pTabBox = GetTabBox(); + tools::Long nRet = pTabBox ? pTabBox->getRowSpan() : 0; + if ( nRet < 1 ) + { + const SwFrame* pRow = GetUpper(); + const SwTabFrame* pTab = pRow ? static_cast<const SwTabFrame*>(pRow->GetUpper()) : nullptr; + + if ( pTab && pTab->IsFollow() && pRow == pTab->GetFirstNonHeadlineRow() ) + nRet = -nRet; + } + return nRet; +} + +const SwCellFrame* SwCellFrame::GetCoveredCellInRow(const SwRowFrame& rRow) const +{ + if (GetLayoutRowSpan() <= 1) + { + // Not merged vertically. + return nullptr; + } + + for (const SwFrame* pCell = rRow.GetLower(); pCell; pCell = pCell->GetNext()) + { + if (!pCell->IsCellFrame()) + { + continue; + } + + auto pCellFrame = static_cast<const SwCellFrame*>(pCell); + if (!pCellFrame->IsCoveredCell()) + { + continue; + } + + if (pCellFrame->getFrameArea().Left() != getFrameArea().Left()) + { + continue; + } + + if (pCellFrame->getFrameArea().Width() != getFrameArea().Width()) + { + continue; + } + + // pCellFrame is covered, there are only covered cell frames between "this" and pCellFrame + // and the horizontal position/size matches "this". + return pCellFrame; + } + + return nullptr; +} + +std::vector<const SwCellFrame*> SwCellFrame::GetCoveredCells() const +{ + std::vector<const SwCellFrame*> aRet; + if (GetLayoutRowSpan() <= 1) + { + return aRet; + } + + if (!GetUpper()->IsRowFrame()) + { + return aRet; + } + + auto pFirstRowFrame = static_cast<const SwRowFrame*>(GetUpper()); + if (!pFirstRowFrame->GetNext()) + { + return aRet; + } + + if (!pFirstRowFrame->GetNext()->IsRowFrame()) + { + return aRet; + } + + for (const SwFrame* pRow = pFirstRowFrame->GetNext(); pRow; pRow = pRow->GetNext()) + { + if (!pRow->IsRowFrame()) + { + continue; + } + + auto pRowFrame = static_cast<const SwRowFrame*>(pRow); + const SwCellFrame* pCovered = GetCoveredCellInRow(*pRowFrame); + if (!pCovered) + { + continue; + } + + // Found a cell in a next row that is covered by "this". + aRet.push_back(pCovered); + } + + return aRet; +} + +void SwCellFrame::dumpAsXmlAttributes(xmlTextWriterPtr pWriter) const +{ + SwFrame::dumpAsXmlAttributes(pWriter); + if (SwCellFrame* pFollow = GetFollowCell()) + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("follow"), "%" SAL_PRIuUINT32, pFollow->GetFrameId()); + + if (SwCellFrame* pPrevious = GetPreviousCell()) + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("precede"), "%" SAL_PRIuUINT32, pPrevious->GetFrameId()); +} + +// #i103961# +void SwCellFrame::Cut() +{ + // notification for accessibility +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + { + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() ) + { + SwViewShell* pVSh = pRootFrame->GetCurrShell(); + if ( pVSh && pVSh->Imp() ) + { + pVSh->Imp()->DisposeAccessibleFrame( this ); + } + } + } +#endif + + SwLayoutFrame::Cut(); +} + +// Helper functions for repeated headlines: + +bool SwTabFrame::IsInHeadline( const SwFrame& rFrame ) const +{ + OSL_ENSURE( IsAnLower( &rFrame ) && rFrame.IsInTab(), + "SwTabFrame::IsInHeadline called for frame not lower of table" ); + + const SwFrame* pTmp = &rFrame; + while ( !pTmp->GetUpper()->IsTabFrame() ) + pTmp = pTmp->GetUpper(); + + return GetTable()->IsHeadline( *static_cast<const SwRowFrame*>(pTmp)->GetTabLine() ); +} + +/* + * If this is a master table, we can may assume, that there are at least + * nRepeat lines in the table. + * If this is a follow table, there are intermediate states for the table + * layout, e.g., during deletion of rows, which makes it necessary to find + * the first non-headline row by evaluating the headline flag at the row frame. + */ +SwRowFrame* SwTabFrame::GetFirstNonHeadlineRow() const +{ + SwRowFrame* pRet = const_cast<SwRowFrame*>(static_cast<const SwRowFrame*>(Lower())); + if ( pRet ) + { + if ( IsFollow() ) + { + while ( pRet && pRet->IsRepeatedHeadline() ) + pRet = static_cast<SwRowFrame*>(pRet->GetNext()); + } + else + { + sal_uInt16 nRepeat = GetTable()->GetRowsToRepeat(); + while ( pRet && nRepeat > 0 ) + { + pRet = static_cast<SwRowFrame*>(pRet->GetNext()); + --nRepeat; + } + } + } + + return pRet; +} + +bool SwTable::IsHeadline( const SwTableLine& rLine ) const +{ + for ( sal_uInt16 i = 0; i < GetRowsToRepeat(); ++i ) + if ( GetTabLines()[ i ] == &rLine ) + return true; + + return false; +} + +bool SwTabFrame::IsLayoutSplitAllowed() const +{ + return GetFormat()->GetLayoutSplit().GetValue(); +} + +// #i29550# + +sal_uInt16 SwTabFrame::GetBottomLineSize() const +{ + OSL_ENSURE( IsCollapsingBorders(), + "BottomLineSize only required for collapsing borders" ); + + OSL_ENSURE( Lower(), "Warning! Trying to prevent a crash" ); + + const SwFrame* pTmp = GetLastLower(); + + // #124755# Try to make code robust + if ( !pTmp ) return 0; + + return static_cast<const SwRowFrame*>(pTmp)->GetBottomLineSize(); +} + +bool SwTabFrame::IsCollapsingBorders() const +{ + return GetFormat()->GetAttrSet().Get( RES_COLLAPSING_BORDERS ).GetValue(); +} + +/// Local helper function to calculate height of first text row +static SwTwips lcl_CalcHeightOfFirstContentLine( const SwRowFrame& rSourceLine ) +{ + // Find corresponding split line in master table + const SwTabFrame* pTab = rSourceLine.FindTabFrame(); + SwRectFnSet aRectFnSet(pTab); + const SwCellFrame* pCurrSourceCell = static_cast<const SwCellFrame*>(rSourceLine.Lower()); + + // 1. Case: rSourceLine is a follow flow line. + // In this case we have to return the minimum of the heights + // of the first lines in rSourceLine. + + // 2. Case: rSourceLine is not a follow flow line. + // In this case we have to return the maximum of the heights + // of the first lines in rSourceLine. + bool bIsInFollowFlowLine = rSourceLine.IsInFollowFlowRow(); + SwTwips nHeight = bIsInFollowFlowLine ? LONG_MAX : 0; + + while ( pCurrSourceCell ) + { + // NEW TABLES + // Skip cells which are not responsible for the height of + // the follow flow line: + if ( bIsInFollowFlowLine && pCurrSourceCell->GetLayoutRowSpan() > 1 ) + { + pCurrSourceCell = static_cast<const SwCellFrame*>(pCurrSourceCell->GetNext()); + continue; + } + + const SwFrame *pTmp = pCurrSourceCell->Lower(); + if ( pTmp ) + { + SwTwips nTmpHeight = USHRT_MAX; + // #i32456# Consider lower row frames + if ( pTmp->IsRowFrame() ) + { + const SwRowFrame* pTmpSourceRow = static_cast<const SwRowFrame*>(pCurrSourceCell->Lower()); + nTmpHeight = lcl_CalcHeightOfFirstContentLine( *pTmpSourceRow ); + } + else if (pTmp->IsTabFrame() || (pTmp->IsSctFrame() && pTmp->GetLower() && pTmp->GetLower()->IsTabFrame())) + { + SwTabFrame const*const pTabFrame(pTmp->IsTabFrame() + ? static_cast<SwTabFrame const*>(pTmp) + : static_cast<SwTabFrame const*>(pTmp->GetLower())); + nTmpHeight = pTabFrame->CalcHeightOfFirstContentLine(); + } + else if (pTmp->IsTextFrame() || (pTmp->IsSctFrame() && pTmp->GetLower() && pTmp->GetLower()->IsTextFrame())) + { + // Section frames don't influence the size/position of text + // frames, so 'text frame' and 'text frame in section frame' is + // the same case. + SwTextFrame* pTextFrame = nullptr; + if (pTmp->IsTextFrame()) + pTextFrame = const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pTmp)); + else + pTextFrame = const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pTmp->GetLower())); + pTextFrame->GetFormatted(); + nTmpHeight = pTextFrame->FirstLineHeight(); + } + + if ( USHRT_MAX != nTmpHeight ) + { + const SwCellFrame* pPrevCell = pCurrSourceCell->GetPreviousCell(); + if ( pPrevCell ) + { + // If we are in a split row, there may be some space + // left in the cell frame of the master row. + // We look for the minimum of all first line heights; + SwTwips nReal = aRectFnSet.GetHeight(pPrevCell->getFramePrintArea()); + const SwFrame* pFrame = pPrevCell->Lower(); + const SwFrame* pLast = pFrame; + while ( pFrame ) + { + nReal -= aRectFnSet.GetHeight(pFrame->getFrameArea()); + pLast = pFrame; + pFrame = pFrame->GetNext(); + } + + // #i26831#, #i26520# + // The additional lower space of the current last. + // #115759# - do *not* consider the + // additional lower space for 'master' text frames + if ( pLast && pLast->IsFlowFrame() && + ( !pLast->IsTextFrame() || + !static_cast<const SwTextFrame*>(pLast)->GetFollow() ) ) + { + nReal += SwFlowFrame::CastFlowFrame(pLast)->CalcAddLowerSpaceAsLastInTableCell(); + } + // Don't forget the upper space and lower space, + // #115759# - do *not* consider the upper + // and the lower space for follow text frames. + if ( pTmp->IsFlowFrame() && + ( !pTmp->IsTextFrame() || + !static_cast<const SwTextFrame*>(pTmp)->IsFollow() ) ) + { + nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp)->CalcUpperSpace( nullptr, pLast); + nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp)->CalcLowerSpace(); + } + // #115759# - consider additional lower + // space of <pTmp>, if contains only one line. + // In this case it would be the new last text frame, which + // would have no follow and thus would add this space. + if ( pTmp->IsTextFrame() && + const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pTmp)) + ->GetLineCount(TextFrameIndex(COMPLETE_STRING)) == 1) + { + nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp) + ->CalcAddLowerSpaceAsLastInTableCell(); + } + if ( nReal > 0 ) + nTmpHeight -= nReal; + } + else + { + // pFirstRow is not a FollowFlowRow. In this case, + // we look for the maximum of all first line heights: + SwBorderAttrAccess aAccess( SwFrame::GetCache(), pCurrSourceCell ); + const SwBorderAttrs &rAttrs = *aAccess.Get(); + nTmpHeight += rAttrs.CalcTop() + rAttrs.CalcBottom(); + // #i26250# + // Don't forget the upper space and lower space, + if ( pTmp->IsFlowFrame() ) + { + nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp)->CalcUpperSpace(); + nTmpHeight += SwFlowFrame::CastFlowFrame(pTmp)->CalcLowerSpace(); + } + } + } + + if ( bIsInFollowFlowLine ) + { + // minimum + if ( nTmpHeight < nHeight ) + nHeight = nTmpHeight; + } + else + { + // maximum + if ( nTmpHeight > nHeight && USHRT_MAX != nTmpHeight ) + nHeight = nTmpHeight; + } + } + + pCurrSourceCell = static_cast<const SwCellFrame*>(pCurrSourceCell->GetNext()); + } + + return ( LONG_MAX == nHeight ) ? 0 : nHeight; +} + +/// Function to calculate height of first text row +SwTwips SwTabFrame::CalcHeightOfFirstContentLine() const +{ + SwRectFnSet aRectFnSet(this); + + const bool bDontSplit = !IsFollow() && !GetFormat()->GetLayoutSplit().GetValue(); + + if ( bDontSplit ) + { + // Table is not allowed to split: Take the whole height, that's all + return aRectFnSet.GetHeight(getFrameArea()); + } + + SwTwips nTmpHeight = 0; + + const SwRowFrame* pFirstRow = GetFirstNonHeadlineRow(); + OSL_ENSURE( !IsFollow() || pFirstRow, "FollowTable without Lower" ); + + // NEW TABLES + if ( pFirstRow && pFirstRow->IsRowSpanLine() && pFirstRow->GetNext() ) + pFirstRow = static_cast<const SwRowFrame*>(pFirstRow->GetNext()); + + // Calculate the height of the headlines: + const sal_uInt16 nRepeat = GetTable()->GetRowsToRepeat(); + SwTwips nRepeatHeight = nRepeat ? lcl_GetHeightOfRows( GetLower(), nRepeat ) : 0; + + // Calculate the height of the keeping lines + // (headlines + following keeping lines): + SwTwips nKeepHeight = nRepeatHeight; + if ( GetFormat()->GetDoc()->GetDocumentSettingManager().get(DocumentSettingId::TABLE_ROW_KEEP) ) + { + sal_uInt16 nKeepRows = nRepeat; + + // Check how many rows want to keep together + while ( pFirstRow && pFirstRow->ShouldRowKeepWithNext() ) + { + ++nKeepRows; + pFirstRow = static_cast<const SwRowFrame*>(pFirstRow->GetNext()); + } + + if ( nKeepRows > nRepeat ) + nKeepHeight = lcl_GetHeightOfRows( GetLower(), nKeepRows ); + } + + // For master tables, the height of the headlines + the height of the + // keeping lines (if any) has to be considered. For follow tables, we + // only consider the height of the keeping rows without the repeated lines: + if ( !IsFollow() ) + { + nTmpHeight = nKeepHeight; + } + else + { + nTmpHeight = nKeepHeight - nRepeatHeight; + } + + // pFirstRow row is the first non-heading row. + // nTmpHeight is the height of the heading row if we are a follow. + if ( pFirstRow ) + { + const bool bSplittable = pFirstRow->IsRowSplitAllowed(); + const SwTwips nFirstLineHeight = aRectFnSet.GetHeight(pFirstRow->getFrameArea()); + + if ( !bSplittable ) + { + // pFirstRow is not splittable, but it is still possible that the line height of pFirstRow + // actually is determined by a lower cell with rowspan = -1. In this case we should not + // just return the height of the first line. Basically we need to get the height of the + // line as it would be on the last page. Since this is quite complicated to calculate, + // we only calculate the height of the first line. + SwFormatFrameSize const& rFrameSize(pFirstRow->GetAttrSet()->GetFrameSize()); + if ( pFirstRow->GetPrev() && + static_cast<const SwRowFrame*>(pFirstRow->GetPrev())->IsRowSpanLine() + && rFrameSize.GetHeightSizeType() != SwFrameSize::Fixed) + { + // Calculate maximum height of all cells with rowspan = 1: + SwTwips nMaxHeight = rFrameSize.GetHeightSizeType() == SwFrameSize::Minimum + ? rFrameSize.GetHeight() + : 0; + const SwCellFrame* pLower2 = static_cast<const SwCellFrame*>(pFirstRow->Lower()); + while ( pLower2 ) + { + if ( 1 == pLower2->GetTabBox()->getRowSpan() ) + { + const SwTwips nCellHeight = lcl_CalcMinCellHeight( pLower2, true ); + nMaxHeight = std::max( nCellHeight, nMaxHeight ); + } + pLower2 = static_cast<const SwCellFrame*>(pLower2->GetNext()); + } + nTmpHeight += nMaxHeight; + } + else + { + nTmpHeight += nFirstLineHeight; + } + } + + // Optimization: lcl_CalcHeightOfFirstContentLine actually can trigger + // a formatting of the row frame (via the GetFormatted()). We don't + // want this formatting if the row does not have a height. + else if ( 0 != nFirstLineHeight ) + { + const bool bOldJoinLock = IsJoinLocked(); + const_cast<SwTabFrame*>(this)->LockJoin(); + const SwTwips nHeightOfFirstContentLine = lcl_CalcHeightOfFirstContentLine( *pFirstRow ); + + // Consider minimum row height: + const SwFormatFrameSize &rSz = pFirstRow->GetFormat()->GetFrameSize(); + + SwTwips nMinRowHeight = 0; + if (rSz.GetHeightSizeType() == SwFrameSize::Minimum) + { + nMinRowHeight = std::max(rSz.GetHeight() - lcl_calcHeightOfRowBeforeThisFrame(*pFirstRow), + tools::Long(0)); + } + + nTmpHeight += std::max( nHeightOfFirstContentLine, nMinRowHeight ); + + if ( !bOldJoinLock ) + const_cast<SwTabFrame*>(this)->UnlockJoin(); + } + } + + return nTmpHeight; +} + +// Some more functions for covered/covering cells. This way inclusion of +// SwCellFrame can be avoided + +bool SwFrame::IsLeaveUpperAllowed() const +{ + return false; +} + +bool SwCellFrame::IsLeaveUpperAllowed() const +{ + return GetLayoutRowSpan() > 1; +} + +bool SwFrame::IsCoveredCell() const +{ + return false; +} + +bool SwCellFrame::IsCoveredCell() const +{ + return GetLayoutRowSpan() < 1; +} + +bool SwFrame::IsInCoveredCell() const +{ + bool bRet = false; + + const SwFrame* pThis = this; + while ( pThis && !pThis->IsCellFrame() ) + pThis = pThis->GetUpper(); + + if ( pThis ) + bRet = pThis->IsCoveredCell(); + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/trvlfrm.cxx b/sw/source/core/layout/trvlfrm.cxx new file mode 100644 index 000000000..375b9fe34 --- /dev/null +++ b/sw/source/core/layout/trvlfrm.cxx @@ -0,0 +1,2654 @@ +/* -*- 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 <hints.hxx> +#include <comphelper/flagguard.hxx> +#include <tools/line.hxx> +#include <editeng/opaqitem.hxx> +#include <editeng/protitem.hxx> +#include <vcl/settings.hxx> +#include <fmtpdsc.hxx> +#include <fmtsrnd.hxx> +#include <pagedesc.hxx> +#include <pagefrm.hxx> +#include <rootfrm.hxx> +#include <ftnfrm.hxx> +#include <flyfrm.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <cellfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <viewopt.hxx> +#include <DocumentSettingManager.hxx> +#include <viscrs.hxx> +#include <dflyobj.hxx> +#include <crstate.hxx> +#include <dcontact.hxx> +#include <sortedobjs.hxx> +#include <txatbase.hxx> +#include <fmtfld.hxx> +#include <fldbas.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <ndtxt.hxx> +#include <undobj.hxx> + +#include <swselectionlist.hxx> +#include <comphelper/lok.hxx> +#include <osl/diagnose.h> + +namespace { + bool lcl_GetModelPositionForViewPoint_Objects( const SwPageFrame* pPageFrame, bool bSearchBackground, + SwPosition *pPos, Point const & rPoint, SwCursorMoveState* pCMS ) + { + bool bRet = false; + Point aPoint( rPoint ); + SwOrderIter aIter( pPageFrame ); + aIter.Top(); + while ( aIter() ) + { + const SwVirtFlyDrawObj* pObj = + static_cast<const SwVirtFlyDrawObj*>(aIter()); + const SwAnchoredObject* pAnchoredObj = GetUserCall( aIter() )->GetAnchoredObj( aIter() ); + const SwFormatSurround& rSurround = pAnchoredObj->GetFrameFormat().GetSurround(); + const SvxOpaqueItem& rOpaque = pAnchoredObj->GetFrameFormat().GetOpaque(); + bool bInBackground = ( rSurround.GetSurround() == css::text::WrapTextMode_THROUGH ) && !rOpaque.GetValue(); + + bool bBackgroundMatches = bInBackground == bSearchBackground; + + const SwFlyFrame* pFly = pObj ? pObj->GetFlyFrame() : nullptr; + if ( pFly && bBackgroundMatches && + ( ( pCMS && pCMS->m_bSetInReadOnly ) || + !pFly->IsProtected() ) && + pFly->GetModelPositionForViewPoint( pPos, aPoint, pCMS ) ) + { + bRet = true; + break; + } + + if ( pCMS && pCMS->m_bStop ) + return false; + aIter.Prev(); + } + return bRet; + } + + double lcl_getDistance( const SwRect& rRect, const Point& rPoint ) + { + double nDist = 0.0; + + // If the point is inside the rectangle, then distance is 0 + // Otherwise, compute the distance to the center of the rectangle. + if ( !rRect.Contains( rPoint ) ) + { + tools::Line aLine( rPoint, rRect.Center( ) ); + nDist = aLine.GetLength( ); + } + + return nDist; + } +} + +namespace { + +//For SwFlyFrame::GetModelPositionForViewPoint +class SwCursorOszControl +{ +public: + // So the compiler can initialize the class already. No DTOR and member + // as public members + const SwFlyFrame* m_pEntry; + const SwFlyFrame* m_pStack1; + const SwFlyFrame* m_pStack2; + + bool ChkOsz( const SwFlyFrame *pFly ) + { + bool bRet = true; + if (pFly != m_pStack1 && pFly != m_pStack2) + { + m_pStack1 = m_pStack2; + m_pStack2 = pFly; + bRet = false; + } + return bRet; + } + + void Entry( const SwFlyFrame *pFly ) + { + if (!m_pEntry) + m_pEntry = m_pStack1 = pFly; + } + + void Exit( const SwFlyFrame *pFly ) + { + if (pFly == m_pEntry) + m_pEntry = m_pStack1 = m_pStack2 = nullptr; + } +}; + +} + +static SwCursorOszControl g_OszCtrl = { nullptr, nullptr, nullptr }; + +/** Searches the ContentFrame owning the PrtArea containing the point. */ +bool SwLayoutFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool ) const +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + bool bRet = false; + const SwFrame *pFrame = Lower(); + while ( !bRet && pFrame ) + { + pFrame->Calc(pRenderContext); + + // #i43742# New function + const bool bContentCheck = pFrame->IsTextFrame() && pCMS && pCMS->m_bContentCheck; + const SwRect aPaintRect( bContentCheck ? + pFrame->UnionFrame() : + pFrame->GetPaintArea() ); + + if ( aPaintRect.Contains( rPoint ) && + ( bContentCheck || pFrame->GetModelPositionForViewPoint( pPos, rPoint, pCMS ) ) ) + bRet = true; + else + pFrame = pFrame->GetNext(); + if ( pCMS && pCMS->m_bStop ) + return false; + } + return bRet; +} + +/** Searches the page containing the searched point. */ + +bool SwPageFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool bTestBackground ) const +{ + Point aPoint( rPoint ); + + // check, if we have to adjust the point + if ( !getFrameArea().Contains( aPoint ) ) + { + aPoint.setX( std::max( aPoint.X(), getFrameArea().Left() ) ); + aPoint.setX( std::min( aPoint.X(), getFrameArea().Right() ) ); + aPoint.setY( std::max( aPoint.Y(), getFrameArea().Top() ) ); + aPoint.setY( std::min( aPoint.Y(), getFrameArea().Bottom() ) ); + } + + bool bRet = false; + //Could it be a free flying one? + //If his content should be protected, we can't set the Cursor in it, thus + //all changes should be impossible. + if ( GetSortedObjs() ) + { + bRet = lcl_GetModelPositionForViewPoint_Objects( this, false, pPos, rPoint, pCMS ); + } + + if ( !bRet ) + { + SwPosition aBackPos( *pPos ); + SwPosition aTextPos( *pPos ); + + //We fix the StartPoint if no Content below the page 'answers' and then + //start all over again one page before the current one. + //However we can't use Flys in such a case. + if (!SwLayoutFrame::GetModelPositionForViewPoint(&aTextPos, aPoint, pCMS)) + { + if ( pCMS && (pCMS->m_bStop || pCMS->m_bExactOnly) ) + { + pCMS->m_bStop = true; + return false; + } + + const SwContentFrame *pCnt = GetContentPos( aPoint, false, false, pCMS, false ); + // GetContentPos may have modified pCMS + if ( pCMS && pCMS->m_bStop ) + return false; + + bool bTextRet = false; + + OSL_ENSURE( pCnt, "Cursor is gone to a Black hole" ); + if( pCMS && pCMS->m_pFill && pCnt->IsTextFrame() ) + bTextRet = pCnt->GetModelPositionForViewPoint( &aTextPos, rPoint, pCMS ); + else + bTextRet = pCnt->GetModelPositionForViewPoint( &aTextPos, aPoint, pCMS ); + + if ( !bTextRet ) + { + // Set point to pCnt, delete mark + // this may happen, if pCnt is hidden + if (pCnt->IsTextFrame()) + { + aTextPos = static_cast<SwTextFrame const*>(pCnt)->MapViewToModelPos(TextFrameIndex(0)); + } + else + { + assert(pCnt->IsNoTextFrame()); + aTextPos = SwPosition( *static_cast<SwNoTextFrame const*>(pCnt)->GetNode() ); + } + } + } + + SwContentNode* pContentNode = aTextPos.nNode.GetNode().GetContentNode(); + bool bConsiderBackground = true; + // If the text position is a clickable field, then that should have priority. + if (pContentNode && pContentNode->IsTextNode()) + { + SwTextNode* pTextNd = pContentNode->GetTextNode(); + SwTextAttr* pTextAttr = pTextNd->GetTextAttrForCharAt(aTextPos.nContent.GetIndex(), RES_TXTATR_FIELD); + if (pTextAttr) + { + const SwField* pField = pTextAttr->GetFormatField().GetField(); + if (pField->IsClickable()) + bConsiderBackground = false; + } + } + + bool bBackRet = false; + // Check objects in the background if nothing else matched + if ( GetSortedObjs() ) + { + bBackRet = lcl_GetModelPositionForViewPoint_Objects( this, true, &aBackPos, rPoint, pCMS ); + } + + if (bConsiderBackground && bTestBackground && bBackRet) + { + (*pPos) = aBackPos; + } + else if (!bBackRet) + { + (*pPos) = aTextPos; + } + else // bBackRet && !(bConsiderBackground && bTestBackground) + { + /* In order to provide a selection as accurate as possible when we have both + * text and background object, then we compute the distance between both + * would-be positions and the click point. The shortest distance wins. + */ + double nTextDistance = 0; + bool bValidTextDistance = false; + if (pContentNode) + { + SwContentFrame* pTextFrame = pContentNode->getLayoutFrame( getRootFrame( ) ); + + // try this again but prefer the "previous" position + SwCursorMoveState aMoveState; + SwCursorMoveState *const pState(pCMS ? pCMS : &aMoveState); + comphelper::FlagRestorationGuard g( + pState->m_bPosMatchesBounds, true); + SwPosition prevTextPos(*pPos); + if (SwLayoutFrame::GetModelPositionForViewPoint(&prevTextPos, aPoint, pState)) + { + SwRect aTextRect; + pTextFrame->GetCharRect(aTextRect, prevTextPos); + + if (prevTextPos.nContent < pContentNode->Len()) + { + // aRextRect is just a line on the left edge of the + // previous character; to get a better measure from + // lcl_getDistance, extend that to a rectangle over + // the entire character. + SwPosition const nextTextPos(prevTextPos.nNode, + SwIndex(prevTextPos.nContent, +1)); + SwRect nextTextRect; + pTextFrame->GetCharRect(nextTextRect, nextTextPos); + SwRectFnSet aRectFnSet(pTextFrame); + if (aRectFnSet.GetTop(aTextRect) == + aRectFnSet.GetTop(nextTextRect)) // same line? + { + // need to handle mixed RTL/LTR portions somehow + if (aRectFnSet.GetLeft(aTextRect) < + aRectFnSet.GetLeft(nextTextRect)) + { + aRectFnSet.SetRight( aTextRect, + aRectFnSet.GetLeft(nextTextRect)); + } + else // RTL + { + aRectFnSet.SetLeft( aTextRect, + aRectFnSet.GetLeft(nextTextRect)); + } + } + } + + nTextDistance = lcl_getDistance(aTextRect, rPoint); + bValidTextDistance = true; + } + } + + double nBackDistance = 0; + bool bValidBackDistance = false; + SwContentNode* pBackNd = aBackPos.nNode.GetNode( ).GetContentNode( ); + if ( pBackNd && bConsiderBackground) + { + // FIXME There are still cases were we don't have the proper node here. + SwContentFrame* pBackFrame = pBackNd->getLayoutFrame( getRootFrame( ) ); + if (pBackFrame) + { + SwRect rBackRect; + pBackFrame->GetCharRect( rBackRect, aBackPos ); + + nBackDistance = lcl_getDistance( rBackRect, rPoint ); + bValidBackDistance = true; + } + } + + if ( bValidTextDistance && bValidBackDistance && basegfx::fTools::more( nTextDistance, nBackDistance ) ) + { + (*pPos) = aBackPos; + } + else + { + (*pPos) = aTextPos; + } + } + } + + rPoint = aPoint; + return true; +} + +bool SwLayoutFrame::FillSelection( SwSelectionList& rList, const SwRect& rRect ) const +{ + if( rRect.Overlaps(GetPaintArea()) ) + { + const SwFrame* pFrame = Lower(); + while( pFrame ) + { + pFrame->FillSelection( rList, rRect ); + pFrame = pFrame->GetNext(); + } + } + return false; +} + +bool SwPageFrame::FillSelection( SwSelectionList& rList, const SwRect& rRect ) const +{ + bool bRet = false; + if( rRect.Overlaps(GetPaintArea()) ) + { + bRet = SwLayoutFrame::FillSelection( rList, rRect ); + if( GetSortedObjs() ) + { + const SwSortedObjs &rObjs = *GetSortedObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + const SwFlyFrame* pFly = pAnchoredObj->DynCastFlyFrame(); + if( !pFly ) + continue; + if( pFly->FillSelection( rList, rRect ) ) + bRet = true; + } + } + } + return bRet; +} + +bool SwRootFrame::FillSelection( SwSelectionList& aSelList, const SwRect& rRect) const +{ + const SwFrame *pPage = Lower(); + const tools::Long nBottom = rRect.Bottom(); + while( pPage ) + { + if( pPage->getFrameArea().Top() < nBottom ) + { + if( pPage->getFrameArea().Bottom() > rRect.Top() ) + pPage->FillSelection( aSelList, rRect ); + pPage = pPage->GetNext(); + } + else + pPage = nullptr; + } + return !aSelList.isEmpty(); +} + +/** Primary passes the call to the first page. + * + * @return false, if the passed Point gets changed + */ +bool SwRootFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool bTestBackground ) const +{ + const bool bOldAction = IsCallbackActionEnabled(); + const_cast<SwRootFrame*>(this)->SetCallbackActionEnabled( false ); + OSL_ENSURE( (Lower() && Lower()->IsPageFrame()), "No PageFrame found." ); + if( pCMS && pCMS->m_pFill ) + pCMS->m_bFillRet = false; + Point aOldPoint = rPoint; + + // search for page containing rPoint. The borders around the pages are considered + const SwPageFrame* pPage = GetPageAtPos( rPoint, nullptr, true ); + + // #i95626# + // special handling for <rPoint> beyond root frames area + if ( !pPage && + rPoint.X() > getFrameArea().Right() && + rPoint.Y() > getFrameArea().Bottom() ) + { + pPage = dynamic_cast<const SwPageFrame*>(Lower()); + while ( pPage && pPage->GetNext() ) + { + pPage = dynamic_cast<const SwPageFrame*>(pPage->GetNext()); + } + } + if ( pPage ) + { + pPage->SwPageFrame::GetModelPositionForViewPoint( pPos, rPoint, pCMS, bTestBackground ); + } + + const_cast<SwRootFrame*>(this)->SetCallbackActionEnabled( bOldAction ); + if( pCMS ) + { + if( pCMS->m_bStop ) + return false; + if( pCMS->m_pFill ) + return pCMS->m_bFillRet; + } + return aOldPoint == rPoint; +} + +/** + * If this is about a Content-carrying cell the Cursor will be force inserted into one of the ContentFrames + * if there are no other options. + * + * There is no entry for protected cells. + */ +bool SwCellFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool ) const +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + // cell frame does not necessarily have a lower (split table cell) + if ( !Lower() ) + return false; + + if ( !(pCMS && pCMS->m_bSetInReadOnly) && + GetFormat()->GetProtect().IsContentProtected() ) + return false; + + if ( pCMS && pCMS->m_eState == CursorMoveState::TableSel ) + { + const SwTabFrame *pTab = FindTabFrame(); + if ( pTab->IsFollow() && pTab->IsInHeadline( *this ) ) + { + pCMS->m_bStop = true; + return false; + } + } + + if ( Lower() ) + { + if ( Lower()->IsLayoutFrame() ) + return SwLayoutFrame::GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + else + { + Calc(pRenderContext); + bool bRet = false; + + const SwFrame *pFrame = Lower(); + while ( pFrame && !bRet ) + { + pFrame->Calc(pRenderContext); + if ( pFrame->getFrameArea().Contains( rPoint ) ) + { + bRet = pFrame->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + if ( pCMS && pCMS->m_bStop ) + return false; + } + pFrame = pFrame->GetNext(); + } + if ( !bRet ) + { + const bool bFill = pCMS && pCMS->m_pFill; + Point aPoint( rPoint ); + const SwContentFrame *pCnt = GetContentPos( rPoint, true ); + if( bFill && pCnt->IsTextFrame() ) + { + rPoint = aPoint; + } + pCnt->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + } + return true; + } + } + + return false; +} + +//Problem: If two Flys have the same size and share the same position then +//they end inside each other. +//Because we recursively check if a Point doesn't randomly lie inside another +//fly which lies completely inside the current Fly we could trigger an endless +//loop with the mentioned situation above. +//Using the helper class SwCursorOszControl we prevent the recursion. During +//a recursion GetModelPositionForViewPoint picks the one which lies on top. +bool SwFlyFrame::GetModelPositionForViewPoint( SwPosition *pPos, Point &rPoint, + SwCursorMoveState* pCMS, bool ) const +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + g_OszCtrl.Entry( this ); + + //If the Points lies inside the Fly, we try hard to set the Cursor inside it. + //However if the Point sits inside a Fly which is completely located inside + //the current one, we call GetModelPositionForViewPoint for it. + Calc(pRenderContext); + bool bInside = getFrameArea().Contains( rPoint ) && Lower(); + bool bRet = false; + + //If a Frame contains a graphic, but only text was requested, it basically + //won't accept the Cursor. + if ( bInside && pCMS && pCMS->m_eState == CursorMoveState::SetOnlyText && + (!Lower() || Lower()->IsNoTextFrame()) ) + bInside = false; + + const SwPageFrame *pPage = FindPageFrame(); + if ( bInside && pPage && pPage->GetSortedObjs() ) + { + SwOrderIter aIter( pPage ); + aIter.Top(); + while ( aIter() && !bRet ) + { + const SwVirtFlyDrawObj* pObj = static_cast<const SwVirtFlyDrawObj*>(aIter()); + const SwFlyFrame* pFly = pObj ? pObj->GetFlyFrame() : nullptr; + if ( pFly && pFly->getFrameArea().Contains( rPoint ) && + getFrameArea().Contains( pFly->getFrameArea() ) ) + { + if (g_OszCtrl.ChkOsz(pFly)) + break; + bRet = pFly->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + if ( bRet ) + break; + if ( pCMS && pCMS->m_bStop ) + return false; + } + aIter.Next(); + } + } + + while ( bInside && !bRet ) + { + const SwFrame *pFrame = Lower(); + while ( pFrame && !bRet ) + { + pFrame->Calc(pRenderContext); + if ( pFrame->getFrameArea().Contains( rPoint ) ) + { + bRet = pFrame->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + if ( pCMS && pCMS->m_bStop ) + return false; + } + pFrame = pFrame->GetNext(); + } + if ( !bRet ) + { + const bool bFill = pCMS && pCMS->m_pFill; + Point aPoint( rPoint ); + const SwContentFrame *pCnt = GetContentPos( rPoint, true, false, pCMS ); + if ( pCMS && pCMS->m_bStop ) + return false; + if( bFill && pCnt->IsTextFrame() ) + { + rPoint = aPoint; + } + pCnt->GetModelPositionForViewPoint( pPos, rPoint, pCMS ); + bRet = true; + } + } + g_OszCtrl.Exit( this ); + return bRet; +} + +/** Layout dependent cursor travelling */ +bool SwNoTextFrame::LeftMargin(SwPaM *pPam) const +{ + if( &pPam->GetNode() != GetNode() ) + return false; + const_cast<SwContentNode*>(GetNode())-> + MakeStartIndex(&pPam->GetPoint()->nContent); + return true; +} + +bool SwNoTextFrame::RightMargin(SwPaM *pPam, bool) const +{ + if( &pPam->GetNode() != GetNode() ) + return false; + const_cast<SwContentNode*>(GetNode())-> + MakeEndIndex(&pPam->GetPoint()->nContent); + return true; +} + +static const SwContentFrame *lcl_GetNxtCnt( const SwContentFrame* pCnt ) +{ + return pCnt->GetNextContentFrame(); +} + +static const SwContentFrame *lcl_GetPrvCnt( const SwContentFrame* pCnt ) +{ + return pCnt->GetPrevContentFrame(); +} + +typedef const SwContentFrame *(*GetNxtPrvCnt)( const SwContentFrame* ); + +/// Frame in repeated headline? +static bool lcl_IsInRepeatedHeadline( const SwFrame *pFrame, + const SwTabFrame** ppTFrame = nullptr ) +{ + const SwTabFrame *pTab = pFrame->FindTabFrame(); + if( ppTFrame ) + *ppTFrame = pTab; + return pTab && pTab->IsFollow() && pTab->IsInHeadline( *pFrame ); +} + +/// Skip protected table cells. Optionally also skip repeated headlines. +//MA 1998-01-26: Chg also skip other protected areas +//FME: Skip follow flow cells +static const SwContentFrame * lcl_MissProtectedFrames( const SwContentFrame *pCnt, + GetNxtPrvCnt fnNxtPrv, + bool bMissHeadline, + bool bInReadOnly, + bool bMissFollowFlowLine ) +{ + if ( pCnt && pCnt->IsInTab() ) + { + bool bProtect = true; + while ( pCnt && bProtect ) + { + const SwLayoutFrame *pCell = pCnt->GetUpper(); + while ( pCell && !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + if ( !pCell || + ( ( bInReadOnly || !pCell->GetFormat()->GetProtect().IsContentProtected() ) && + ( !bMissHeadline || !lcl_IsInRepeatedHeadline( pCell ) ) && + ( !bMissFollowFlowLine || !pCell->IsInFollowFlowRow() ) && + !pCell->IsCoveredCell() ) ) + bProtect = false; + else + pCnt = (*fnNxtPrv)( pCnt ); + } + } + else if ( !bInReadOnly ) + while ( pCnt && pCnt->IsProtected() ) + pCnt = (*fnNxtPrv)( pCnt ); + + return pCnt; +} + +static bool lcl_UpDown( SwPaM *pPam, const SwContentFrame *pStart, + GetNxtPrvCnt fnNxtPrv, bool bInReadOnly ) +{ + OSL_ENSURE( FrameContainsNode(*pStart, pPam->GetNode().GetIndex()), + "lcl_UpDown doesn't work for others." ); + + const SwContentFrame *pCnt = nullptr; + + //We have to cheat a little bit during a table selection: Go to the + //beginning of the cell while going up and go to the end of the cell while + //going down. + bool bTableSel = false; + if ( pStart->IsInTab() && + pPam->GetNode().StartOfSectionNode() != + pPam->GetNode( false ).StartOfSectionNode() ) + { + bTableSel = true; + const SwLayoutFrame *pCell = pStart->GetUpper(); + while ( !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + + // Check, if cell has a Prev/Follow cell: + const bool bFwd = ( fnNxtPrv == lcl_GetNxtCnt ); + const SwLayoutFrame* pTmpCell = bFwd ? + static_cast<const SwCellFrame*>(pCell)->GetFollowCell() : + static_cast<const SwCellFrame*>(pCell)->GetPreviousCell(); + + const SwContentFrame* pTmpStart = pStart; + while ( pTmpCell && nullptr != ( pTmpStart = pTmpCell->ContainsContent() ) ) + { + pCell = pTmpCell; + pTmpCell = bFwd ? + static_cast<const SwCellFrame*>(pCell)->GetFollowCell() : + static_cast<const SwCellFrame*>(pCell)->GetPreviousCell(); + } + const SwContentFrame *pNxt = pCnt = pTmpStart; + + while ( pCell->IsAnLower( pNxt ) ) + { + pCnt = pNxt; + pNxt = (*fnNxtPrv)( pNxt ); + } + } + + pCnt = (*fnNxtPrv)( pCnt ? pCnt : pStart ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + + const SwTabFrame *pStTab = pStart->FindTabFrame(); + const SwTabFrame *pTable = nullptr; + const bool bTab = pStTab || (pCnt && pCnt->IsInTab()); + bool bEnd = !bTab; + + const SwFrame* pVertRefFrame = pStart; + if ( bTableSel && pStTab ) + pVertRefFrame = pStTab; + SwRectFnSet aRectFnSet(pVertRefFrame); + + SwTwips nX = 0; + if ( bTab ) + { + // pStart or pCnt is inside a table. nX will be used for travelling: + SwRect aRect( pStart->getFrameArea() ); + pStart->GetCharRect( aRect, *pPam->GetPoint() ); + Point aCenter = aRect.Center(); + nX = aRectFnSet.IsVert() ? aCenter.Y() : aCenter.X(); + + pTable = pCnt ? pCnt->FindTabFrame() : nullptr; + if ( !pTable ) + pTable = pStTab; + + if ( pStTab && + !pStTab->GetUpper()->IsInTab() && + !pTable->GetUpper()->IsInTab() ) + { + const SwFrame *pCell = pStart->GetUpper(); + while ( pCell && !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + OSL_ENSURE( pCell, "could not find the cell" ); + nX = aRectFnSet.XInc(aRectFnSet.GetLeft(pCell->getFrameArea()), + aRectFnSet.GetWidth(pCell->getFrameArea()) / 2); + + //The flow leads from one table to the next. The X-value needs to be + //corrected based on the middle of the starting cell by the amount + //of the offset of the tables. + if ( pStTab != pTable ) + { + nX += aRectFnSet.GetLeft(pTable->getFrameArea()) - + aRectFnSet.GetLeft(pStTab->getFrameArea()); + } + } + + // Restrict nX to the left and right borders of pTab: + // (is this really necessary?) + if (pTable && !pTable->GetUpper()->IsInTab()) + { + const bool bRTL = pTable->IsRightToLeft(); + const tools::Long nPrtLeft = bRTL ? + aRectFnSet.GetPrtRight(*pTable) : + aRectFnSet.GetPrtLeft(*pTable); + if (bRTL != (aRectFnSet.XDiff(nPrtLeft, nX) > 0)) + nX = nPrtLeft; + else + { + const tools::Long nPrtRight = bRTL ? + aRectFnSet.GetPrtLeft(*pTable) : + aRectFnSet.GetPrtRight(*pTable); + if (bRTL != (aRectFnSet.XDiff(nX, nPrtRight) > 0)) + nX = nPrtRight; + } + } + } + + do + { + //If I'm in the DocumentBody, I want to stay there. + if ( pStart->IsInDocBody() ) + { + while ( pCnt && (!pCnt->IsInDocBody() || + (pCnt->IsTextFrame() && static_cast<const SwTextFrame*>(pCnt)->IsHiddenNow()))) + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + //If I'm in the FootNoteArea, I try to reach the next FootNoteArea in + //case of necessity. + else if ( pStart->IsInFootnote() ) + { + while ( pCnt && (!pCnt->IsInFootnote() || + (pCnt->IsTextFrame() && static_cast<const SwTextFrame*>(pCnt)->IsHiddenNow()))) + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + //In Flys we can go ahead blindly as long as we find a Content. + else if ( pStart->IsInFly() ) + { + if ( pCnt && pCnt->IsTextFrame() && static_cast<const SwTextFrame*>(pCnt)->IsHiddenNow() ) + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + //Otherwise I'll just refuse to leave to current area. + else if ( pCnt ) + { + const SwFrame *pUp = pStart->GetUpper(); + while (pUp && pUp->GetUpper() && !(pUp->GetType() & FRM_HEADFOOT)) + pUp = pUp->GetUpper(); + bool bSame = false; + const SwFrame *pCntUp = pCnt->GetUpper(); + while ( pCntUp && !bSame ) + { + if ( pUp == pCntUp ) + bSame = true; + else + pCntUp = pCntUp->GetUpper(); + } + if ( !bSame ) + pCnt = nullptr; + else if (pCnt->IsTextFrame() && static_cast<const SwTextFrame*>(pCnt)->IsHiddenNow()) // i73332 + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + if ( bTab ) + { + if ( !pCnt ) + bEnd = true; + else + { + const SwTabFrame *pTab = pCnt->FindTabFrame(); + if( !pTab ) + bEnd = true; + else + { + if ( pTab != pTable ) + { + //The flow leads from one table to the next. The X-value + //needs to be corrected by the amount of the offset of + //the tables + if ( pTable && + !pTab->GetUpper()->IsInTab() && + !pTable->GetUpper()->IsInTab() ) + nX += pTab->getFrameArea().Left() - pTable->getFrameArea().Left(); + pTable = pTab; + } + const SwLayoutFrame *pCell = pCnt->GetUpper(); + while ( pCell && !pCell->IsCellFrame() ) + pCell = pCell->GetUpper(); + + Point aInsideCell; + Point aInsideCnt; + if ( pCell ) + { + tools::Long nTmpTop = aRectFnSet.GetTop(pCell->getFrameArea()); + if ( aRectFnSet.IsVert() ) + { + if ( nTmpTop ) + nTmpTop = aRectFnSet.XInc(nTmpTop, -1); + + aInsideCell = Point( nTmpTop, nX ); + } + else + aInsideCell = Point( nX, nTmpTop ); + } + + tools::Long nTmpTop = aRectFnSet.GetTop(pCnt->getFrameArea()); + if ( aRectFnSet.IsVert() ) + { + if ( nTmpTop ) + nTmpTop = aRectFnSet.XInc(nTmpTop, -1); + + aInsideCnt = Point( nTmpTop, nX ); + } + else + aInsideCnt = Point( nX, nTmpTop ); + + if ( pCell && pCell->getFrameArea().Contains( aInsideCell ) ) + { + bEnd = true; + //Get the right Content out of the cell. + if ( !pCnt->getFrameArea().Contains( aInsideCnt ) ) + { + pCnt = pCell->ContainsContent(); + if ( fnNxtPrv == lcl_GetPrvCnt ) + while ( pCell->IsAnLower(pCnt->GetNextContentFrame()) ) + pCnt = pCnt->GetNextContentFrame(); + } + } + else if ( pCnt->getFrameArea().Contains( aInsideCnt ) ) + bEnd = true; + } + } + if ( !bEnd ) + { + pCnt = (*fnNxtPrv)( pCnt ); + pCnt = ::lcl_MissProtectedFrames( pCnt, fnNxtPrv, true, bInReadOnly, bTableSel ); + } + } + + } while ( !bEnd || + (pCnt && pCnt->IsTextFrame() && static_cast<const SwTextFrame*>(pCnt)->IsHiddenNow())); + + if (pCnt == nullptr) + { + return false; + } + if (pCnt->IsTextFrame()) + { + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pCnt)); + *pPam->GetPoint() = pFrame->MapViewToModelPos(TextFrameIndex( + fnNxtPrv == lcl_GetPrvCnt + ? pFrame->GetText().getLength() + : 0)); + } + else + { // set the Point on the Content-Node + assert(pCnt->IsNoTextFrame()); + SwContentNode *const pCNd = const_cast<SwContentNode*>(static_cast<SwNoTextFrame const*>(pCnt)->GetNode()); + pPam->GetPoint()->nNode = *pCNd; + if ( fnNxtPrv == lcl_GetPrvCnt ) + pCNd->MakeEndIndex( &pPam->GetPoint()->nContent ); + else + pCNd->MakeStartIndex( &pPam->GetPoint()->nContent ); + } + return true; +} + +bool SwContentFrame::UnitUp( SwPaM* pPam, const SwTwips, bool bInReadOnly ) const +{ + return ::lcl_UpDown( pPam, this, lcl_GetPrvCnt, bInReadOnly ); +} + +bool SwContentFrame::UnitDown( SwPaM* pPam, const SwTwips, bool bInReadOnly ) const +{ + return ::lcl_UpDown( pPam, this, lcl_GetNxtCnt, bInReadOnly ); +} + +/** Returns the number of the current page. + * + * If the method gets a PaM then the current page is the one in which the PaM sits. Otherwise the + * current page is the first one inside the VisibleArea. We only work on available pages! + */ +sal_uInt16 SwRootFrame::GetCurrPage( const SwPaM *pActualCursor ) const +{ + OSL_ENSURE( pActualCursor, "got no page cursor" ); + SwFrame const*const pActFrame = pActualCursor->GetPoint()->nNode.GetNode(). + GetContentNode()->getLayoutFrame(this, + pActualCursor->GetPoint()); + return pActFrame->FindPageFrame()->GetPhyPageNum(); +} + +/** Returns a PaM which sits at the beginning of the requested page. + * + * Formatting is done as far as necessary. + * The PaM sits on the last page, if the page number was chosen too big. + * + * @return Null, if the operation was not possible. + */ +sal_uInt16 SwRootFrame::SetCurrPage( SwCursor* pToSet, sal_uInt16 nPageNum ) +{ + vcl::RenderContext* pRenderContext = GetCurrShell() ? GetCurrShell()->GetOut() : nullptr; + OSL_ENSURE( Lower() && Lower()->IsPageFrame(), "No page available." ); + + SwPageFrame *pPage = static_cast<SwPageFrame*>(Lower()); + bool bEnd =false; + while ( !bEnd && pPage->GetPhyPageNum() != nPageNum ) + { if ( pPage->GetNext() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + else + { //Search the first ContentFrame and format until a new page is started + //or until the ContentFrame are all done. + const SwContentFrame *pContent = pPage->ContainsContent(); + while ( pContent && pPage->IsAnLower( pContent ) ) + { + pContent->Calc(pRenderContext); + pContent = pContent->GetNextContentFrame(); + } + //Either this is a new page or we found the last page. + if ( pPage->GetNext() ) + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + else + bEnd = true; + } + } + //pPage now points to the 'requested' page. Now we have to create the PaM + //on the beginning of the first ContentFrame in the body-text. + //If this is a footnote-page, the PaM will be set in the first footnote. + const SwContentFrame *pContent = pPage->ContainsContent(); + if ( pPage->IsFootnotePage() ) + while ( pContent && !pContent->IsInFootnote() ) + pContent = pContent->GetNextContentFrame(); + else + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + if ( pContent ) + { + assert(pContent->IsTextFrame()); + SwTextFrame const*const pFrame(static_cast<const SwTextFrame*>(pContent)); + *pToSet->GetPoint() = pFrame->MapViewToModelPos(pFrame->GetOffset()); + + SwShellCursor* pSCursor = dynamic_cast<SwShellCursor*>(pToSet); + if( pSCursor ) + { + Point &rPt = pSCursor->GetPtPos(); + rPt = pContent->getFrameArea().Pos(); + rPt += pContent->getFramePrintArea().Pos(); + } + return pPage->GetPhyPageNum(); + } + return 0; +} + +SwContentFrame *GetFirstSub( const SwLayoutFrame *pLayout ) +{ + return const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(pLayout))->FindFirstBodyContent(); +} + +SwContentFrame *GetLastSub( const SwLayoutFrame *pLayout ) +{ + return const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(pLayout))->FindLastBodyContent(); +} + +SwLayoutFrame *GetNextFrame( const SwLayoutFrame *pFrame ) +{ + SwLayoutFrame *pNext = + (pFrame->GetNext() && pFrame->GetNext()->IsLayoutFrame()) ? + const_cast<SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(pFrame->GetNext())) : nullptr; + // #i39402# in case of an empty page + if(pNext && !pNext->ContainsContent()) + pNext = (pNext->GetNext() && pNext->GetNext()->IsLayoutFrame()) ? + static_cast<SwLayoutFrame*>(pNext->GetNext()) : nullptr; + return pNext; +} + +SwLayoutFrame *GetThisFrame( const SwLayoutFrame *pFrame ) +{ + return const_cast<SwLayoutFrame*>(pFrame); +} + +SwLayoutFrame *GetPrevFrame( const SwLayoutFrame *pFrame ) +{ + SwLayoutFrame *pPrev = + (pFrame->GetPrev() && pFrame->GetPrev()->IsLayoutFrame()) ? + const_cast<SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(pFrame->GetPrev())) : nullptr; + // #i39402# in case of an empty page + if(pPrev && !pPrev->ContainsContent()) + pPrev = (pPrev->GetPrev() && pPrev->GetPrev()->IsLayoutFrame()) ? + static_cast<SwLayoutFrame*>(pPrev->GetPrev()) : nullptr; + return pPrev; +} + +/** + * Returns the first/last Contentframe (controlled using the parameter fnPosPage) + * of the current/previous/next page (controlled using the parameter fnWhichPage). + */ +bool GetFrameInPage( const SwContentFrame *pCnt, SwWhichPage fnWhichPage, + SwPosPage fnPosPage, SwPaM *pPam ) +{ + //First find the requested page, at first the current, then the one which + //was requests through fnWichPage. + const SwLayoutFrame *pLayoutFrame = pCnt->FindPageFrame(); + if ( !pLayoutFrame || (nullptr == (pLayoutFrame = (*fnWhichPage)(pLayoutFrame))) ) + return false; + + //Now the desired ContentFrame below the page + pCnt = (*fnPosPage)(pLayoutFrame); + if( nullptr == pCnt ) + return false; + else + { + // repeated headlines in tables + if ( pCnt->IsInTab() && fnPosPage == GetFirstSub ) + { + const SwTabFrame* pTab = pCnt->FindTabFrame(); + if ( pTab->IsFollow() ) + { + if ( pTab->IsInHeadline( *pCnt ) ) + { + SwLayoutFrame* pRow = pTab->GetFirstNonHeadlineRow(); + if ( pRow ) + { + // We are in the first line of a follow table + // with repeated headings. + // To actually make a "real" move we take the first content + // of the next row + pCnt = pRow->ContainsContent(); + if ( ! pCnt ) + return false; + } + } + } + } + + assert(pCnt->IsTextFrame()); + SwTextFrame const*const pFrame(static_cast<const SwTextFrame*>(pCnt)); + TextFrameIndex const nIdx((fnPosPage == GetFirstSub) + ? pFrame->GetOffset() + : (pFrame->GetFollow()) + ? pFrame->GetFollow()->GetOffset() - TextFrameIndex(1) + : TextFrameIndex(pFrame->GetText().getLength())); + *pPam->GetPoint() = pFrame->MapViewToModelPos(nIdx); + return true; + } +} + +static sal_uInt64 CalcDiff(const Point &rPt1, const Point &rPt2) +{ + //Calculate the distance between the two points. + //'delta' X^2 + 'delta'Y^2 = 'distance'^2 + sal_uInt64 dX = std::max( rPt1.X(), rPt2.X() ) - + std::min( rPt1.X(), rPt2.X() ), + dY = std::max( rPt1.Y(), rPt2.Y() ) - + std::min( rPt1.Y(), rPt2.Y() ); + return (dX * dX) + (dY * dY); +} + +/** Check if the point lies inside the page part in which also the ContentFrame lies. + * + * In this context header, page body, footer and footnote-container count as page part. + * This will suit the purpose that the ContentFrame which lies in the "right" page part will be + * accepted instead of one which doesn't lie there although his distance to the point is shorter. + */ +static const SwLayoutFrame* lcl_Inside( const SwContentFrame *pCnt, Point const & rPt ) +{ + const SwLayoutFrame* pUp = pCnt->GetUpper(); + while( pUp ) + { + if( pUp->IsPageBodyFrame() || pUp->IsFooterFrame() || pUp->IsHeaderFrame() ) + { + if( rPt.Y() >= pUp->getFrameArea().Top() && rPt.Y() <= pUp->getFrameArea().Bottom() ) + return pUp; + return nullptr; + } + if( pUp->IsFootnoteContFrame() ) + return pUp->getFrameArea().Contains( rPt ) ? pUp : nullptr; + pUp = pUp->GetUpper(); + } + return nullptr; +} + +/** Search for the nearest Content to pass. + * + * Considers the previous, the current and the next page. + * If no content is found, the area gets expanded until one is found. + * + * @return The 'semantically correct' position inside the PrtArea of the found ContentFrame. + */ +const SwContentFrame *SwLayoutFrame::GetContentPos( Point& rPoint, + const bool bDontLeave, + const bool bBodyOnly, + SwCursorMoveState *pCMS, + const bool bDefaultExpand ) const +{ + //Determine the first ContentFrame. + const SwLayoutFrame *pStart = (!bDontLeave && bDefaultExpand && GetPrev()) ? + static_cast<const SwLayoutFrame*>(GetPrev()) : this; + const SwContentFrame *pContent = pStart->ContainsContent(); + + if ( !pContent && (GetPrev() && !bDontLeave) ) + pContent = ContainsContent(); + + if ( bBodyOnly && pContent && !pContent->IsInDocBody() ) + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + + const SwContentFrame *pActual= pContent; + const SwLayoutFrame *pInside = nullptr; + sal_uInt16 nMaxPage = GetPhyPageNum() + (bDefaultExpand ? 1 : 0); + Point aPoint = rPoint; + sal_uInt64 nDistance = SAL_MAX_UINT64; + + while ( true ) //A loop to be sure we always find one. + { + while ( pContent && + ((!bDontLeave || IsAnLower( pContent )) && + (pContent->GetPhyPageNum() <= nMaxPage)) ) + { + if ( pContent->getFrameArea().Width() && + ( !bBodyOnly || pContent->IsInDocBody() ) ) + { + //If the Content lies in a protected area (cell, Footnote, section), + //we search the next Content which is not protected. + const SwContentFrame *pComp = pContent; + pContent = ::lcl_MissProtectedFrames( pContent, lcl_GetNxtCnt, false, + pCMS && pCMS->m_bSetInReadOnly, false ); + if ( pComp != pContent ) + continue; + + if ( !pContent->IsTextFrame() || !static_cast<const SwTextFrame*>(pContent)->IsHiddenNow() ) + { + SwRect aContentFrame( pContent->UnionFrame() ); + if ( aContentFrame.Contains( rPoint ) ) + { + pActual = pContent; + aPoint = rPoint; + break; + } + //The distance from rPoint to the nearest Point of pContent + //will now be calculated. + Point aContentPoint( rPoint ); + + //First set the vertical position + if ( aContentFrame.Top() > aContentPoint.Y() ) + aContentPoint.setY( aContentFrame.Top() ); + else if ( aContentFrame.Bottom() < aContentPoint.Y() ) + aContentPoint.setY( aContentFrame.Bottom() ); + + //Now the horizontal position + if ( aContentFrame.Left() > aContentPoint.X() ) + aContentPoint.setX( aContentFrame.Left() ); + else if ( aContentFrame.Right() < aContentPoint.X() ) + aContentPoint.setX( aContentFrame.Right() ); + + // pInside is a page area in which the point lies. As soon + // as pInside != 0 only frames are accepted which are + // placed inside. + if( !pInside || ( pInside->IsAnLower( pContent ) && + ( !pContent->IsInFootnote() || pInside->IsFootnoteContFrame() ) ) ) + { + const sal_uInt64 nDiff = ::CalcDiff(aContentPoint, rPoint); + bool bBetter = nDiff < nDistance; // This one is nearer + if( !pInside ) + { + pInside = lcl_Inside( pContent, rPoint ); + if( pInside ) // In the "right" page area + bBetter = true; + } + if( bBetter ) + { + aPoint = aContentPoint; + nDistance = nDiff; + pActual = pContent; + } + } + } + } + pContent = pContent->GetNextContentFrame(); + if ( bBodyOnly ) + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + } + if ( !pActual ) + { //If we not yet found one we have to expand the searched + //area, sometime we will find one! + //MA 1997-01-09: Opt for many empty pages - if we only search inside + //the body, we can expand the searched area sufficiently in one step. + if ( bBodyOnly ) + { + while ( !pContent && pStart->GetPrev() ) + { + ++nMaxPage; + if( !pStart->GetPrev()->IsLayoutFrame() ) + return nullptr; + pStart = static_cast<const SwLayoutFrame*>(pStart->GetPrev()); + if( pStart->IsInDocBody() ) + pContent = pStart->ContainsContent(); + else + { + const SwPageFrame *pPage = pStart->FindPageFrame(); + if( !pPage ) + return nullptr; + pContent = pPage->FindFirstBodyContent(); + } + } + if ( !pContent ) // Somewhere down the road we have to start with one! + { + const SwPageFrame *pPage = pStart->FindPageFrame(); + if( !pPage ) + return nullptr; + pContent = pPage->GetUpper()->ContainsContent(); + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + if ( !pContent ) + return nullptr; // There is no document content yet! + } + } + else + { + ++nMaxPage; + if ( pStart->GetPrev() ) + { + if( !pStart->GetPrev()->IsLayoutFrame() ) + return nullptr; + pStart = static_cast<const SwLayoutFrame*>(pStart->GetPrev()); + pContent = pStart->ContainsContent(); + } + else // Somewhere down the road we have to start with one! + { + const SwPageFrame *pPage = pStart->FindPageFrame(); + if( !pPage ) + return nullptr; + pContent = pPage->GetUpper()->ContainsContent(); + } + } + pActual = pContent; + } + else + break; + } + + OSL_ENSURE( pActual, "no Content found." ); + OSL_ENSURE( !bBodyOnly || pActual->IsInDocBody(), "Content not in Body." ); + + //Special case for selecting tables not in repeated TableHeadlines. + if ( pActual->IsInTab() && pCMS && pCMS->m_eState == CursorMoveState::TableSel ) + { + const SwTabFrame *pTab = pActual->FindTabFrame(); + if ( pTab->IsFollow() && pTab->IsInHeadline( *pActual ) ) + { + pCMS->m_bStop = true; + return nullptr; + } + } + + //A small correction at the first/last + Size aActualSize( pActual->getFramePrintArea().SSize() ); + if ( aActualSize.Height() > pActual->GetUpper()->getFramePrintArea().Height() ) + aActualSize.setHeight( pActual->GetUpper()->getFramePrintArea().Height() ); + + SwRectFnSet aRectFnSet(pActual); + if ( !pActual->GetPrev() && + aRectFnSet.YDiff( aRectFnSet.GetPrtTop(*pActual), + aRectFnSet.IsVert() ? rPoint.X() : rPoint.Y() ) > 0 ) + { + aPoint.setY( pActual->getFrameArea().Top() + pActual->getFramePrintArea().Top() ); + aPoint.setX( pActual->getFrameArea().Left() + + ( pActual->IsRightToLeft() || aRectFnSet.IsVert() ? + pActual->getFramePrintArea().Right() : + pActual->getFramePrintArea().Left() ) ); + } + else if ( !pActual->GetNext() && + aRectFnSet.YDiff( aRectFnSet.GetPrtBottom(*pActual), + aRectFnSet.IsVert() ? rPoint.X() : rPoint.Y() ) < 0 ) + { + aPoint.setY( pActual->getFrameArea().Top() + pActual->getFramePrintArea().Bottom() ); + aPoint.setX( pActual->getFrameArea().Left() + + ( pActual->IsRightToLeft() || aRectFnSet.IsVert() ? + pActual->getFramePrintArea().Left() : + pActual->getFramePrintArea().Right() ) ); + } + + //Bring the Point into the PrtArea + const SwRect aRect( pActual->getFrameArea().Pos() + pActual->getFramePrintArea().Pos(), + aActualSize ); + if ( aPoint.Y() < aRect.Top() ) + aPoint.setY( aRect.Top() ); + else if ( aPoint.Y() > aRect.Bottom() ) + aPoint.setY( aRect.Bottom() ); + if ( aPoint.X() < aRect.Left() ) + aPoint.setX( aRect.Left() ); + else if ( aPoint.X() > aRect.Right() ) + aPoint.setX( aRect.Right() ); + rPoint = aPoint; + return pActual; +} + +/** Same as SwLayoutFrame::GetContentPos(). Specialized for fields and border. */ +void SwPageFrame::GetContentPosition( const Point &rPt, SwPosition &rPos ) const +{ + //Determine the first ContentFrame. + const SwContentFrame *pContent = ContainsContent(); + if ( pContent ) + { + //Look back one more (if possible). + const SwContentFrame *pTmp = pContent->GetPrevContentFrame(); + while ( pTmp && !pTmp->IsInDocBody() ) + pTmp = pTmp->GetPrevContentFrame(); + if ( pTmp ) + pContent = pTmp; + } + else + pContent = GetUpper()->ContainsContent(); + + const SwContentFrame *pAct = pContent; + Point aAct = rPt; + sal_uInt64 nDist = SAL_MAX_UINT64; + + while ( pContent ) + { + SwRect aContentFrame( pContent->UnionFrame() ); + if ( aContentFrame.Contains( rPt ) ) + { + //This is the nearest one. + pAct = pContent; + break; + } + + //Calculate the distance from rPt to the nearest point of pContent. + Point aPoint( rPt ); + + //Calculate the vertical position first + if ( aContentFrame.Top() > rPt.Y() ) + aPoint.setY( aContentFrame.Top() ); + else if ( aContentFrame.Bottom() < rPt.Y() ) + aPoint.setY( aContentFrame.Bottom() ); + + //And now the horizontal position + if ( aContentFrame.Left() > rPt.X() ) + aPoint.setX( aContentFrame.Left() ); + else if ( aContentFrame.Right() < rPt.X() ) + aPoint.setX( aContentFrame.Right() ); + + const sal_uInt64 nDiff = ::CalcDiff( aPoint, rPt ); + if ( nDiff < nDist ) + { + aAct = aPoint; + nDist = nDiff; + pAct = pContent; + } + else if ( aContentFrame.Top() > getFrameArea().Bottom() ) + //In terms of fields, it's not possible to be closer any more! + break; + + pContent = pContent->GetNextContentFrame(); + while ( pContent && !pContent->IsInDocBody() ) + pContent = pContent->GetNextContentFrame(); + } + + //Bring the point into the PrtArea. + const SwRect aRect( pAct->getFrameArea().Pos() + pAct->getFramePrintArea().Pos(), pAct->getFramePrintArea().SSize() ); + if ( aAct.Y() < aRect.Top() ) + aAct.setY( aRect.Top() ); + else if ( aAct.Y() > aRect.Bottom() ) + aAct.setY( aRect.Bottom() ); + if ( aAct.X() < aRect.Left() ) + aAct.setX( aRect.Left() ); + else if ( aAct.X() > aRect.Right() ) + aAct.setX( aRect.Right() ); + + if (!pAct->isFrameAreaDefinitionValid() || + (pAct->IsTextFrame() && !static_cast<SwTextFrame const*>(pAct)->HasPara())) + { + // ContentFrame not formatted -> always on node-beginning + // tdf#100635 also if the SwTextFrame would require reformatting, + // which is unwanted in case this is called from text formatting code + rPos = static_cast<SwTextFrame const*>(pAct)->MapViewToModelPos(TextFrameIndex(0)); + } + else + { + SwCursorMoveState aTmpState( CursorMoveState::SetOnlyText ); + pAct->GetModelPositionForViewPoint( &rPos, aAct, &aTmpState ); + } +} + +/** Search the nearest Content to the passed point. + * + * Only search inside the BodyText. + * @note Only the nearest vertically one will be searched. + * @note JP 11.10.2001: only in tables we try to find the right column - Bug 72294 + */ +Point SwRootFrame::GetNextPrevContentPos( const Point& rPoint, bool bNext ) const +{ + vcl::RenderContext* pRenderContext = GetCurrShell() ? GetCurrShell()->GetOut() : nullptr; + // #123110# - disable creation of an action by a callback + // event during processing of this method. Needed because formatting is + // triggered by this method. + DisableCallbackAction aDisableCallbackAction(const_cast<SwRootFrame&>(*this)); + //Search the first ContentFrame and his successor in the body area. + //To be efficient (and not formatting too much) we'll start at the correct + //page. + const SwLayoutFrame *pPage = static_cast<const SwLayoutFrame*>(Lower()); + if( pPage ) + while( pPage->GetNext() && pPage->getFrameArea().Bottom() < rPoint.Y() ) + pPage = static_cast<const SwLayoutFrame*>(pPage->GetNext()); + + const SwContentFrame *pCnt = pPage ? pPage->ContainsContent() : ContainsContent(); + while ( pCnt && !pCnt->IsInDocBody() ) + pCnt = pCnt->GetNextContentFrame(); + + if ( !pCnt ) + return Point( 0, 0 ); + + pCnt->Calc(pRenderContext); + if( !bNext ) + { + // As long as the point lies before the first ContentFrame and there are + // still precedent pages I'll go to the next page. + while ( rPoint.Y() < pCnt->getFrameArea().Top() && pPage->GetPrev() ) + { + pPage = static_cast<const SwLayoutFrame*>(pPage->GetPrev()); + pCnt = pPage->ContainsContent(); + while ( !pCnt ) + { + pPage = static_cast<const SwLayoutFrame*>(pPage->GetPrev()); + if ( pPage ) + pCnt = pPage->ContainsContent(); + else + return ContainsContent()->UnionFrame().Pos(); + } + pCnt->Calc(pRenderContext); + } + } + + //Does the point lie above the first ContentFrame? + if ( rPoint.Y() < pCnt->getFrameArea().Top() && !lcl_IsInRepeatedHeadline( pCnt ) ) + return pCnt->UnionFrame().Pos(); + + Point aRet(0, 0); + do + { + //Does the point lie in the current ContentFrame? + SwRect aContentFrame( pCnt->UnionFrame() ); + if ( aContentFrame.Contains( rPoint ) && !lcl_IsInRepeatedHeadline( pCnt )) + { + aRet = rPoint; + break; + } + + //Is the current one the last ContentFrame? + //If the next ContentFrame lies behind the point, then the current on is the + //one we searched. + const SwContentFrame *pNxt = pCnt->GetNextContentFrame(); + while ( pNxt && !pNxt->IsInDocBody() ) + pNxt = pNxt->GetNextContentFrame(); + + //Does the point lie behind the last ContentFrame? + if ( !pNxt ) + { + aRet = Point( aContentFrame.Right(), aContentFrame.Bottom() ); + break; + } + + //If the next ContentFrame lies behind the point then it is the one we + //searched. + const SwTabFrame* pTFrame; + pNxt->Calc(pRenderContext); + if( pNxt->getFrameArea().Top() > rPoint.Y() && + !lcl_IsInRepeatedHeadline( pCnt, &pTFrame ) && + ( !pTFrame || pNxt->getFrameArea().Left() > rPoint.X() )) + { + if (bNext) + aRet = pNxt->getFrameArea().Pos(); + else + aRet = Point( aContentFrame.Right(), aContentFrame.Bottom() ); + break; + } + pCnt = pNxt; + } + while (pCnt); + return aRet; +} + +/** Returns the absolute document position of the desired page. + * + * Formatting is done only as far as needed and only if bFormat=true. + * Pos is set to the one of the last page, if the page number was chosen too big. + * + * @return Null, if the operation failed. + */ +Point SwRootFrame::GetPagePos( sal_uInt16 nPageNum ) const +{ + OSL_ENSURE( Lower() && Lower()->IsPageFrame(), "No page available." ); + + const SwPageFrame *pPage = static_cast<const SwPageFrame*>(Lower()); + while ( true ) + { + if ( pPage->GetPhyPageNum() >= nPageNum || !pPage->GetNext() ) + break; + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + } + return pPage->getFrameArea().Pos(); +} + +/** get page frame by physical page number + * + * @return pointer to the page frame with the given physical page number + */ +SwPageFrame* SwRootFrame::GetPageByPageNum( sal_uInt16 _nPageNum ) const +{ + const SwPageFrame* pPageFrame = static_cast<const SwPageFrame*>( Lower() ); + while ( pPageFrame && pPageFrame->GetPhyPageNum() < _nPageNum ) + { + pPageFrame = static_cast<const SwPageFrame*>( pPageFrame->GetNext() ); + } + + if ( pPageFrame && pPageFrame->GetPhyPageNum() == _nPageNum ) + { + return const_cast<SwPageFrame*>( pPageFrame ); + } + else + { + return nullptr; + } +} + +/** + * @return true, when the given physical pagenumber doesn't exist or this page is an empty page. + */ +bool SwRootFrame::IsDummyPage( sal_uInt16 nPageNum ) const +{ + if( !Lower() || !nPageNum || nPageNum > GetPageNum() ) + return true; + + const SwPageFrame *pPage = static_cast<const SwPageFrame*>(Lower()); + while( pPage && nPageNum < pPage->GetPhyPageNum() ) + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + return !pPage || pPage->IsEmptyPage(); +} + +/** Is the Frame or rather the Section in which it lies protected? + * + * Also Fly in Fly in ... and Footnotes + */ +bool SwFrame::IsProtected() const +{ + if (IsTextFrame()) + { + const SwDoc *pDoc = &static_cast<const SwTextFrame*>(this)->GetDoc(); + bool isFormProtected=pDoc->GetDocumentSettingManager().get(DocumentSettingId::PROTECT_FORM ); + if (isFormProtected) + { + return false; // TODO a hack for now, well deal with it later, I we return true here we have a "double" locking + } + } + //The Frame can be protected in borders, cells or sections. + //Also goes up FlyFrames recursive and from footnote to anchor. + const SwFrame *pFrame = this; + do + { + if (pFrame->IsTextFrame()) + { // sw_redlinehide: redlines can't overlap section nodes, so any node will do + if (static_cast<SwTextFrame const*>(pFrame)->GetTextNodeFirst()->IsInProtectSect()) + { + return true; + } + } + else if ( pFrame->IsContentFrame() ) + { + assert(pFrame->IsNoTextFrame()); + if (static_cast<const SwNoTextFrame*>(pFrame)->GetNode() && + static_cast<const SwNoTextFrame*>(pFrame)->GetNode()->IsInProtectSect()) + { + return true; + } + } + else + { + if ( static_cast<const SwLayoutFrame*>(pFrame)->GetFormat() && + static_cast<const SwLayoutFrame*>(pFrame)->GetFormat()-> + GetProtect().IsContentProtected() ) + return true; + if ( pFrame->IsCoveredCell() ) + return true; + } + if ( pFrame->IsFlyFrame() ) + { + //In a chain the protection of the content can be specified by the + //master of the chain. + if ( static_cast<const SwFlyFrame*>(pFrame)->GetPrevLink() ) + { + const SwFlyFrame *pMaster = static_cast<const SwFlyFrame*>(pFrame); + do + { pMaster = pMaster->GetPrevLink(); + } while ( pMaster->GetPrevLink() ); + if ( pMaster->IsProtected() ) + return true; + } + pFrame = static_cast<const SwFlyFrame*>(pFrame)->GetAnchorFrame(); + } + else if ( pFrame->IsFootnoteFrame() ) + pFrame = static_cast<const SwFootnoteFrame*>(pFrame)->GetRef(); + else + pFrame = pFrame->GetUpper(); + + } while ( pFrame ); + + return false; +} + +/** @return the physical page number */ +sal_uInt16 SwFrame::GetPhyPageNum() const +{ + const SwPageFrame *pPage = FindPageFrame(); + return pPage ? pPage->GetPhyPageNum() : 0; +} + +/** Decides if the page want to be a right page or not. + * + * If the first content of the page has a page descriptor, we take the follow + * of the page descriptor of the last not empty page. If this descriptor allows + * only right(left) pages and the page isn't an empty page then it wants to be + * such right(left) page. If the descriptor allows right and left pages, we + * look for a number offset in the first content. If there is one, odd number + * results right pages (or left pages if document starts with even number), + * even number results left pages (or right pages if document starts with even + * number). + * If there is no number offset, we take the physical page number instead, + * but a previous empty page doesn't count. + */ +bool SwFrame::WannaRightPage() const +{ + const SwPageFrame *pPage = FindPageFrame(); + if ( !pPage || !pPage->GetUpper() ) + return true; + + const SwFrame *pFlow = pPage->FindFirstBodyContent(); + const SwPageDesc *pDesc = nullptr; + ::std::optional<sal_uInt16> oPgNum; + if ( pFlow ) + { + if ( pFlow->IsInTab() ) + pFlow = pFlow->FindTabFrame(); + const SwFlowFrame *pTmp = SwFlowFrame::CastFlowFrame( pFlow ); + if ( !pTmp->IsFollow() ) + { + const SwFormatPageDesc& rPgDesc = pFlow->GetPageDescItem(); + pDesc = rPgDesc.GetPageDesc(); + oPgNum = rPgDesc.GetNumOffset(); + } + } + if ( !pDesc ) + { + SwPageFrame *pPrv = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(pPage->GetPrev())); + if( pPrv && pPrv->IsEmptyPage() ) + pPrv = static_cast<SwPageFrame*>(pPrv->GetPrev()); + if( pPrv ) + pDesc = pPrv->GetPageDesc()->GetFollow(); + else + { + const SwDoc* pDoc = pPage->GetFormat()->GetDoc(); + pDesc = &pDoc->GetPageDesc( 0 ); + } + } + OSL_ENSURE( pDesc, "No pagedescriptor" ); + bool isRightPage; + if( oPgNum ) + isRightPage = sw::IsRightPageByNumber(*mpRoot, *oPgNum); + else + { + isRightPage = pPage->OnRightPage(); + if( pPage->GetPrev() && static_cast<const SwPageFrame*>(pPage->GetPrev())->IsEmptyPage() ) + isRightPage = !isRightPage; + } + if( !pPage->IsEmptyPage() ) + { + if( !pDesc->GetRightFormat() ) + isRightPage = false; + else if( !pDesc->GetLeftFormat() ) + isRightPage = true; + } + return isRightPage; +} + +bool SwFrame::OnFirstPage() const +{ + bool bRet = false; + const SwPageFrame *pPage = FindPageFrame(); + + if (pPage) + { + const SwPageFrame* pPrevFrame = dynamic_cast<const SwPageFrame*>(pPage->GetPrev()); + if (pPrevFrame) + { + // first page of layout may be empty page, but only if it starts with "Left Page" style + const SwPageDesc* pDesc = pPage->GetPageDesc(); + bRet = pPrevFrame->GetPageDesc() != pDesc; + } + else + bRet = true; + } + return bRet; +} + +void SwFrame::Calc(vcl::RenderContext* pRenderContext) const +{ + if ( !isFrameAreaPositionValid() || !isFramePrintAreaValid() || !isFrameAreaSizeValid() ) + { + const_cast<SwFrame*>(this)->PrepareMake(pRenderContext); + } +} + +Point SwFrame::GetRelPos() const +{ + Point aRet( getFrameArea().Pos() ); + // here we cast since SwLayoutFrame is declared only as forwarded + aRet -= GetUpper()->getFramePrintArea().Pos(); + aRet -= GetUpper()->getFrameArea().Pos(); + return aRet; +} + +/** @return the virtual page number with the offset. */ +sal_uInt16 SwFrame::GetVirtPageNum() const +{ + const SwPageFrame *pPage = FindPageFrame(); + if ( !pPage || !pPage->GetUpper() ) + return 0; + + sal_uInt16 nPhyPage = pPage->GetPhyPageNum(); + if ( !static_cast<const SwRootFrame*>(pPage->GetUpper())->IsVirtPageNum() ) + return nPhyPage; + + //Search the nearest section using the virtual page number. + //Because searching backwards needs a lot of time we search specific using + //the dependencies. From the PageDescs we get the attributes and from the + //attributes we get the sections. + const SwPageFrame *pVirtPage = nullptr; + const SwFrame *pFrame = nullptr; + const SfxItemPool &rPool = pPage->GetFormat()->GetDoc()->GetAttrPool(); + for (const SfxPoolItem* pItem : rPool.GetItemSurrogates(RES_PAGEDESC)) + { + const SwFormatPageDesc *pDesc = dynamic_cast<const SwFormatPageDesc*>(pItem); + if ( !pDesc ) + continue; + + if ( pDesc->GetNumOffset() && pDesc->GetDefinedIn() ) + { + const sw::BroadcastingModify *pMod = pDesc->GetDefinedIn(); + SwVirtPageNumInfo aInfo( pPage ); + pMod->GetInfo( aInfo ); + if ( aInfo.GetPage() ) + { + if( !pVirtPage || aInfo.GetPage()->GetPhyPageNum() > pVirtPage->GetPhyPageNum() ) + { + pVirtPage = aInfo.GetPage(); + pFrame = aInfo.GetFrame(); + } + } + } + } + if ( pFrame ) + { + ::std::optional<sal_uInt16> oNumOffset = pFrame->GetPageDescItem().GetNumOffset(); + if (oNumOffset) + { + return nPhyPage - pFrame->GetPhyPageNum() + *oNumOffset; + } + else + { + return nPhyPage - pFrame->GetPhyPageNum(); + } + } + return nPhyPage; +} + +/** Determines and sets those cells which are enclosed by the selection. */ +bool SwRootFrame::MakeTableCursors( SwTableCursor& rTableCursor ) +{ + //Find Union-Rects and tables (Follows) of the selection. + OSL_ENSURE( rTableCursor.GetContentNode() && rTableCursor.GetContentNode( false ), + "Tabselection not on Cnt." ); + + bool bRet = false; + + // For new table models there's no need to ask the layout... + if( rTableCursor.NewTableSelection() ) + return true; + + Point aPtPt, aMkPt; + { + SwShellCursor* pShCursor = dynamic_cast<SwShellCursor*>(&rTableCursor); + + if( pShCursor ) + { + aPtPt = pShCursor->GetPtPos(); + aMkPt = pShCursor->GetMkPos(); + } + } + + // #151012# Made code robust here + const SwContentNode* pTmpStartNode = rTableCursor.GetContentNode(); + const SwContentNode* pTmpEndNode = rTableCursor.GetContentNode(false); + + std::pair<Point, bool> tmp(aPtPt, false); + const SwFrame *const pTmpStartFrame = pTmpStartNode ? pTmpStartNode->getLayoutFrame(this, nullptr, &tmp) : nullptr; + tmp.first = aMkPt; + const SwFrame *const pTmpEndFrame = pTmpEndNode ? pTmpEndNode->getLayoutFrame(this, nullptr, &tmp) : nullptr; + + const SwLayoutFrame* pStart = pTmpStartFrame ? pTmpStartFrame->GetUpper() : nullptr; + const SwLayoutFrame* pEnd = pTmpEndFrame ? pTmpEndFrame->GetUpper() : nullptr; + + OSL_ENSURE( pStart && pEnd, "MakeTableCursors: Good to have the code robust here!" ); + + /* #109590# Only change table boxes if the frames are + valid. Needed because otherwise the table cursor after moving + table cells by dnd resulted in an empty tables cursor. */ + if ( pStart && pEnd && pStart->isFrameAreaDefinitionValid() && pEnd->isFrameAreaDefinitionValid()) + { + SwSelUnions aUnions; + ::MakeSelUnions( aUnions, pStart, pEnd ); + + SwSelBoxes aNew; + + const bool bReadOnlyAvailable = rTableCursor.IsReadOnlyAvailable(); + + for (SwSelUnion & rUnion : aUnions) + { + const SwTabFrame *pTable = rUnion.GetTable(); + + // Skip any repeated headlines in the follow: + SwLayoutFrame* pRow = pTable->IsFollow() ? + pTable->GetFirstNonHeadlineRow() : + const_cast<SwLayoutFrame*>(static_cast<const SwLayoutFrame*>(pTable->Lower())); + + while ( pRow ) + { + if ( pRow->getFrameArea().Overlaps( rUnion.GetUnion() ) ) + { + const SwLayoutFrame *pCell = pRow->FirstCell(); + + while ( pCell && pRow->IsAnLower( pCell ) ) + { + OSL_ENSURE( pCell->IsCellFrame(), "Frame without cell" ); + if( IsFrameInTableSel( rUnion.GetUnion(), pCell ) && + (bReadOnlyAvailable || + !pCell->GetFormat()->GetProtect().IsContentProtected())) + { + SwTableBox* pInsBox = const_cast<SwTableBox*>( + static_cast<const SwCellFrame*>(pCell)->GetTabBox()); + aNew.insert( pInsBox ); + } + if ( pCell->GetNext() ) + { + pCell = static_cast<const SwLayoutFrame*>(pCell->GetNext()); + if ( pCell->Lower() && pCell->Lower()->IsRowFrame() ) + pCell = pCell->FirstCell(); + } + else + { + const SwLayoutFrame* pLastCell = pCell; + do + { + pCell = pCell->GetNextLayoutLeaf(); + } while ( pCell && pLastCell->IsAnLower( pCell ) ); + // For sections with columns + if( pCell && pCell->IsInTab() ) + { + while( !pCell->IsCellFrame() ) + { + pCell = pCell->GetUpper(); + OSL_ENSURE( pCell, "Where's my cell?" ); + } + } + } + } + } + pRow = static_cast<SwLayoutFrame*>(pRow->GetNext()); + } + } + + rTableCursor.ActualizeSelection( aNew ); + bRet = true; + } + + return bRet; +} + +static void Sub( SwRegionRects& rRegion, const SwRect& rRect ) +{ + if( rRect.Width() > 1 && rRect.Height() > 1 && + rRect.Overlaps( rRegion.GetOrigin() )) + rRegion -= rRect; +} + +static void Add( SwRegionRects& rRegion, const SwRect& rRect ) +{ + if( rRect.Width() > 1 && rRect.Height() > 1 ) + rRegion += rRect; +} + +/* + * The following situations can happen: + * 1. Start and end lie in one screen-row and in the same node + * -> one rectangle out of start and end; and we're okay + * 2. Start and end lie in one frame (therefore in the same node!) + * -> expand start to the right, end to the left and if more than two + * screen-rows are involved - calculate the in-between + * 3. Start and end lie in different frames + * -> expand start to the right until frame-end, calculate Rect + * expand end to the left until frame-start, calculate Rect + * and if more than two frames are involved add the PrtArea of all + * frames which lie in between + * + * Big reorganization because of the FlyFrame - those need to be locked out. + * Exceptions: - The Fly in which the selection took place (if it took place + * in a Fly) + * - The Flys which are underrun by the text + * - The Flys which are anchored to somewhere inside the selection. + * Functioning: First a SwRegion with a root gets initialized. + * Out of the region the inverted sections are cut out. The + * section gets compressed and finally inverted and thereby the + * inverted rectangles are available. + * In the end the Flys are cut out of the section. + */ +void SwRootFrame::CalcFrameRects(SwShellCursor &rCursor) +{ + SwPosition *pStartPos = rCursor.Start(), + *pEndPos = rCursor.End(); + + SwViewShell *pSh = GetCurrShell(); + + bool bIgnoreVisArea = true; + if (pSh) + bIgnoreVisArea = pSh->GetViewOptions()->IsPDFExport() || comphelper::LibreOfficeKit::isActive(); + + // #i12836# enhanced pdf + SwRegionRects aRegion( !bIgnoreVisArea ? + pSh->VisArea() : + getFrameArea() ); + if( !pStartPos->nNode.GetNode().IsContentNode() || + !pStartPos->nNode.GetNode().GetContentNode()->getLayoutFrame(this) || + ( pStartPos->nNode != pEndPos->nNode && + ( !pEndPos->nNode.GetNode().IsContentNode() || + !pEndPos->nNode.GetNode().GetContentNode()->getLayoutFrame(this) ) ) ) + { + return; + } + + DisableCallbackAction a(*this); // the GetCharRect below may format + + //First obtain the ContentFrames for the start and the end - those are needed + //anyway. + std::pair<Point, bool> tmp(rCursor.GetSttPos(), true); + SwContentFrame* pStartFrame = pStartPos->nNode.GetNode(). + GetContentNode()->getLayoutFrame(this, pStartPos, &tmp); + + tmp.first = rCursor.GetEndPos(); + SwContentFrame* pEndFrame = pEndPos->nNode.GetNode(). + GetContentNode()->getLayoutFrame(this, pEndPos, &tmp); + + assert(pStartFrame && pEndFrame && "No ContentFrames found."); + //tdf#119224 start and end are expected to exist for the scope of this function + SwFrameDeleteGuard aStartFrameGuard(pStartFrame), aEndFrameGuard(pEndFrame); + + //Do not subtract the FlyFrames in which selected Frames lie. + SwSortedObjs aSortObjs; + if ( pStartFrame->IsInFly() ) + { + const SwAnchoredObject* pObj = pStartFrame->FindFlyFrame(); + OSL_ENSURE( pObj, "No Start Object." ); + if (pObj) aSortObjs.Insert( *const_cast<SwAnchoredObject*>(pObj) ); + const SwAnchoredObject* pObj2 = pEndFrame->FindFlyFrame(); + OSL_ENSURE( pObj2, "SwRootFrame::CalcFrameRects(..) - FlyFrame missing - looks like an invalid selection" ); + if ( pObj2 != nullptr && pObj2 != pObj ) + { + aSortObjs.Insert( *const_cast<SwAnchoredObject*>(pObj2) ); + } + } + + // if a selection which is not allowed exists, we correct what is not + // allowed (header/footer/table-headline) for two pages. + do { // middle check loop + const SwLayoutFrame* pSttLFrame = pStartFrame->GetUpper(); + const SwFrameType cHdFtTableHd = SwFrameType::Header | SwFrameType::Footer | SwFrameType::Tab; + while( pSttLFrame && + ! (cHdFtTableHd & pSttLFrame->GetType() )) + pSttLFrame = pSttLFrame->GetUpper(); + if( !pSttLFrame ) + break; + const SwLayoutFrame* pEndLFrame = pEndFrame->GetUpper(); + while( pEndLFrame && + ! (cHdFtTableHd & pEndLFrame->GetType() )) + pEndLFrame = pEndLFrame->GetUpper(); + if( !pEndLFrame ) + break; + + OSL_ENSURE( pEndLFrame->GetType() == pSttLFrame->GetType(), + "Selection over different content" ); + switch( pSttLFrame->GetType() ) + { + case SwFrameType::Header: + case SwFrameType::Footer: + // On different pages? Then always on the start-page + if( pEndLFrame->FindPageFrame() != pSttLFrame->FindPageFrame() ) + { + // Set end- to the start-ContentFrame + if( pStartPos == rCursor.GetPoint() ) + pEndFrame = pStartFrame; + else + pStartFrame = pEndFrame; + } + break; + case SwFrameType::Tab: + // On different pages? Then check for table-headline + { + const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pSttLFrame); + if( ( pTabFrame->GetFollow() || + static_cast<const SwTabFrame*>(pEndLFrame)->GetFollow() ) && + pTabFrame->GetTable()->GetRowsToRepeat() > 0 && + pTabFrame->GetLower() != static_cast<const SwTabFrame*>(pEndLFrame)->GetLower() && + ( lcl_IsInRepeatedHeadline( pStartFrame ) || + lcl_IsInRepeatedHeadline( pEndFrame ) ) ) + { + // Set end- to the start-ContentFrame + if( pStartPos == rCursor.GetPoint() ) + pEndFrame = pStartFrame; + else + pStartFrame = pEndFrame; + } + } + break; + default: break; + } + } while( false ); + + SwCursorMoveState aTmpState( CursorMoveState::NONE ); + aTmpState.m_b2Lines = true; + aTmpState.m_bNoScroll = true; + aTmpState.m_nCursorBidiLevel = pStartFrame->IsRightToLeft() ? 1 : 0; + + //ContentRects to Start- and EndFrames. + SwRect aStRect, aEndRect; + pStartFrame->GetCharRect( aStRect, *pStartPos, &aTmpState ); + std::unique_ptr<Sw2LinesPos> pSt2Pos = std::move(aTmpState.m_p2Lines); + aTmpState.m_nCursorBidiLevel = pEndFrame->IsRightToLeft() ? 1 : 0; + + pEndFrame->GetCharRect( aEndRect, *pEndPos, &aTmpState ); + std::unique_ptr<Sw2LinesPos> pEnd2Pos = std::move(aTmpState.m_p2Lines); + + SwRect aStFrame ( pStartFrame->UnionFrame( true ) ); + aStFrame.Intersection( pStartFrame->GetPaintArea() ); + SwRect aEndFrame( pStartFrame == pEndFrame ? aStFrame : pEndFrame->UnionFrame( true ) ); + if( pStartFrame != pEndFrame ) + { + aEndFrame.Intersection( pEndFrame->GetPaintArea() ); + } + SwRectFnSet aRectFnSet(pStartFrame); + const bool bR2L = pStartFrame->IsRightToLeft(); + const bool bEndR2L = pEndFrame->IsRightToLeft(); + const bool bB2T = pStartFrame->IsVertLRBT(); + + // If there's no doubleline portion involved or start and end are both + // in the same doubleline portion, all works fine, but otherwise + // we need the following... + if( pSt2Pos != pEnd2Pos && ( !pSt2Pos || !pEnd2Pos || + pSt2Pos->aPortion != pEnd2Pos->aPortion ) ) + { + // If we have a start(end) position inside a doubleline portion + // the surrounded part of the doubleline portion is subtracted + // from the region and the aStRect(aEndRect) is set to the + // end(start) of the doubleline portion. + if( pSt2Pos ) + { + SwRect aTmp( aStRect ); + + // BiDi-Portions are swimming against the current. + const bool bPorR2L = ( MultiPortionType::BIDI == pSt2Pos->nMultiType ) ? + ! bR2L : + bR2L; + + if( MultiPortionType::BIDI == pSt2Pos->nMultiType && + aRectFnSet.GetWidth(pSt2Pos->aPortion2) ) + { + // nested bidi portion + tools::Long nRightAbs = aRectFnSet.GetRight(pSt2Pos->aPortion); + nRightAbs -= aRectFnSet.GetLeft(pSt2Pos->aPortion2); + tools::Long nLeftAbs = nRightAbs - aRectFnSet.GetWidth(pSt2Pos->aPortion2); + + aRectFnSet.SetRight( aTmp, nRightAbs ); + + if ( ! pEnd2Pos || pEnd2Pos->aPortion != pSt2Pos->aPortion ) + { + SwRect aTmp2( pSt2Pos->aPortion ); + aRectFnSet.SetRight( aTmp2, nLeftAbs ); + aTmp2.Intersection( aEndFrame ); + Sub( aRegion, aTmp2 ); + } + } + else + { + if( bPorR2L ) + aRectFnSet.SetLeft( aTmp, aRectFnSet.GetLeft(pSt2Pos->aPortion) ); + else + aRectFnSet.SetRight( aTmp, aRectFnSet.GetRight(pSt2Pos->aPortion) ); + } + + if( MultiPortionType::ROT_90 == pSt2Pos->nMultiType || + aRectFnSet.GetTop(pSt2Pos->aPortion) == + aRectFnSet.GetTop(aTmp) ) + { + aRectFnSet.SetTop( aTmp, aRectFnSet.GetTop(pSt2Pos->aLine) ); + } + + aTmp.Intersection( aStFrame ); + Sub( aRegion, aTmp ); + + SwTwips nTmp = aRectFnSet.GetBottom(pSt2Pos->aLine); + if( MultiPortionType::ROT_90 != pSt2Pos->nMultiType && + aRectFnSet.BottomDist( aStRect, nTmp ) > 0 ) + { + aRectFnSet.SetTop( aTmp, aRectFnSet.GetBottom(aTmp) ); + aRectFnSet.SetBottom( aTmp, nTmp ); + if( aRectFnSet.BottomDist( aStRect, aRectFnSet.GetBottom(pSt2Pos->aPortion) ) > 0 ) + { + if( bPorR2L ) + aRectFnSet.SetRight( aTmp, aRectFnSet.GetRight(pSt2Pos->aPortion) ); + else + aRectFnSet.SetLeft( aTmp, aRectFnSet.GetLeft(pSt2Pos->aPortion) ); + } + aTmp.Intersection( aStFrame ); + Sub( aRegion, aTmp ); + } + + aStRect = pSt2Pos->aLine; + aRectFnSet.SetLeft( aStRect, bR2L ? + aRectFnSet.GetLeft(pSt2Pos->aPortion) : + aRectFnSet.GetRight(pSt2Pos->aPortion) ); + aRectFnSet.SetWidth( aStRect, 1 ); + } + + if( pEnd2Pos ) + { + SwRectFnSet fnRectX(pEndFrame); + SwRect aTmp( aEndRect ); + + // BiDi-Portions are swimming against the current. + const bool bPorR2L = ( MultiPortionType::BIDI == pEnd2Pos->nMultiType ) ? + ! bEndR2L : + bEndR2L; + + if( MultiPortionType::BIDI == pEnd2Pos->nMultiType && + fnRectX.GetWidth(pEnd2Pos->aPortion2) ) + { + // nested bidi portion + tools::Long nRightAbs = fnRectX.GetRight(pEnd2Pos->aPortion); + nRightAbs = nRightAbs - fnRectX.GetLeft(pEnd2Pos->aPortion2); + tools::Long nLeftAbs = nRightAbs - fnRectX.GetWidth(pEnd2Pos->aPortion2); + + fnRectX.SetLeft( aTmp, nLeftAbs ); + + if ( ! pSt2Pos || pSt2Pos->aPortion != pEnd2Pos->aPortion ) + { + SwRect aTmp2( pEnd2Pos->aPortion ); + fnRectX.SetLeft( aTmp2, nRightAbs ); + aTmp2.Intersection( aEndFrame ); + Sub( aRegion, aTmp2 ); + } + } + else + { + if ( bPorR2L ) + fnRectX.SetRight( aTmp, fnRectX.GetRight(pEnd2Pos->aPortion) ); + else + fnRectX.SetLeft( aTmp, fnRectX.GetLeft(pEnd2Pos->aPortion) ); + } + + if( MultiPortionType::ROT_90 == pEnd2Pos->nMultiType || + fnRectX.GetBottom(pEnd2Pos->aPortion) == + fnRectX.GetBottom(aEndRect) ) + { + fnRectX.SetBottom( aTmp, fnRectX.GetBottom(pEnd2Pos->aLine) ); + } + + aTmp.Intersection( aEndFrame ); + Sub( aRegion, aTmp ); + + // The next statement means neither ruby nor rotate(90): + if( MultiPortionType::RUBY != pEnd2Pos->nMultiType && MultiPortionType::ROT_90 != pEnd2Pos->nMultiType ) + { + SwTwips nTmp = fnRectX.GetTop(pEnd2Pos->aLine); + if( fnRectX.GetTop(aEndRect) != nTmp ) + { + fnRectX.SetBottom( aTmp, fnRectX.GetTop(aTmp) ); + fnRectX.SetTop( aTmp, nTmp ); + if( fnRectX.GetTop(aEndRect) != + fnRectX.GetTop(pEnd2Pos->aPortion) ) + { + if( bPorR2L ) + fnRectX.SetLeft( aTmp, fnRectX.GetLeft(pEnd2Pos->aPortion) ); + else + fnRectX.SetRight( aTmp, fnRectX.GetRight(pEnd2Pos->aPortion) ); + } + aTmp.Intersection( aEndFrame ); + Sub( aRegion, aTmp ); + } + } + + aEndRect = pEnd2Pos->aLine; + fnRectX.SetLeft( aEndRect, bEndR2L ? + fnRectX.GetRight(pEnd2Pos->aPortion) : + fnRectX.GetLeft(pEnd2Pos->aPortion) ); + fnRectX.SetWidth( aEndRect, 1 ); + } + } + else if( pSt2Pos && pEnd2Pos && + MultiPortionType::BIDI == pSt2Pos->nMultiType && + MultiPortionType::BIDI == pEnd2Pos->nMultiType && + pSt2Pos->aPortion == pEnd2Pos->aPortion && + pSt2Pos->aPortion2 != pEnd2Pos->aPortion2 ) + { + // This is the ugly special case, where the selection starts and + // ends in the same bidi portion but one start or end is inside a + // nested bidi portion. + + if ( aRectFnSet.GetWidth(pSt2Pos->aPortion2) ) + { + SwRect aTmp( aStRect ); + tools::Long nRightAbs = aRectFnSet.GetRight(pSt2Pos->aPortion); + nRightAbs -= aRectFnSet.GetLeft(pSt2Pos->aPortion2); + tools::Long nLeftAbs = nRightAbs - aRectFnSet.GetWidth(pSt2Pos->aPortion2); + + aRectFnSet.SetRight( aTmp, nRightAbs ); + aTmp.Intersection( aStFrame ); + Sub( aRegion, aTmp ); + + aStRect = pSt2Pos->aLine; + aRectFnSet.SetLeft( aStRect, bR2L ? nRightAbs : nLeftAbs ); + aRectFnSet.SetWidth( aStRect, 1 ); + } + + SwRectFnSet fnRectX(pEndFrame); + if ( fnRectX.GetWidth(pEnd2Pos->aPortion2) ) + { + SwRect aTmp( aEndRect ); + tools::Long nRightAbs = fnRectX.GetRight(pEnd2Pos->aPortion); + nRightAbs -= fnRectX.GetLeft(pEnd2Pos->aPortion2); + tools::Long nLeftAbs = nRightAbs - fnRectX.GetWidth(pEnd2Pos->aPortion2); + + fnRectX.SetLeft( aTmp, nLeftAbs ); + aTmp.Intersection( aEndFrame ); + Sub( aRegion, aTmp ); + + aEndRect = pEnd2Pos->aLine; + fnRectX.SetLeft( aEndRect, bEndR2L ? nLeftAbs : nRightAbs ); + fnRectX.SetWidth( aEndRect, 1 ); + } + } + + // The charrect may be outside the paintarea (for cursortravelling) + // but the selection has to be restricted to the paintarea + if( aStRect.Left() < aStFrame.Left() ) + aStRect.Left( aStFrame.Left() ); + else if( aStRect.Left() > aStFrame.Right() ) + aStRect.Left( aStFrame.Right() ); + SwTwips nTmp = aStRect.Right(); + if( nTmp < aStFrame.Left() ) + aStRect.Right( aStFrame.Left() ); + else if( nTmp > aStFrame.Right() ) + aStRect.Right( aStFrame.Right() ); + if( aEndRect.Left() < aEndFrame.Left() ) + aEndRect.Left( aEndFrame.Left() ); + else if( aEndRect.Left() > aEndFrame.Right() ) + aEndRect.Left( aEndFrame.Right() ); + nTmp = aEndRect.Right(); + if( nTmp < aEndFrame.Left() ) + aEndRect.Right( aEndFrame.Left() ); + else if( nTmp > aEndFrame.Right() ) + aEndRect.Right( aEndFrame.Right() ); + + if( pStartFrame == pEndFrame ) + { + bool bSameRotatedOrBidi = pSt2Pos && pEnd2Pos && + ( MultiPortionType::BIDI == pSt2Pos->nMultiType || + MultiPortionType::ROT_270 == pSt2Pos->nMultiType || + MultiPortionType::ROT_90 == pSt2Pos->nMultiType ) && + pSt2Pos->aPortion == pEnd2Pos->aPortion; + //case 1: (Same frame and same row) + if( bSameRotatedOrBidi || + aRectFnSet.GetTop(aStRect) == aRectFnSet.GetTop(aEndRect) ) + { + Point aTmpSt( aStRect.Pos() ); + Point aTmpEnd( aEndRect.Right(), aEndRect.Bottom() ); + if (bSameRotatedOrBidi || bR2L || bB2T) + { + if( aTmpSt.Y() > aTmpEnd.Y() ) + { + tools::Long nTmpY = aTmpEnd.Y(); + aTmpEnd.setY( aTmpSt.Y() ); + aTmpSt.setY( nTmpY ); + } + if( aTmpSt.X() > aTmpEnd.X() ) + { + tools::Long nTmpX = aTmpEnd.X(); + aTmpEnd.setX( aTmpSt.X() ); + aTmpSt.setX( nTmpX ); + } + } + + SwRect aTmp( aTmpSt, aTmpEnd ); + // Bug 34888: If content is selected which doesn't take space + // away (i.e. PostIts, RefMarks, TOXMarks), then at + // least set the width of the Cursor. + if( 1 == aRectFnSet.GetWidth(aTmp) && + pStartPos->nContent.GetIndex() != + pEndPos->nContent.GetIndex() ) + { + OutputDevice* pOut = pSh->GetOut(); + tools::Long nCursorWidth = pOut->GetSettings().GetStyleSettings(). + GetCursorSize(); + aRectFnSet.SetWidth( aTmp, pOut->PixelToLogic( + Size( nCursorWidth, 0 ) ).Width() ); + } + aTmp.Intersection( aStFrame ); + Sub( aRegion, aTmp ); + } + //case 2: (Same frame, but not the same line) + else + { + SwTwips lLeft, lRight; + if( pSt2Pos && pEnd2Pos && pSt2Pos->aPortion == pEnd2Pos->aPortion ) + { + lLeft = aRectFnSet.GetLeft(pSt2Pos->aPortion); + lRight = aRectFnSet.GetRight(pSt2Pos->aPortion); + } + else + { + lLeft = aRectFnSet.GetLeft(pStartFrame->getFrameArea()) + + aRectFnSet.GetLeft(pStartFrame->getFramePrintArea()); + lRight = aRectFnSet.GetRight(aEndFrame); + } + if( lLeft < aRectFnSet.GetLeft(aStFrame) ) + lLeft = aRectFnSet.GetLeft(aStFrame); + if( lRight > aRectFnSet.GetRight(aStFrame) ) + lRight = aRectFnSet.GetRight(aStFrame); + SwRect aSubRect( aStRect ); + //First line + if( bR2L ) + aRectFnSet.SetLeft( aSubRect, lLeft ); + else + aRectFnSet.SetRight( aSubRect, lRight ); + Sub( aRegion, aSubRect ); + + //If there's at least a twips between start- and endline, + //so the whole area between will be added. + SwTwips aTmpBottom = aRectFnSet.GetBottom(aStRect); + SwTwips aTmpTop = aRectFnSet.GetTop(aEndRect); + if( aTmpBottom != aTmpTop ) + { + aRectFnSet.SetLeft( aSubRect, lLeft ); + aRectFnSet.SetRight( aSubRect, lRight ); + aRectFnSet.SetTop( aSubRect, aTmpBottom ); + aRectFnSet.SetBottom( aSubRect, aTmpTop ); + Sub( aRegion, aSubRect ); + } + //and the last line + aSubRect = aEndRect; + if( bR2L ) + aRectFnSet.SetRight( aSubRect, lRight ); + else + aRectFnSet.SetLeft( aSubRect, lLeft ); + Sub( aRegion, aSubRect ); + } + } + //case 3: (Different frames, maybe with other frames between) + else + { + //The startframe first... + SwRect aSubRect( aStRect ); + if( bR2L ) + aRectFnSet.SetLeft( aSubRect, aRectFnSet.GetLeft(aStFrame)); + else + aRectFnSet.SetRight( aSubRect, aRectFnSet.GetRight(aStFrame)); + Sub( aRegion, aSubRect ); + SwTwips nTmpTwips = aRectFnSet.GetBottom(aStRect); + if( aRectFnSet.GetBottom(aStFrame) != nTmpTwips ) + { + aSubRect = aStFrame; + aRectFnSet.SetTop( aSubRect, nTmpTwips ); + Sub( aRegion, aSubRect ); + } + + //Now the frames between, if there are any + bool const bBody = pStartFrame->IsInDocBody(); + const SwTableBox* pCellBox = pStartFrame->GetUpper()->IsCellFrame() ? + static_cast<const SwCellFrame*>(pStartFrame->GetUpper())->GetTabBox() : nullptr; + if (pSh->IsSelectAll()) + pCellBox = nullptr; + + const SwContentFrame *pContent = pStartFrame->GetNextContentFrame(); + SwRect aPrvRect; + + OSL_ENSURE( pContent, + "<SwRootFrame::CalcFrameRects(..)> - no content frame. This is a serious defect" ); + while ( pContent && pContent != pEndFrame ) + { + if ( pContent->IsInFly() ) + { + const SwAnchoredObject* pObj = pContent->FindFlyFrame(); + if (!aSortObjs.Contains(*pObj)) + { // is this even possible, assuming valid cursor pos.? + aSortObjs.Insert( *const_cast<SwAnchoredObject*>(pObj) ); + } + } + + // Consider only frames which have the same IsInDocBody value like pStartFrame + // If pStartFrame is inside a SwCellFrame, consider only frames which are inside the + // same cell frame (or its follow cell) + const SwTableBox* pTmpCellBox = pContent->GetUpper()->IsCellFrame() ? + static_cast<const SwCellFrame*>(pContent->GetUpper())->GetTabBox() : nullptr; + if (pSh->IsSelectAll()) + pTmpCellBox = nullptr; + if ( bBody == pContent->IsInDocBody() && + ( !pCellBox || pCellBox == pTmpCellBox ) ) + { + SwRect aCRect( pContent->UnionFrame( true ) ); + aCRect.Intersection( pContent->GetPaintArea() ); + if( aCRect.Overlaps( aRegion.GetOrigin() )) + { + SwRect aTmp( aPrvRect ); + aTmp.Union( aCRect ); + if ( (aPrvRect.Height() * aPrvRect.Width() + + aCRect.Height() * aCRect.Width()) == + (aTmp.Height() * aTmp.Width()) ) + { + aPrvRect.Union( aCRect ); + } + else + { + if ( aPrvRect.HasArea() ) + Sub( aRegion, aPrvRect ); + aPrvRect = aCRect; + } + } + } + pContent = pContent->GetNextContentFrame(); + OSL_ENSURE( pContent, + "<SwRootFrame::CalcFrameRects(..)> - no content frame. This is a serious defect!" ); + } + if ( aPrvRect.HasArea() ) + Sub( aRegion, aPrvRect ); + + //At least the endframe... + aRectFnSet.Refresh(pEndFrame); + nTmpTwips = aRectFnSet.GetTop(aEndRect); + if( aRectFnSet.GetTop(aEndFrame) != nTmpTwips ) + { + aSubRect = aEndFrame; + aRectFnSet.SetBottom( aSubRect, nTmpTwips ); + Sub( aRegion, aSubRect ); + } + aSubRect = aEndRect; + if( bEndR2L ) + aRectFnSet.SetRight(aSubRect, aRectFnSet.GetRight(aEndFrame)); + else + aRectFnSet.SetLeft( aSubRect, aRectFnSet.GetLeft(aEndFrame) ); + Sub( aRegion, aSubRect ); + } + + aRegion.Invert(); + pSt2Pos.reset(); + pEnd2Pos.reset(); + + // Cut out Flys during loop. We don't cut out Flys when: + // - the Lower is StartFrame/EndFrame (FlyInCnt and all other Flys which again + // sit in it) + // - if in the Z-order we have Flys above those in which the StartFrame is + // placed + // - if they are anchored to inside the selection and thus part of it + const SwPageFrame *pPage = pStartFrame->FindPageFrame(); + const SwPageFrame *pEndPage = pEndFrame->FindPageFrame(); + + while ( pPage ) + { + if ( pPage->GetSortedObjs() ) + { + const SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + const SwFlyFrame* pFly = pAnchoredObj->DynCastFlyFrame(); + if ( !pFly ) + continue; + const SwVirtFlyDrawObj* pObj = pFly->GetVirtDrawObj(); + const SwFormatSurround &rSur = pFly->GetFormat()->GetSurround(); + SwFormatAnchor const& rAnchor(pAnchoredObj->GetFrameFormat().GetAnchor()); + const SwPosition* anchoredAt = rAnchor.GetContentAnchor(); + bool inSelection = ( + anchoredAt != nullptr + && ( (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR + && IsDestroyFrameAnchoredAtChar(*anchoredAt, *pStartPos, *pEndPos)) + || (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA + && IsSelectFrameAnchoredAtPara(*anchoredAt, *pStartPos, *pEndPos)))); + if( inSelection ) + Add( aRegion, pFly->getFrameArea() ); + else if ( !pFly->IsAnLower( pStartFrame ) && + (rSur.GetSurround() != css::text::WrapTextMode_THROUGH && + !rSur.IsContour()) ) + { + if ( aSortObjs.Contains( *pAnchoredObj ) ) + continue; + + bool bSub = true; + const sal_uInt32 nPos = pObj->GetOrdNum(); + for ( size_t k = 0; bSub && k < aSortObjs.size(); ++k ) + { + assert( dynamic_cast< const SwFlyFrame *>( aSortObjs[k] ) && + "<SwRootFrame::CalcFrameRects(..)> - object in <aSortObjs> of unexpected type" ); + const SwFlyFrame* pTmp = static_cast<SwFlyFrame*>(aSortObjs[k]); + do + { + if ( nPos < pTmp->GetVirtDrawObj()->GetOrdNumDirect() ) + { + bSub = false; + } + else + { + pTmp = pTmp->GetAnchorFrame()->FindFlyFrame(); + } + } while ( bSub && pTmp ); + } + if ( bSub ) + Sub( aRegion, pFly->getFrameArea() ); + } + } + } + if ( pPage == pEndPage ) + break; + else + pPage = static_cast<const SwPageFrame*>(pPage->GetNext()); + } + + //Because it looks better, we close the DropCaps. + SwRect aDropRect; + if ( pStartFrame->IsTextFrame() ) + { + if ( static_cast<const SwTextFrame*>(pStartFrame)->GetDropRect( aDropRect ) ) + Sub( aRegion, aDropRect ); + } + if ( pEndFrame != pStartFrame && pEndFrame->IsTextFrame() ) + { + if ( static_cast<const SwTextFrame*>(pEndFrame)->GetDropRect( aDropRect ) ) + Sub( aRegion, aDropRect ); + } + + rCursor.assign( aRegion.begin(), aRegion.end() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/unusedf.cxx b/sw/source/core/layout/unusedf.cxx new file mode 100644 index 000000000..25de7617c --- /dev/null +++ b/sw/source/core/layout/unusedf.cxx @@ -0,0 +1,79 @@ +/* -*- 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 <rootfrm.hxx> +#include <cntfrm.hxx> +#include <flyfrm.hxx> +#include <osl/diagnose.h> + +void SwFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs * ) +{ + OSL_FAIL( "Format() of the base class called." ); +} + +void SwFrame::PaintSwFrame(vcl::RenderContext&, SwRect const&, SwPrintData const*const) const +{ + OSL_FAIL( "PaintSwFrame() of the base class called." ); +} + +bool SwContentFrame::WouldFit(SwTwips &, bool&, bool, bool) +{ + OSL_FAIL( "WouldFit of ContentFrame called." ); + return false; +} + +bool SwFrame::FillSelection( SwSelectionList& , const SwRect& ) const +{ + OSL_FAIL( "Don't call this function at the base class!" ); + return false; +} + +bool SwFrame::GetModelPositionForViewPoint( SwPosition *, Point&, SwCursorMoveState*, bool ) const +{ + OSL_FAIL( "GetModelPositionForViewPoint of the base class, hi!" ); + return false; +} + +#ifdef DBG_UTIL + +void SwRootFrame::Cut() +{ + OSL_FAIL( "Cut() of RootFrame called." ); +} + +void SwRootFrame::Paste( SwFrame *, SwFrame * ) +{ + OSL_FAIL( "Paste() of RootFrame called." ); +} + +void SwFlyFrame::Paste( SwFrame *, SwFrame * ) +{ + OSL_FAIL( "Paste() of FlyFrame called." ); +} + +#endif + +bool SwFrame::GetCharRect( SwRect&, const SwPosition&, + SwCursorMoveState*, bool ) const +{ + OSL_FAIL( "GetCharRect() of the base called." ); + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/virtoutp.cxx b/sw/source/core/layout/virtoutp.cxx new file mode 100644 index 000000000..dd044317d --- /dev/null +++ b/sw/source/core/layout/virtoutp.cxx @@ -0,0 +1,189 @@ +/* -*- 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 "virtoutp.hxx" +#include <viewopt.hxx> +#include <rootfrm.hxx> +#include <osl/diagnose.h> + +/* The SWLayVout class manages the virtual output devices. + * RootFrame has a static member of this class which is created in FrameInit + * and destroyed in FrameFinit. + * */ + +bool SwRootFrame::FlushVout() +{ + if (SwRootFrame::s_pVout->IsFlushable()) + { + SwRootFrame::s_pVout->Flush_(); + return true; + } + return false; +} + +bool SwRootFrame::HasSameRect( const SwRect& rRect ) +{ + if (SwRootFrame::s_pVout->IsFlushable()) + return ( rRect == SwRootFrame::s_pVout->GetOrgRect() ); + return false; +} + +/** method to set mapping/pixel offset for virtual output device + + OD 12.11.2002 #96272# - method implements two solutions for the mapping of + the virtual output device: + The old solution set the origin of the mapping mode, which will be used in + the virtual output device. This causes several paint errors, because of the + different roundings in the virtual output device and the original output device. + The new solution avoids the rounding differences between virtual and original + output device by setting a pixel offset at the virtual output device. + A define controls, which solution is used, in order to switch in escalation + back to old solution. + + @param _pOrgOutDev + input parameter - constant instance of the original output device, for which + the virtual output device is created. + + @param _pVirDev + input/output parameter - instance of the virtual output device. + + @param _rNewOrigin + input parameter - constant instance of the origin, which will be used in + the virtual output device +*/ +// define to control, if old or new solution for setting the mapping for +// a virtual output device is used. +static void SetMappingForVirtDev( const Point& _rNewOrigin, + const vcl::RenderContext* _pOrgOutDev, + vcl::RenderContext* _pVirDev ) +{ + // new solution: set pixel offset at virtual output device + Point aPixelOffset = _pOrgOutDev->LogicToPixel( _rNewOrigin ); + _pVirDev->SetPixelOffset( Size( -aPixelOffset.X(), -aPixelOffset.Y() ) ); +} + +// rSize must be pixel coordinates! +bool SwLayVout::DoesFit( const Size &rNew ) +{ + if( rNew.Height() > VIRTUALHEIGHT ) + return false; + if( rNew.IsEmpty() ) + return false; + if( rNew.Width() <= m_aSize.Width() ) + return true; + if( !m_pVirDev ) + { + m_pVirDev = VclPtr<VirtualDevice>::Create(); + m_pVirDev->SetLineColor(); + if( m_pOut ) + { + if( m_pVirDev->GetFillColor() != m_pOut->GetFillColor() ) + m_pVirDev->SetFillColor( m_pOut->GetFillColor() ); + } + } + + if( rNew.Width() > m_aSize.Width() ) + { + m_aSize.setWidth( rNew.Width() ); + if( !m_pVirDev->SetOutputSizePixel( m_aSize ) ) + { + m_pVirDev.disposeAndClear(); + m_aSize.setWidth( 0 ); + return false; + } + } + return true; +} + +/// change 2nd parameter <rRect> - no longer <const> +/// in order to return value of class member variable <aRect>, if virtual +/// output is used. +/// <aRect> contains the rectangle that represents the area the virtual +/// output device is used for and that is flushed at the end. +void SwLayVout::Enter( SwViewShell *pShell, SwRect &rRect, bool bOn ) +{ + Flush(); + +#ifdef DBG_UTIL + if( pShell->GetViewOptions()->IsTest3() ) + { + ++m_nCount; + return; + } +#endif + + bOn = bOn && !m_nCount && rRect.HasArea() && pShell->GetWin(); + ++m_nCount; + if( !bOn ) + return; + + m_pShell = pShell; + m_pOut = nullptr; + OutputDevice *pO = m_pShell->GetOut(); +// We don't cheat on printers or virtual output devices... + if( OUTDEV_WINDOW != pO->GetOutDevType() ) + return; + + m_pOut = pO; + Size aPixSz( m_pOut->PixelToLogic( Size( 1,1 )) ); + SwRect aTmp( rRect ); + aTmp.AddWidth(aPixSz.Width()/2 + 1 ); + aTmp.AddHeight(aPixSz.Height()/2 + 1 ); + tools::Rectangle aTmpRect( pO->LogicToPixel( aTmp.SVRect() ) ); + + OSL_ENSURE( !m_pShell->GetWin()->IsReallyVisible() || + aTmpRect.GetWidth() <= m_pShell->GetWin()->GetOutputSizePixel().Width() + 2, + "Paintwidth bigger than visarea?" ); + // Does the rectangle fit in our buffer? + if( !DoesFit( aTmpRect.GetSize() ) ) + { + m_pOut = nullptr; + return; + } + + m_aRect = SwRect( pO->PixelToLogic( aTmpRect ) ); + + SetOutDev( m_pShell, m_pVirDev ); + + if( m_pVirDev->GetFillColor() != m_pOut->GetFillColor() ) + m_pVirDev->SetFillColor( m_pOut->GetFillColor() ); + + MapMode aMapMode( m_pOut->GetMapMode() ); + // use method to set mapping + //aMapMode.SetOrigin( Point(0,0) - aRect.Pos() ); + ::SetMappingForVirtDev( m_aRect.Pos(), m_pOut, m_pVirDev ); + + if( aMapMode != m_pVirDev->GetMapMode() ) + m_pVirDev->SetMapMode( aMapMode ); + + // set value of parameter <rRect> + rRect = m_aRect; + +} + +void SwLayVout::Flush_() +{ + OSL_ENSURE( m_pVirDev, "SwLayVout::DrawOut: nothing left Toulouse" ); + m_pOut->DrawOutDev( m_aRect.Pos(), m_aRect.SSize(), + m_aRect.Pos(), m_aRect.SSize(), *m_pVirDev ); + SetOutDev( m_pShell, m_pOut ); + m_pOut = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/virtoutp.hxx b/sw/source/core/layout/virtoutp.hxx new file mode 100644 index 000000000..df24ec196 --- /dev/null +++ b/sw/source/core/layout/virtoutp.hxx @@ -0,0 +1,61 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SW_SOURCE_CORE_LAYOUT_VIRTOUTP_HXX +#define INCLUDED_SW_SOURCE_CORE_LAYOUT_VIRTOUTP_HXX + +#include <vcl/virdev.hxx> + +#include <swrect.hxx> + +class SwViewShell; +#define VIRTUALHEIGHT 64 + +class SwLayVout +{ + friend void FrameFinit(); //deletes Vout +private: + SwViewShell* m_pShell; + VclPtr<OutputDevice> m_pOut; + VclPtr<VirtualDevice> m_pVirDev; + SwRect m_aRect; + SwRect m_aOrgRect; + Size m_aSize; + sal_uInt16 m_nCount; + + bool DoesFit( const Size &rOut ); + +public: + SwLayVout() : m_pShell(nullptr), m_pOut(nullptr), m_pVirDev(nullptr), m_aSize(0, VIRTUALHEIGHT), m_nCount(0) {} + ~SwLayVout() { m_pVirDev.disposeAndClear(); } + + /// OD 27.09.2002 #103636# - change 2nd parameter <rRect> - no longer <const> + void Enter( SwViewShell *pShell, SwRect &rRect, bool bOn ); + void Leave() { --m_nCount; Flush(); } + + void SetOrgRect( SwRect const &rRect ) { m_aOrgRect = rRect; } + const SwRect& GetOrgRect() const { return m_aOrgRect; } + + bool IsFlushable() const { return bool(m_pOut); } + void Flush_(); + void Flush() { if( m_pOut ) Flush_(); } +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/wsfrm.cxx b/sw/source/core/layout/wsfrm.cxx new file mode 100644 index 000000000..1eadc9db6 --- /dev/null +++ b/sw/source/core/layout/wsfrm.cxx @@ -0,0 +1,4743 @@ +/* -*- 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 <config_wasm_strip.h> + +#include <hints.hxx> +#include <osl/diagnose.h> +#include <o3tl/safeint.hxx> +#include <svl/itemiter.hxx> +#include <editeng/brushitem.hxx> +#include <fmtornt.hxx> +#include <pagefrm.hxx> +#include <section.hxx> +#include <rootfrm.hxx> +#include <anchoreddrawobject.hxx> +#include <fmtanchr.hxx> +#include <viewimp.hxx> +#include <viewopt.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <redline.hxx> +#include <docsh.hxx> +#include <ftninfo.hxx> +#include <ftnidx.hxx> +#include <fmtclbl.hxx> +#include <fmtfsize.hxx> +#include <fmtpdsc.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <fmtsrnd.hxx> +#include <fmtcntnt.hxx> +#include <ftnfrm.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <flyfrm.hxx> +#include <sectfrm.hxx> +#include <fmtclds.hxx> +#include <txtfrm.hxx> +#include <bodyfrm.hxx> +#include <cellfrm.hxx> +#include <dbg_lay.hxx> +#include <editeng/frmdiritem.hxx> +#include <sortedobjs.hxx> +#include <frmatr.hxx> +#include <frmtool.hxx> +#include <layact.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> + +// RotateFlyFrame3 +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +using namespace ::com::sun::star; + +SwFrameAreaDefinition::SwFrameAreaDefinition() +: mbFrameAreaPositionValid(false), + mbFrameAreaSizeValid(false), + mbFramePrintAreaValid(false), + mnFrameId(SwFrameAreaDefinition::snLastFrameId++) +{ +} + +SwFrameAreaDefinition::~SwFrameAreaDefinition() +{ +} + +void SwFrameAreaDefinition::setFrameAreaPositionValid(bool bNew) +{ + if(mbFrameAreaPositionValid != bNew) + { + mbFrameAreaPositionValid = bNew; + } +} + +void SwFrameAreaDefinition::setFrameAreaSizeValid(bool bNew) +{ + if(mbFrameAreaSizeValid != bNew) + { + mbFrameAreaSizeValid = bNew; + } +} + +void SwFrameAreaDefinition::setFramePrintAreaValid(bool bNew) +{ + if(mbFramePrintAreaValid != bNew) + { + mbFramePrintAreaValid = bNew; + } +} + +SwFrameAreaDefinition::FrameAreaWriteAccess::~FrameAreaWriteAccess() +{ + if(mrTarget.maFrameArea != *this) + { + mrTarget.maFrameArea = *this; + } +} + +SwFrameAreaDefinition::FramePrintAreaWriteAccess::~FramePrintAreaWriteAccess() +{ + if(mrTarget.maFramePrintArea != *this) + { + mrTarget.maFramePrintArea = *this; + } +} + +// RotateFlyFrame3 - Support for Transformations +basegfx::B2DHomMatrix SwFrameAreaDefinition::getFrameAreaTransformation() const +{ + // default implementation hands out FrameArea (outer frame) + const SwRect& rFrameArea(getFrameArea()); + + return basegfx::utils::createScaleTranslateB2DHomMatrix( + rFrameArea.Width(), rFrameArea.Height(), + rFrameArea.Left(), rFrameArea.Top()); +} + +basegfx::B2DHomMatrix SwFrameAreaDefinition::getFramePrintAreaTransformation() const +{ + // default implementation hands out FramePrintArea (outer frame) + // Take into account that FramePrintArea is relative to FrameArea + const SwRect& rFrameArea(getFrameArea()); + const SwRect& rFramePrintArea(getFramePrintArea()); + + return basegfx::utils::createScaleTranslateB2DHomMatrix( + rFramePrintArea.Width(), rFramePrintArea.Height(), + rFramePrintArea.Left() + rFrameArea.Left(), + rFramePrintArea.Top() + rFrameArea.Top()); +} + +void SwFrameAreaDefinition::transform_translate(const Point& rOffset) +{ + // RotateFlyFrame3: default is to change the FrameArea, FramePrintArea needs no + // change since it is relative to FrameArea + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + if (aFrm.Pos().X() != FAR_AWAY) + { + aFrm.Pos().AdjustX(rOffset.X() ); + } + + if (aFrm.Pos().Y() != FAR_AWAY) + { + aFrm.Pos().AdjustY(rOffset.Y() ); + } +} + +SwRect TransformableSwFrame::getUntransformedFrameArea() const +{ + const basegfx::B2DHomMatrix& rSource(getLocalFrameAreaTransformation()); + + if(rSource.isIdentity()) + { + return mrSwFrameAreaDefinition.getFrameArea(); + } + else + { + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rSource.decompose(aScale, aTranslate, fRotate, fShearX); + const basegfx::B2DPoint aCenter(rSource * basegfx::B2DPoint(0.5, 0.5)); + const basegfx::B2DVector aAbsScale(basegfx::absolute(aScale)); + + return SwRect( + basegfx::fround(aCenter.getX() - (0.5 * aAbsScale.getX())), + basegfx::fround(aCenter.getY() - (0.5 * aAbsScale.getY())), + basegfx::fround(aAbsScale.getX()), + basegfx::fround(aAbsScale.getY())); + } +} + +SwRect TransformableSwFrame::getUntransformedFramePrintArea() const +{ + const basegfx::B2DHomMatrix& rSource(getLocalFramePrintAreaTransformation()); + + if(rSource.isIdentity()) + { + return mrSwFrameAreaDefinition.getFramePrintArea(); + } + else + { + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rSource.decompose(aScale, aTranslate, fRotate, fShearX); + const basegfx::B2DPoint aCenter(rSource * basegfx::B2DPoint(0.5, 0.5)); + const basegfx::B2DVector aAbsScale(basegfx::absolute(aScale)); + const SwRect aUntransformedFrameArea(getUntransformedFrameArea()); + + return SwRect( + basegfx::fround(aCenter.getX() - (0.5 * aAbsScale.getX())) - aUntransformedFrameArea.Left(), + basegfx::fround(aCenter.getY() - (0.5 * aAbsScale.getY())) - aUntransformedFrameArea.Top(), + basegfx::fround(aAbsScale.getX()), + basegfx::fround(aAbsScale.getY())); + } +} + +void TransformableSwFrame::createFrameAreaTransformations( + double fRotation, + const basegfx::B2DPoint& rCenter) +{ + const basegfx::B2DHomMatrix aRotateAroundCenter( + basegfx::utils::createRotateAroundPoint( + rCenter.getX(), + rCenter.getY(), + fRotation)); + const SwRect& rFrameArea(mrSwFrameAreaDefinition.getFrameArea()); + const SwRect& rFramePrintArea(mrSwFrameAreaDefinition.getFramePrintArea()); + + maFrameAreaTransformation = aRotateAroundCenter * basegfx::utils::createScaleTranslateB2DHomMatrix( + rFrameArea.Width(), rFrameArea.Height(), + rFrameArea.Left(), rFrameArea.Top()); + maFramePrintAreaTransformation = aRotateAroundCenter * basegfx::utils::createScaleTranslateB2DHomMatrix( + rFramePrintArea.Width(), rFramePrintArea.Height(), + rFramePrintArea.Left() + rFrameArea.Left(), rFramePrintArea.Top() + rFrameArea.Top()); +} + +void TransformableSwFrame::adaptFrameAreasToTransformations() +{ + if(!getLocalFrameAreaTransformation().isIdentity()) + { + basegfx::B2DRange aRangeFrameArea(0.0, 0.0, 1.0, 1.0); + aRangeFrameArea.transform(getLocalFrameAreaTransformation()); + const SwRect aNewFrm( + basegfx::fround(aRangeFrameArea.getMinX()), basegfx::fround(aRangeFrameArea.getMinY()), + basegfx::fround(aRangeFrameArea.getWidth()), basegfx::fround(aRangeFrameArea.getHeight())); + + if(aNewFrm != mrSwFrameAreaDefinition.getFrameArea()) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(mrSwFrameAreaDefinition); + aFrm.setSwRect(aNewFrm); + } + } + + if(getLocalFramePrintAreaTransformation().isIdentity()) + return; + + basegfx::B2DRange aRangeFramePrintArea(0.0, 0.0, 1.0, 1.0); + aRangeFramePrintArea.transform(getLocalFramePrintAreaTransformation()); + const SwRect aNewPrt( + basegfx::fround(aRangeFramePrintArea.getMinX()) - mrSwFrameAreaDefinition.getFrameArea().Left(), + basegfx::fround(aRangeFramePrintArea.getMinY()) - mrSwFrameAreaDefinition.getFrameArea().Top(), + basegfx::fround(aRangeFramePrintArea.getWidth()), + basegfx::fround(aRangeFramePrintArea.getHeight())); + + if(aNewPrt != mrSwFrameAreaDefinition.getFramePrintArea()) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(mrSwFrameAreaDefinition); + aPrt.setSwRect(aNewPrt); + } +} + +void TransformableSwFrame::restoreFrameAreas() +{ + // This can be done fully based on the Transformations currently + // set, so use this. Only needed when transformation *is* used + if(!getLocalFrameAreaTransformation().isIdentity()) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(mrSwFrameAreaDefinition); + aFrm.setSwRect(getUntransformedFrameArea()); + } + + if(!getLocalFramePrintAreaTransformation().isIdentity()) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(mrSwFrameAreaDefinition); + aPrt.setSwRect(getUntransformedFramePrintArea()); + } +} + +// transform by given B2DHomMatrix +void TransformableSwFrame::transform(const basegfx::B2DHomMatrix& aTransform) +{ + maFrameAreaTransformation *= aTransform; + maFramePrintAreaTransformation *= aTransform; +} + +SwFrame::SwFrame( sw::BroadcastingModify *pMod, SwFrame* pSib ) +: SwClient( pMod ), + mpRoot( pSib ? pSib->getRootFrame() : nullptr ), + mpUpper(nullptr), + mpNext(nullptr), + mpPrev(nullptr), + mnFrameType(SwFrameType::None), + mbInDtor(false), + mbInvalidR2L(true), + mbDerivedR2L(false), + mbRightToLeft(false), + mbInvalidVert(true), + mbDerivedVert(false), + mbVertical(false), + mbVertLR(false), + mbVertLRBT(false), + mbValidLineNum(false), + mbFixSize(false), + mbCompletePaint(true), + mbRetouche(false), + mbInfInvalid(true), + mbInfBody( false ), + mbInfTab ( false ), + mbInfFly ( false ), + mbInfFootnote ( false ), + mbInfSct ( false ), + mbColLocked(false), + m_isInDestroy(false), + mnForbidDelete(0) +{ + OSL_ENSURE( pMod, "No frame format given." ); +} + +const IDocumentDrawModelAccess& SwFrame::getIDocumentDrawModelAccess() +{ + return GetUpper()->GetFormat()->getIDocumentDrawModelAccess(); +} + +bool SwFrame::KnowsFormat( const SwFormat& rFormat ) const +{ + return GetRegisteredIn() == &rFormat; +} + +void SwFrame::RegisterToFormat( SwFormat& rFormat ) +{ + rFormat.Add( this ); +} + +void SwFrame::CheckDir( SvxFrameDirection nDir, bool bVert, bool bOnlyBiDi, bool bBrowse ) +{ + if( SvxFrameDirection::Environment == nDir || ( bVert && bOnlyBiDi ) ) + { + mbDerivedVert = true; + if( SvxFrameDirection::Environment == nDir ) + mbDerivedR2L = true; + SetDirFlags( bVert ); + } + else if( bVert ) + { + mbInvalidVert = false; + if( SvxFrameDirection::Horizontal_LR_TB == nDir || SvxFrameDirection::Horizontal_RL_TB == nDir + || bBrowse ) + { + mbVertical = false; + mbVertLR = false; + mbVertLRBT = false; + } + else + { + mbVertical = true; + if(SvxFrameDirection::Vertical_RL_TB == nDir) + { + mbVertLR = false; + mbVertLRBT = false; + } + else if(SvxFrameDirection::Vertical_LR_TB==nDir) + { + mbVertLR = true; + mbVertLRBT = false; + } + else if (nDir == SvxFrameDirection::Vertical_LR_BT) + { + mbVertLR = true; + mbVertLRBT = true; + } + } + } + else + { + mbInvalidR2L = false; + if( SvxFrameDirection::Horizontal_RL_TB == nDir ) + mbRightToLeft = true; + else + mbRightToLeft = false; + } +} + +void SwFrame::CheckDirection( bool bVert ) +{ + if( bVert ) + { + if( !IsHeaderFrame() && !IsFooterFrame() ) + { + mbDerivedVert = true; + SetDirFlags( bVert ); + } + } + else + { + mbDerivedR2L = true; + SetDirFlags( bVert ); + } +} + +void SwSectionFrame::CheckDirection( bool bVert ) +{ + const SwFrameFormat* pFormat = GetFormat(); + if( pFormat ) + { + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + CheckDir(pFormat->GetFormatAttr(RES_FRAMEDIR).GetValue(), + bVert, true, bBrowseMode ); + } + else + SwFrame::CheckDirection( bVert ); +} + +void SwFlyFrame::CheckDirection( bool bVert ) +{ + const SwFrameFormat* pFormat = GetFormat(); + if( pFormat ) + { + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + CheckDir(pFormat->GetFormatAttr(RES_FRAMEDIR).GetValue(), + bVert, false, bBrowseMode ); + } + else + SwFrame::CheckDirection( bVert ); +} + +void SwTabFrame::CheckDirection( bool bVert ) +{ + const SwFrameFormat* pFormat = GetFormat(); + if( pFormat ) + { + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + CheckDir(pFormat->GetFormatAttr(RES_FRAMEDIR).GetValue(), + bVert, true, bBrowseMode ); + } + else + SwFrame::CheckDirection( bVert ); +} + +void SwCellFrame::CheckDirection( bool bVert ) +{ + const SwFrameFormat* pFormat = GetFormat(); + const SvxFrameDirectionItem* pFrameDirItem; + // Check if the item is set, before actually + // using it. Otherwise the dynamic pool default is used, which may be set + // to LTR in case of OOo 1.0 documents. + if( pFormat && (pFrameDirItem = pFormat->GetItemIfSet( RES_FRAMEDIR ) ) ) + { + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + CheckDir( pFrameDirItem->GetValue(), bVert, false, bBrowseMode ); + } + else + SwFrame::CheckDirection( bVert ); +} + +void SwTextFrame::CheckDirection( bool bVert ) +{ + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + CheckDir(GetTextNodeForParaProps()->GetSwAttrSet().GetFrameDir().GetValue(), + bVert, true, bBrowseMode); +} + +void SwFrame::SwClientNotify(const SwModify&, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + SwFrameInvFlags eInvFlags = SwFrameInvFlags::NONE; + + if(pLegacy->m_pOld && pLegacy->m_pNew && RES_ATTRSET_CHG == pLegacy->m_pNew->Which()) + { + SfxItemIter aNIter(*static_cast<const SwAttrSetChg*>(pLegacy->m_pNew)->GetChgSet()); + SfxItemIter aOIter(*static_cast<const SwAttrSetChg*>(pLegacy->m_pOld)->GetChgSet()); + const SfxPoolItem* pNItem = aNIter.GetCurItem(); + const SfxPoolItem* pOItem = aOIter.GetCurItem(); + do + { + UpdateAttrFrame(pOItem, pNItem, eInvFlags); + pNItem = aNIter.NextItem(); + pOItem = aOIter.NextItem(); + } while (pNItem); + } + else + UpdateAttrFrame(pLegacy->m_pOld, pLegacy->m_pNew, eInvFlags); + + if(eInvFlags == SwFrameInvFlags::NONE) + return; + + SwPageFrame* pPage = FindPageFrame(); + InvalidatePage(pPage); + if(eInvFlags & SwFrameInvFlags::InvalidatePrt) + { + InvalidatePrt_(); + if(!GetPrev() && IsTabFrame() && IsInSct()) + FindSctFrame()->InvalidatePrt_(); + } + if(eInvFlags & SwFrameInvFlags::InvalidateSize) + InvalidateSize_(); + if(eInvFlags & SwFrameInvFlags::InvalidatePos) + InvalidatePos_(); + if(eInvFlags & SwFrameInvFlags::SetCompletePaint) + SetCompletePaint(); + SwFrame *pNxt; + if (eInvFlags & (SwFrameInvFlags::NextInvalidatePos | SwFrameInvFlags::NextSetCompletePaint) + && nullptr != (pNxt = GetNext())) + { + pNxt->InvalidatePage(pPage); + if(eInvFlags & SwFrameInvFlags::NextInvalidatePos) + pNxt->InvalidatePos_(); + if(eInvFlags & SwFrameInvFlags::NextSetCompletePaint) + pNxt->SetCompletePaint(); + } +} + +void SwFrame::UpdateAttrFrame( const SfxPoolItem *pOld, const SfxPoolItem *pNew, + SwFrameInvFlags &rInvFlags ) +{ + sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + switch( nWhich ) + { + case RES_BOX: + case RES_SHADOW: + Prepare( PrepareHint::FixSizeChanged ); + [[fallthrough]]; + case RES_LR_SPACE: + case RES_UL_SPACE: + case RES_RTL_GUTTER: + rInvFlags |= SwFrameInvFlags::InvalidatePrt | SwFrameInvFlags::InvalidateSize + | SwFrameInvFlags::SetCompletePaint; + break; + + case RES_HEADER_FOOTER_EAT_SPACING: + rInvFlags |= SwFrameInvFlags::InvalidatePrt | SwFrameInvFlags::InvalidateSize; + break; + + case RES_BACKGROUND: + case RES_BACKGROUND_FULL_SIZE: + rInvFlags |= SwFrameInvFlags::SetCompletePaint | SwFrameInvFlags::NextSetCompletePaint; + break; + + case RES_KEEP: + rInvFlags |= SwFrameInvFlags::InvalidatePos; + break; + + case RES_FRM_SIZE: + ReinitializeFrameSizeAttrFlags(); + rInvFlags |= SwFrameInvFlags::InvalidatePrt | SwFrameInvFlags::InvalidateSize + | SwFrameInvFlags::NextInvalidatePos; + break; + + case RES_FMT_CHG: + rInvFlags |= SwFrameInvFlags::InvalidatePrt | SwFrameInvFlags::InvalidateSize + | SwFrameInvFlags::InvalidatePos | SwFrameInvFlags::SetCompletePaint; + break; + + case RES_ROW_SPLIT: + { + if ( IsRowFrame() ) + { + bool bInFollowFlowRow = nullptr != IsInFollowFlowRow(); + if ( bInFollowFlowRow || nullptr != IsInSplitTableRow() ) + { + SwTabFrame* pTab = FindTabFrame(); + if ( bInFollowFlowRow ) + pTab = pTab->FindMaster(); + pTab->SetRemoveFollowFlowLinePending( true ); + } + } + break; + } + case RES_COL: + OSL_FAIL( "Columns for new FrameType?" ); + break; + + default: + // the new FillStyle has to do the same as previous RES_BACKGROUND + if(nWhich >= XATTR_FILL_FIRST && nWhich <= XATTR_FILL_LAST) + { + rInvFlags + |= SwFrameInvFlags::SetCompletePaint | SwFrameInvFlags::NextSetCompletePaint; + } + /* do Nothing */; + } +} + +bool SwFrame::Prepare( const PrepareHint, const void *, bool ) +{ + /* Do nothing */ + return false; +} + +/** + * Invalidates the page in which the Frame is currently placed. + * The page is invalidated depending on the type (Layout, Content, FlyFrame) + */ +void SwFrame::InvalidatePage( const SwPageFrame *pPage ) const +{ + if ( !pPage ) + { + pPage = FindPageFrame(); + // #i28701# - for at-character and as-character + // anchored Writer fly frames additionally invalidate also page frame + // its 'anchor character' is on. + if ( pPage && pPage->GetUpper() && IsFlyFrame() ) + { + const SwFlyFrame* pFlyFrame = static_cast<const SwFlyFrame*>(this); + if ( pFlyFrame->IsAutoPos() || pFlyFrame->IsFlyInContentFrame() ) + { + // #i33751#, #i34060# - method <GetPageFrameOfAnchor()> + // is replaced by method <FindPageFrameOfAnchor()>. It's return value + // have to be checked. + SwPageFrame* pPageFrameOfAnchor = + const_cast<SwFlyFrame*>(pFlyFrame)->FindPageFrameOfAnchor(); + if ( pPageFrameOfAnchor && pPageFrameOfAnchor != pPage ) + { + InvalidatePage( pPageFrameOfAnchor ); + } + } + } + } + + if ( !(pPage && pPage->GetUpper()) ) + return; + + if ( pPage->GetFormat()->GetDoc()->IsInDtor() ) + return; + + SwRootFrame *pRoot = const_cast<SwRootFrame*>(static_cast<const SwRootFrame*>(pPage->GetUpper())); + const SwFlyFrame *pFly = FindFlyFrame(); + if ( IsContentFrame() ) + { + if ( pRoot->IsTurboAllowed() ) + { + // If a ContentFrame wants to register for a second time, make it a TurboAction. + if ( !pRoot->GetTurbo() || this == pRoot->GetTurbo() ) + pRoot->SetTurbo( static_cast<const SwContentFrame*>(this) ); + else + { + pRoot->DisallowTurbo(); + //The page of the Turbo could be a different one then mine, + //therefore we have to invalidate it. + const SwFrame *pTmp = pRoot->GetTurbo(); + pRoot->ResetTurbo(); + pTmp->InvalidatePage(); + } + } + if ( !pRoot->GetTurbo() ) + { + if ( pFly ) + { if( !pFly->IsLocked() ) + { + if ( pFly->IsFlyInContentFrame() ) + { pPage->InvalidateFlyInCnt(); + pFly->GetAnchorFrame()->InvalidatePage(); + } + else + pPage->InvalidateFlyContent(); + } + } + else + pPage->InvalidateContent(); + } + } + else + { + pRoot->DisallowTurbo(); + if ( pFly ) + { + if ( !pFly->IsLocked() ) + { + if ( pFly->IsFlyInContentFrame() ) + { + pPage->InvalidateFlyInCnt(); + pFly->GetAnchorFrame()->InvalidatePage(); + } + else + pPage->InvalidateFlyLayout(); + } + } + else + pPage->InvalidateLayout(); + + if ( pRoot->GetTurbo() ) + { const SwFrame *pTmp = pRoot->GetTurbo(); + pRoot->ResetTurbo(); + pTmp->InvalidatePage(); + } + } + pRoot->SetIdleFlags(); + + if (!IsTextFrame()) + return; + + SwTextFrame const*const pText(static_cast<SwTextFrame const*>(this)); + if (sw::MergedPara const*const pMergedPara = pText->GetMergedPara()) + { + SwTextNode const* pNode(nullptr); + for (auto const& e : pMergedPara->extents) + { + if (e.pNode != pNode) + { + pNode = e.pNode; + if (pNode->IsGrammarCheckDirty()) + { + pRoot->SetNeedGrammarCheck( true ); + break; + } + } + } + } + else + { + if (pText->GetTextNodeFirst()->IsGrammarCheckDirty()) + { + pRoot->SetNeedGrammarCheck( true ); + } + } +} + +Size SwFrame::ChgSize( const Size& aNewSize ) +{ + mbFixSize = true; + const Size aOldSize( getFrameArea().SSize() ); + if ( aNewSize == aOldSize ) + return aOldSize; + + if ( GetUpper() ) + { + bool bNeighb = IsNeighbourFrame(); + SwRectFn fnRect = IsVertical() == bNeighb ? fnRectHori : ( IsVertLR() ? (IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ); + SwRect aNew( Point(0,0), aNewSize ); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + (aFrm.*fnRect->fnSetWidth)( (aNew.*fnRect->fnGetWidth)() ); + } + + tools::Long nNew = (aNew.*fnRect->fnGetHeight)(); + tools::Long nDiff = nNew - (getFrameArea().*fnRect->fnGetHeight)(); + + if( nDiff ) + { + if ( GetUpper()->IsFootnoteBossFrame() && HasFixSize() && + SwNeighbourAdjust::GrowShrink != + static_cast<SwFootnoteBossFrame*>(GetUpper())->NeighbourhoodAdjustment() ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + (aFrm.*fnRect->fnSetHeight)( nNew ); + } + + SwTwips nReal = static_cast<SwLayoutFrame*>(this)->AdjustNeighbourhood(nDiff); + + if ( nReal != nDiff ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + (aFrm.*fnRect->fnSetHeight)( nNew - nDiff + nReal ); + } + } + else + { + // OD 24.10.2002 #97265# - grow/shrink not for neighbour frames + // NOTE: neighbour frames are cell and column frames. + if ( !bNeighb ) + { + if ( nDiff > 0 ) + Grow( nDiff ); + else + Shrink( -nDiff ); + + if ( GetUpper() && (getFrameArea().*fnRect->fnGetHeight)() != nNew ) + { + GetUpper()->InvalidateSize_(); + } + } + + // Even if grow/shrink did not yet set the desired width, for + // example when called by ChgColumns to set the column width, we + // set the right width now. + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + (aFrm.*fnRect->fnSetHeight)( nNew ); + } + } + } + else + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.SSize( aNewSize ); + } + + if ( getFrameArea().SSize() != aOldSize ) + { + SwPageFrame *pPage = FindPageFrame(); + if ( GetNext() ) + { + GetNext()->InvalidatePos_(); + GetNext()->InvalidatePage( pPage ); + } + if( IsLayoutFrame() ) + { + if( IsRightToLeft() ) + InvalidatePos_(); + if( static_cast<SwLayoutFrame*>(this)->Lower() ) + static_cast<SwLayoutFrame*>(this)->Lower()->InvalidateSize_(); + } + InvalidatePrt_(); + InvalidateSize_(); + InvalidatePage( pPage ); + } + + return getFrameArea().SSize(); +} + +/** Insert SwFrame into existing structure. + * + * Insertion is done below the parent either before pBehind or + * at the end of the chain if pBehind is empty. + */ +void SwFrame::InsertBefore( SwLayoutFrame* pParent, SwFrame* pBehind ) +{ + OSL_ENSURE( pParent, "No parent for insert." ); + OSL_ENSURE( (!pBehind || pParent == pBehind->GetUpper()), + "Frame tree is inconsistent." ); + + mpUpper = pParent; + mpNext = pBehind; + if( pBehind ) + { //Insert before pBehind. + mpPrev = pBehind->mpPrev; + if( nullptr != mpPrev ) + mpPrev->mpNext = this; + else + mpUpper->m_pLower = this; + pBehind->mpPrev = this; + } + else + { //Insert at the end, or as first node in the sub tree + mpPrev = mpUpper->Lower(); + if ( mpPrev ) + { + while( mpPrev->mpNext ) + mpPrev = mpPrev->mpNext; + mpPrev->mpNext = this; + } + else + mpUpper->m_pLower = this; + } +} + +/** Insert SwFrame into existing structure. + * + * Insertion is done below the parent either after pBehind or + * at the beginning of the chain if pBehind is empty. + */ +void SwFrame::InsertBehind( SwLayoutFrame *pParent, SwFrame *pBefore ) +{ + OSL_ENSURE( pParent, "No Parent for Insert." ); + OSL_ENSURE( (!pBefore || pParent == pBefore->GetUpper()), + "Frame tree is inconsistent." ); + + mpUpper = pParent; + mpPrev = pBefore; + if ( pBefore ) + { + //Insert after pBefore + mpNext = pBefore->mpNext; + if ( nullptr != mpNext ) + mpNext->mpPrev = this; + pBefore->mpNext = this; + } + else + { + //Insert at the beginning of the chain + mpNext = pParent->Lower(); + if ( pParent->Lower() ) + pParent->Lower()->mpPrev = this; + pParent->m_pLower = this; + } +} + +/** Insert a chain of SwFrames into an existing structure + * + * Currently, this method is used to insert a SectionFrame (which may have some siblings) into an + * existing structure. If the third parameter is NULL, this method is (besides handling the + * siblings) equal to SwFrame::InsertBefore(..). + * + * If the third parameter is passed, the following happens: + * - this becomes mpNext of pParent + * - pSct becomes mpNext of the last one in the this-chain + * - pBehind is reconnected from pParent to pSct + * The purpose is: a SectionFrame (this) won't become a child of another SectionFrame (pParent), but + * pParent gets split into two siblings (pParent+pSect) and this is inserted between. + */ +bool SwFrame::InsertGroupBefore( SwFrame* pParent, SwFrame* pBehind, SwFrame* pSct ) +{ + OSL_ENSURE( pParent, "No parent for insert." ); + OSL_ENSURE( (!pBehind || ( (pBehind && (pParent == pBehind->GetUpper())) + || ((pParent->IsSctFrame() && pBehind->GetUpper()->IsColBodyFrame())) ) ), + "Frame tree inconsistent." ); + if( pSct ) + { + mpUpper = pParent->GetUpper(); + SwFrame *pLast = this; + while( pLast->GetNext() ) + { + pLast = pLast->GetNext(); + pLast->mpUpper = GetUpper(); + } + if( pBehind ) + { + pLast->mpNext = pSct; + pSct->mpPrev = pLast; + pSct->mpNext = pParent->GetNext(); + } + else + { + pLast->mpNext = pParent->GetNext(); + if( pLast->GetNext() ) + pLast->GetNext()->mpPrev = pLast; + } + pParent->mpNext = this; + mpPrev = pParent; + if( pSct->GetNext() ) + pSct->GetNext()->mpPrev = pSct; + while( pLast->GetNext() ) + { + pLast = pLast->GetNext(); + pLast->mpUpper = GetUpper(); + } + if( pBehind ) + { // Insert before pBehind. + if( pBehind->GetPrev() ) + pBehind->GetPrev()->mpNext = nullptr; + else + pBehind->GetUpper()->m_pLower = nullptr; + pBehind->mpPrev = nullptr; + SwLayoutFrame* pTmp = static_cast<SwLayoutFrame*>(pSct); + if( pTmp->Lower() ) + { + OSL_ENSURE( pTmp->Lower()->IsColumnFrame(), "InsertGrp: Used SectionFrame" ); + pTmp = static_cast<SwLayoutFrame*>(static_cast<SwLayoutFrame*>(pTmp->Lower())->Lower()); + OSL_ENSURE( pTmp, "InsertGrp: Missing ColBody" ); + } + pBehind->mpUpper = pTmp; + pBehind->GetUpper()->m_pLower = pBehind; + pLast = pBehind->GetNext(); + while ( pLast ) + { + pLast->mpUpper = pBehind->GetUpper(); + pLast = pLast->GetNext(); + } + } + else + { + OSL_ENSURE( pSct->IsSctFrame(), "InsertGroup: For SectionFrames only" ); + SwFrame::DestroyFrame(pSct); + return false; + } + } + else + { + mpUpper = static_cast<SwLayoutFrame*>(pParent); + SwFrame *pLast = this; + while( pLast->GetNext() ) + { + pLast = pLast->GetNext(); + pLast->mpUpper = GetUpper(); + } + pLast->mpNext = pBehind; + if( pBehind ) + { // Insert before pBehind. + mpPrev = pBehind->mpPrev; + if( nullptr != mpPrev ) + mpPrev->mpNext = this; + else + mpUpper->m_pLower = this; + pBehind->mpPrev = pLast; + } + else + { + //Insert at the end, or ... the first node in the subtree + mpPrev = mpUpper->Lower(); + if ( mpPrev ) + { + while( mpPrev->mpNext ) + mpPrev = mpPrev->mpNext; + mpPrev->mpNext = this; + } + else + mpUpper->m_pLower = this; + } + } + return true; +} + +void SwFrame::RemoveFromLayout() +{ + OSL_ENSURE( mpUpper, "Remove without upper?" ); + + if (mpPrev) + // one out of the middle is removed + mpPrev->mpNext = mpNext; + else if (mpUpper) + { // the first in a list is removed //TODO + OSL_ENSURE( mpUpper->m_pLower == this, "Layout is inconsistent." ); + mpUpper->m_pLower = mpNext; + } + if( mpNext ) + mpNext->mpPrev = mpPrev; + + // Remove link + mpNext = mpPrev = nullptr; + mpUpper = nullptr; +} + +void SwContentFrame::Paste( SwFrame* pParent, SwFrame* pSibling) +{ + OSL_ENSURE( pParent, "No parent for pasting." ); + OSL_ENSURE( pParent->IsLayoutFrame(), "Parent is ContentFrame." ); + OSL_ENSURE( pParent != this, "I'm the parent." ); + OSL_ENSURE( pSibling != this, "I'm my own neighbour." ); + OSL_ENSURE( !GetPrev() && !GetNext() && !GetUpper(), + "I'm still registered somewhere" ); + OSL_ENSURE( !pSibling || pSibling->IsFlowFrame(), + "<SwContentFrame::Paste(..)> - sibling not of expected type." ); + + //Insert in the tree. + InsertBefore( static_cast<SwLayoutFrame*>(pParent), pSibling ); + + SwPageFrame *pPage = FindPageFrame(); + InvalidateAll_(); + InvalidatePage( pPage ); + + if( pPage ) + { + pPage->InvalidateSpelling(); + pPage->InvalidateSmartTags(); + pPage->InvalidateAutoCompleteWords(); + pPage->InvalidateWordCount(); + } + + if ( GetNext() ) + { + SwFrame* pNxt = GetNext(); + pNxt->InvalidatePrt_(); + pNxt->InvalidatePos_(); + pNxt->InvalidatePage( pPage ); + if( pNxt->IsSctFrame() ) + pNxt = static_cast<SwSectionFrame*>(pNxt)->ContainsContent(); + if( pNxt && pNxt->IsTextFrame() && pNxt->IsInFootnote() ) + pNxt->Prepare( PrepareHint::FootnoteInvalidation, nullptr, false ); + } + + if ( getFrameArea().Height() ) + pParent->Grow( getFrameArea().Height() ); + + if ( getFrameArea().Width() != pParent->getFramePrintArea().Width() ) + Prepare( PrepareHint::FixSizeChanged ); + + if ( GetPrev() ) + { + if ( IsFollow() ) + //I'm a direct follower of my master now + static_cast<SwContentFrame*>(GetPrev())->Prepare( PrepareHint::FollowFollows ); + else + { + if ( GetPrev()->getFrameArea().Height() != + GetPrev()->getFramePrintArea().Height() + GetPrev()->getFramePrintArea().Top() ) + { + // Take the border into account? + GetPrev()->InvalidatePrt_(); + } + // OD 18.02.2003 #104989# - force complete paint of previous frame, + // if frame is inserted at the end of a section frame, in order to + // get subsidiary lines repainted for the section. + if ( pParent->IsSctFrame() && !GetNext() ) + { + // force complete paint of previous frame, if new inserted frame + // in the section is the last one. + GetPrev()->SetCompletePaint(); + } + GetPrev()->InvalidatePage( pPage ); + } + } + if ( IsInFootnote() ) + { + SwFrame* pFrame = GetIndPrev(); + if( pFrame && pFrame->IsSctFrame() ) + pFrame = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pFrame ) + pFrame->Prepare( PrepareHint::QuoVadis, nullptr, false ); + if( !GetNext() ) + { + pFrame = FindFootnoteFrame()->GetNext(); + if( pFrame && nullptr != (pFrame=static_cast<SwLayoutFrame*>(pFrame)->ContainsAny()) ) + pFrame->InvalidatePrt_(); + } + } + + InvalidateLineNum_(); + SwFrame *pNxt = FindNextCnt(); + if ( !pNxt ) + return; + + while ( pNxt && pNxt->IsInTab() ) + { + pNxt = pNxt->FindTabFrame(); + if( nullptr != pNxt ) + pNxt = pNxt->FindNextCnt(); + } + if ( pNxt ) + { + pNxt->InvalidateLineNum_(); + if ( pNxt != GetNext() ) + pNxt->InvalidatePage(); + } +} + +void SwContentFrame::Cut() +{ + OSL_ENSURE( GetUpper(), "Cut without Upper()." ); + + SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + SwFrame *pFrame = GetIndPrev(); + if( pFrame ) + { + if( pFrame->IsSctFrame() ) + pFrame = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if ( pFrame && pFrame->IsContentFrame() ) + { + pFrame->InvalidatePrt_(); + if( IsInFootnote() ) + pFrame->Prepare( PrepareHint::QuoVadis, nullptr, false ); + } + // #i26250# - invalidate printing area of previous + // table frame. + else if ( pFrame && pFrame->IsTabFrame() ) + { + pFrame->InvalidatePrt(); + } + } + + SwFrame *pNxt = FindNextCnt(); + if ( pNxt ) + { + while ( pNxt && pNxt->IsInTab() ) + { + pNxt = pNxt->FindTabFrame(); + if( nullptr != pNxt ) + pNxt = pNxt->FindNextCnt(); + } + if ( pNxt ) + { + pNxt->InvalidateLineNum_(); + if ( pNxt != GetNext() ) + pNxt->InvalidatePage(); + } + } + + SwTabFrame* pMasterTab(nullptr); + pFrame = GetIndNext(); + if( pFrame ) + { + // The old follow may have calculated a gap to the predecessor which + // now becomes obsolete or different as it becomes the first one itself + pFrame->InvalidatePrt_(); + pFrame->InvalidatePos_(); + pFrame->InvalidatePage( pPage ); + if( pFrame->IsSctFrame() ) + { + pFrame = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pFrame ) + { + pFrame->InvalidatePrt_(); + pFrame->InvalidatePos_(); + pFrame->InvalidatePage( pPage ); + } + } + if( pFrame && IsInFootnote() ) + pFrame->Prepare( PrepareHint::ErgoSum, nullptr, false ); + if( IsInSct() && !GetPrev() ) + { + SwSectionFrame* pSct = FindSctFrame(); + if( !pSct->IsFollow() ) + { + pSct->InvalidatePrt_(); + pSct->InvalidatePage( pPage ); + } + } + } + else + { + InvalidateNextPos(); + //Someone needs to do the retouching: predecessor or upper + pFrame = GetPrev(); + if ( nullptr != pFrame ) + { pFrame->SetRetouche(); + pFrame->Prepare( PrepareHint::WidowsOrphans ); + pFrame->InvalidatePos_(); + pFrame->InvalidatePage( pPage ); + } + // If I'm (was) the only ContentFrame in my upper, it has to do the + // retouching. Also, perhaps a page became empty. + else + { SwRootFrame *pRoot = getRootFrame(); + if ( pRoot ) + { + pRoot->SetSuperfluous(); + // RemoveSuperfluous can only remove empty pages at the end; + // find if there are pages without content following pPage + // and if so request a call to CheckPageDescs() + SwPageFrame const* pNext(pPage); + SwViewShell *pSh = pRoot->GetCurrShell(); + if (pSh && pSh->Imp()->IsAction()) + { + while ((pNext = static_cast<SwPageFrame const*>(pNext->GetNext()))) + { + if (!sw::IsPageFrameEmpty(*pNext) && !pNext->IsFootnotePage()) + { + pSh->Imp()->GetLayAction().SetCheckPageNum(pPage->GetPhyPageNum()); + break; + } + } + } + GetUpper()->SetCompletePaint(); + GetUpper()->InvalidatePage( pPage ); + } + if( IsInSct() ) + { + SwSectionFrame* pSct = FindSctFrame(); + if( !pSct->IsFollow() ) + { + pSct->InvalidatePrt_(); + pSct->InvalidatePage( pPage ); + } + } + // #i52253# The master table should take care + // of removing the follow flow line. + if ( IsInTab() ) + { + SwTabFrame* pThisTab = FindTabFrame(); + if (pThisTab && pThisTab->IsFollow()) + { + pMasterTab = pThisTab->FindMaster(); + } + } + } + } + //Remove first, then shrink the upper. + SwLayoutFrame *pUp = GetUpper(); + RemoveFromLayout(); + if ( !pUp ) + { + assert(!pMasterTab); + return; + } + + if (pMasterTab + && !pMasterTab->GetFollow()->GetFirstNonHeadlineRow()->ContainsContent()) + { // only do this if there's no content in other cells of the row! + pMasterTab->InvalidatePos_(); + pMasterTab->SetRemoveFollowFlowLinePending(true); + } + + SwSectionFrame *pSct = nullptr; + if ( !pUp->Lower() && + ( ( pUp->IsFootnoteFrame() && !pUp->IsColLocked() ) || + ( pUp->IsInSct() && + // #i29438# + // We have to consider the case that the section may be "empty" + // except from a temporary empty table frame. + // This can happen due to the new cell split feature. + !pUp->IsCellFrame() && + // #126020# - adjust check for empty section + // #130797# - correct fix #126020# + !(pSct = pUp->FindSctFrame())->ContainsContent() && + !pSct->ContainsAny( true ) ) ) ) + { + if ( pUp->GetUpper() ) + { + + // prevent delete of <ColLocked> footnote frame + if ( pUp->IsFootnoteFrame() && !pUp->IsColLocked()) + { + if( pUp->GetNext() && !pUp->GetPrev() ) + { + SwFrame* pTmp = static_cast<SwLayoutFrame*>(pUp->GetNext())->ContainsAny(); + if( pTmp ) + pTmp->InvalidatePrt_(); + } + if (!pUp->IsDeleteForbidden()) + { + pUp->Cut(); + SwFrame::DestroyFrame(pUp); + } + } + else + { + + if ( pSct->IsColLocked() || !pSct->IsInFootnote() || + ( pUp->IsFootnoteFrame() && pUp->IsColLocked() ) ) + { + pSct->DelEmpty( false ); + // If a locked section may not be deleted then at least + // its size became invalid after removing its last + // content. + pSct->InvalidateSize_(); + } + else + { + pSct->DelEmpty( true ); + SwFrame::DestroyFrame(pSct); + } + } + } + } + else + { + SwRectFnSet aRectFnSet(this); + tools::Long nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if( nFrameHeight ) + pUp->Shrink( nFrameHeight ); + } +} + +void SwLayoutFrame::Paste( SwFrame* pParent, SwFrame* pSibling) +{ + OSL_ENSURE( pParent, "No parent for pasting." ); + OSL_ENSURE( pParent->IsLayoutFrame(), "Parent is ContentFrame." ); + OSL_ENSURE( pParent != this, "I'm the parent oneself." ); + OSL_ENSURE( pSibling != this, "I'm my own neighbour." ); + OSL_ENSURE( !GetPrev() && !GetNext() && !GetUpper(), + "I'm still registered somewhere." ); + + //Insert in the tree. + InsertBefore( static_cast<SwLayoutFrame*>(pParent), pSibling ); + + // OD 24.10.2002 #103517# - correct setting of variable <fnRect> + // <fnRect> is used for the following: + // (1) To invalidate the frame's size, if its size, which has to be the + // same as its upper/parent, differs from its upper's/parent's. + // (2) To adjust/grow the frame's upper/parent, if it has a dimension in its + // size, which is not determined by its upper/parent. + // Which size is which depends on the frame type and the layout direction + // (vertical or horizontal). + // There are the following cases: + // (A) Header and footer frames both in vertical and in horizontal layout + // have to size the width to the upper/parent. A dimension in the height + // has to cause an adjustment/grow of the upper/parent. + // --> <fnRect> = fnRectHori + // (B) Cell and column frames in vertical layout, the width has to be the + // same as upper/parent and a dimension in height causes adjustment/grow + // of the upper/parent. + // --> <fnRect> = fnRectHori + // in horizontal layout the other way around + // --> <fnRect> = fnRectVert + // (C) Other frames in vertical layout, the height has to be the + // same as upper/parent and a dimension in width causes adjustment/grow + // of the upper/parent. + // --> <fnRect> = fnRectVert + // in horizontal layout the other way around + // --> <fnRect> = fnRectHori + //SwRectFn fnRect = IsVertical() ? fnRectHori : fnRectVert; + SwRectFn fnRect; + if ( IsHeaderFrame() || IsFooterFrame() ) + fnRect = fnRectHori; + else if ( IsCellFrame() || IsColumnFrame() ) + fnRect = GetUpper()->IsVertical() ? fnRectHori : ( GetUpper()->IsVertLR() ? (GetUpper()->IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ); + else + fnRect = GetUpper()->IsVertical() ? ( GetUpper()->IsVertLR() ? (GetUpper()->IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ) : fnRectHori; + + if( (getFrameArea().*fnRect->fnGetWidth)() != (pParent->getFramePrintArea().*fnRect->fnGetWidth)()) + InvalidateSize_(); + InvalidatePos_(); + const SwPageFrame *pPage = FindPageFrame(); + InvalidatePage( pPage ); + if( !IsColumnFrame() ) + { + SwFrame *pFrame = GetIndNext(); + if( nullptr != pFrame ) + { + pFrame->InvalidatePos_(); + if( IsInFootnote() ) + { + if( pFrame->IsSctFrame() ) + pFrame = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pFrame ) + pFrame->Prepare( PrepareHint::ErgoSum, nullptr, false ); + } + } + if ( IsInFootnote() && nullptr != ( pFrame = GetIndPrev() ) ) + { + if( pFrame->IsSctFrame() ) + pFrame = static_cast<SwSectionFrame*>(pFrame)->ContainsAny(); + if( pFrame ) + pFrame->Prepare( PrepareHint::QuoVadis, nullptr, false ); + } + } + + if( !(getFrameArea().*fnRect->fnGetHeight)() ) + return; + + // AdjustNeighbourhood is now also called in columns which are not + // placed inside a frame + SwNeighbourAdjust nAdjust = GetUpper()->IsFootnoteBossFrame() ? + static_cast<SwFootnoteBossFrame*>(GetUpper())->NeighbourhoodAdjustment() + : SwNeighbourAdjust::GrowShrink; + SwTwips nGrow = (getFrameArea().*fnRect->fnGetHeight)(); + if( SwNeighbourAdjust::OnlyAdjust == nAdjust ) + AdjustNeighbourhood( nGrow ); + else + { + SwTwips nReal = 0; + if( SwNeighbourAdjust::AdjustGrow == nAdjust ) + nReal = AdjustNeighbourhood( nGrow ); + if( nReal < nGrow ) + nReal += pParent->Grow( nGrow - nReal ); + if( SwNeighbourAdjust::GrowAdjust == nAdjust && nReal < nGrow ) + AdjustNeighbourhood( nGrow - nReal ); + } +} + +void SwLayoutFrame::Cut() +{ + if ( GetNext() ) + GetNext()->InvalidatePos_(); + + SwRectFnSet aRectFnSet(this); + SwTwips nShrink = aRectFnSet.GetHeight(getFrameArea()); + + // Remove first, then shrink upper. + SwLayoutFrame *pUp = GetUpper(); + + // AdjustNeighbourhood is now also called in columns which are not + // placed inside a frame. + + // Remove must not be called before an AdjustNeighbourhood, but it has to + // be called before the upper-shrink-call, if the upper-shrink takes care + // of its content. + if ( pUp && nShrink ) + { + if( pUp->IsFootnoteBossFrame() ) + { + SwNeighbourAdjust nAdjust= static_cast<SwFootnoteBossFrame*>(pUp)->NeighbourhoodAdjustment(); + if( SwNeighbourAdjust::OnlyAdjust == nAdjust ) + AdjustNeighbourhood( -nShrink ); + else + { + SwTwips nReal = 0; + if( SwNeighbourAdjust::AdjustGrow == nAdjust ) + nReal = -AdjustNeighbourhood( -nShrink ); + if( nReal < nShrink ) + { + const SwTwips nOldHeight = aRectFnSet.GetHeight(getFrameArea()); + + // seems as if this needs to be forwarded to the SwFrame already here, + // changing to zero seems temporary anyways + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, 0 ); + } + + nReal += pUp->Shrink( nShrink - nReal ); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nOldHeight ); + } + } + + if( SwNeighbourAdjust::GrowAdjust == nAdjust && nReal < nShrink ) + AdjustNeighbourhood( nReal - nShrink ); + } + RemoveFromLayout(); + } + else + { + RemoveFromLayout(); + pUp->Shrink( nShrink ); + } + } + else + RemoveFromLayout(); + + if( pUp && !pUp->Lower() ) + { + pUp->SetCompletePaint(); + pUp->InvalidatePage(); + } +} + +SwTwips SwFrame::Grow( SwTwips nDist, bool bTst, bool bInfo ) +{ + OSL_ENSURE( nDist >= 0, "Negative growth?" ); + + PROTOCOL_ENTER( this, bTst ? PROT::GrowTest : PROT::Grow, DbgAction::NONE, &nDist ) + + if ( nDist ) + { + SwRectFnSet aRectFnSet(this); + + SwTwips nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + if( nPrtHeight > 0 && nDist > (LONG_MAX - nPrtHeight) ) + nDist = LONG_MAX - nPrtHeight; + + if ( IsFlyFrame() ) + return static_cast<SwFlyFrame*>(this)->Grow_( nDist, bTst ); + else if( IsSctFrame() ) + return static_cast<SwSectionFrame*>(this)->Grow_( nDist, bTst ); + else + { + if (IsCellFrame()) + { + const SwCellFrame* pThisCell = static_cast<const SwCellFrame*>(this); + const SwTabFrame* pTab = FindTabFrame(); + + // NEW TABLES + if ( pTab->IsVertical() != IsVertical() || + pThisCell->GetLayoutRowSpan() < 1 ) + return 0; + } + const SwTwips nReal = GrowFrame( nDist, bTst, bInfo ); + if( !bTst ) + { + nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, nPrtHeight + ( IsContentFrame() ? nDist : nReal ) ); + } + return nReal; + } + } + return 0; +} + +SwTwips SwFrame::Shrink( SwTwips nDist, bool bTst, bool bInfo ) +{ + OSL_ENSURE( nDist >= 0, "Negative reduction?" ); + + PROTOCOL_ENTER( this, bTst ? PROT::ShrinkTest : PROT::Shrink, DbgAction::NONE, &nDist ) + + if ( nDist ) + { + if ( IsFlyFrame() ) + return static_cast<SwFlyFrame*>(this)->Shrink_( nDist, bTst ); + else if( IsSctFrame() ) + return static_cast<SwSectionFrame*>(this)->Shrink_( nDist, bTst ); + else + { + if (IsCellFrame()) + { + const SwCellFrame* pThisCell = static_cast<const SwCellFrame*>(this); + const SwTabFrame* pTab = FindTabFrame(); + + // NEW TABLES + if ( (pTab && pTab->IsVertical() != IsVertical()) || + pThisCell->GetLayoutRowSpan() < 1 ) + return 0; + } + SwRectFnSet aRectFnSet(this); + SwTwips nReal = aRectFnSet.GetHeight(getFrameArea()); + ShrinkFrame( nDist, bTst, bInfo ); + nReal -= aRectFnSet.GetHeight(getFrameArea()); + if( !bTst ) + { + const SwTwips nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, nPrtHeight - ( IsContentFrame() ? nDist : nReal ) ); + } + return nReal; + } + } + return 0; +} + +/** Adjust surrounding neighbourhood after insertion + * + * A Frame needs "normalization" if it is directly placed below a footnote boss (page/column) and its + * size changes. There is always a frame that takes the maximum possible space (the frame that + * contains the Body text) and zero or more frames which only take the space needed (header/footer + * area, footnote container). If one of these frames changes, the body-text-frame has to grow or + * shrink accordingly, even though it's fixed. + * + * !! Is it possible to do this in a generic way and not restrict it to the page and a distinct + * frame which takes the maximum space (controlled using the FrameSize attribute)? + * Problems: + * - What if multiple frames taking the maximum space are placed next to each other? + * - How is the maximum space calculated? + * - How small can those frames become? + * + * In any case, only a certain amount of space is allowed, so we never go below a minimum value for + * the height of the body. + * + * @param nDiff the value around which the space has to be allocated + */ +SwTwips SwFrame::AdjustNeighbourhood( SwTwips nDiff, bool bTst ) +{ + PROTOCOL_ENTER( this, PROT::AdjustN, DbgAction::NONE, &nDiff ); + + if ( !nDiff || !GetUpper()->IsFootnoteBossFrame() ) // only inside pages/columns + return 0; + + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowse = pSh && pSh->GetViewOptions()->getBrowseMode(); + + //The (Page-)Body only changes in BrowseMode, but only if it does not + //contain columns. + if ( IsPageBodyFrame() && (!bBrowse || + (static_cast<SwLayoutFrame*>(this)->Lower() && + static_cast<SwLayoutFrame*>(this)->Lower()->IsColumnFrame())) ) + return 0; + + //In BrowseView mode the PageFrame can handle some of the requests. + tools::Long nBrowseAdd = 0; + if ( bBrowse && GetUpper()->IsPageFrame() ) // only (Page-)BodyFrames + { + SwViewShell *pViewShell = getRootFrame()->GetCurrShell(); + SwLayoutFrame *pUp = GetUpper(); + tools::Long nChg; + const tools::Long nUpPrtBottom = pUp->getFrameArea().Height() - + pUp->getFramePrintArea().Height() - pUp->getFramePrintArea().Top(); + SwRect aInva( pUp->getFrameArea() ); + if ( pViewShell ) + { + aInva.Pos().setX( pViewShell->VisArea().Left() ); + aInva.Width( pViewShell->VisArea().Width() ); + } + if ( nDiff > 0 ) + { + nChg = BROWSE_HEIGHT - pUp->getFrameArea().Height(); + nChg = std::min( nDiff, SwTwips(nChg) ); + + if ( !IsBodyFrame() ) + { + SetCompletePaint(); + if ( !pViewShell || pViewShell->VisArea().Height() >= pUp->getFrameArea().Height() ) + { + //First minimize Body, it will grow again later. + SwFrame *pBody = static_cast<SwFootnoteBossFrame*>(pUp)->FindBodyCont(); + const tools::Long nTmp = nChg - pBody->getFramePrintArea().Height(); + if ( !bTst ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pBody); + aFrm.Height(std::max( tools::Long(0), aFrm.Height() - nChg )); + } + + pBody->InvalidatePrt_(); + pBody->InvalidateSize_(); + if ( pBody->GetNext() ) + pBody->GetNext()->InvalidatePos_(); + if ( !IsHeaderFrame() ) + pBody->SetCompletePaint(); + } + nChg = nTmp <= 0 ? 0 : nTmp; + } + } + + const tools::Long nTmp = nUpPrtBottom + 20; + aInva.Top( aInva.Bottom() - nTmp ); + aInva.Height( nChg + nTmp ); + } + else + { + //The page can shrink to 0. The first page keeps the same size like + //VisArea. + nChg = nDiff; + tools::Long nInvaAdd = 0; + if ( pViewShell && !pUp->GetPrev() && + pUp->getFrameArea().Height() + nDiff < pViewShell->VisArea().Height() ) + { + // This means that we have to invalidate adequately. + nChg = pViewShell->VisArea().Height() - pUp->getFrameArea().Height(); + nInvaAdd = -(nDiff - nChg); + } + + //Invalidate including bottom border. + tools::Long nBorder = nUpPrtBottom + 20; + nBorder -= nChg; + aInva.Top( aInva.Bottom() - (nBorder+nInvaAdd) ); + if ( !IsBodyFrame() ) + { + SetCompletePaint(); + if ( !IsHeaderFrame() ) + static_cast<SwFootnoteBossFrame*>(pUp)->FindBodyCont()->SetCompletePaint(); + } + //Invalidate the page because of the frames. Thereby the page becomes + //the right size again if a frame didn't fit. This only works + //randomly for paragraph bound frames otherwise (NotifyFlys). + pUp->InvalidateSize(); + } + if ( !bTst ) + { + //Independent from nChg + if ( pViewShell && aInva.HasArea() && pUp->GetUpper() ) + pViewShell->InvalidateWindows( aInva ); + } + if ( !bTst && nChg ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pUp); + aFrm.AddHeight(nChg ); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pUp); + aPrt.AddHeight(nChg ); + } + + if ( pViewShell ) + pViewShell->Imp()->SetFirstVisPageInvalid(); + + if ( GetNext() ) + GetNext()->InvalidatePos_(); + + //Trigger a repaint if necessary. + std::unique_ptr<SvxBrushItem> aBack(pUp->GetFormat()->makeBackgroundBrushItem()); + const SvxGraphicPosition ePos = aBack->GetGraphicPos(); + if ( ePos != GPOS_NONE && ePos != GPOS_TILED ) + pViewShell->InvalidateWindows( pUp->getFrameArea() ); + + if ( pUp->GetUpper() ) + { + if ( pUp->GetNext() ) + pUp->GetNext()->InvalidatePos(); + + //Sad but true: during notify on ViewImp a Calc on the page and + //its Lower may be called. The values should not be changed + //because the caller takes care of the adjustment of Frame and + //Prt. + const tools::Long nOldFrameHeight = getFrameArea().Height(); + const tools::Long nOldPrtHeight = getFramePrintArea().Height(); + const bool bOldComplete = IsCompletePaint(); + + if ( IsBodyFrame() ) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( nOldFrameHeight ); + } + + if ( pUp->GetUpper() ) + { + static_cast<SwRootFrame*>(pUp->GetUpper())->CheckViewLayout( nullptr, nullptr ); + } + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( nOldFrameHeight ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( nOldPrtHeight ); + + mbCompletePaint = bOldComplete; + } + if ( !IsBodyFrame() ) + pUp->InvalidateSize_(); + InvalidatePage( static_cast<SwPageFrame*>(pUp) ); + } + nDiff -= nChg; + if ( !nDiff ) + return nChg; + else + nBrowseAdd = nChg; + } + + const SwFootnoteBossFrame *pBoss = static_cast<SwFootnoteBossFrame*>(GetUpper()); + + SwTwips nReal = 0, + nAdd = 0; + SwFrame *pFrame = nullptr; + SwRectFnSet aRectFnSet(this); + + if( IsBodyFrame() ) + { + if( IsInSct() ) + { + SwSectionFrame *pSect = FindSctFrame(); + if( nDiff > 0 && pSect->IsEndnAtEnd() && GetNext() && + GetNext()->IsFootnoteContFrame() ) + { + SwFootnoteContFrame* pCont = static_cast<SwFootnoteContFrame*>(GetNext()); + SwTwips nMinH = 0; + SwFootnoteFrame* pFootnote = static_cast<SwFootnoteFrame*>(pCont->Lower()); + bool bFootnote = false; + while( pFootnote ) + { + if( !pFootnote->GetAttr()->GetFootnote().IsEndNote() ) + { + nMinH += aRectFnSet.GetHeight(pFootnote->getFrameArea()); + bFootnote = true; + } + pFootnote = static_cast<SwFootnoteFrame*>(pFootnote->GetNext()); + } + if( bFootnote ) + nMinH += aRectFnSet.GetTop(pCont->getFramePrintArea()); + nReal = aRectFnSet.GetHeight(pCont->getFrameArea()) - nMinH; + if( nReal > nDiff ) + nReal = nDiff; + if( nReal > 0 ) + pFrame = GetNext(); + else + nReal = 0; + } + if( !bTst && !pSect->IsColLocked() ) + pSect->InvalidateSize(); + } + if( !pFrame ) + return nBrowseAdd; + } + else + { + const bool bFootnotePage = pBoss->IsPageFrame() && static_cast<const SwPageFrame*>(pBoss)->IsFootnotePage(); + if ( bFootnotePage && !IsFootnoteContFrame() ) + pFrame = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoss->FindFootnoteCont())); + if ( !pFrame ) + pFrame = const_cast<SwFrame*>(static_cast<SwFrame const *>(pBoss->FindBodyCont())); + + if ( !pFrame ) + return 0; + + //If not one is found, everything else is solved. + nReal = aRectFnSet.GetHeight(pFrame->getFrameArea()); + if( nReal > nDiff ) + nReal = nDiff; + if( !bFootnotePage ) + { + //Respect the minimal boundary! + if( nReal ) + { + const SwTwips nMax = pBoss->GetVarSpace(); + if ( nReal > nMax ) + nReal = nMax; + } + if( !IsFootnoteContFrame() && nDiff > nReal && + pFrame->GetNext() && pFrame->GetNext()->IsFootnoteContFrame() + && ( pFrame->GetNext()->IsVertical() == IsVertical() ) + ) + { + //If the Body doesn't return enough, we look for a footnote, if + //there is one, we steal there accordingly. + const SwTwips nAddMax = aRectFnSet.GetHeight(pFrame->GetNext()->getFrameArea()); + nAdd = nDiff - nReal; + if ( nAdd > nAddMax ) + nAdd = nAddMax; + if ( !bTst ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame->GetNext()); + aRectFnSet.SetHeight(aFrm, nAddMax-nAdd); + + if( aRectFnSet.IsVert() && !aRectFnSet.IsVertL2R() ) + { + aFrm.Pos().AdjustX(nAdd ); + } + } + + pFrame->GetNext()->InvalidatePrt(); + + if ( pFrame->GetNext()->GetNext() ) + { + pFrame->GetNext()->GetNext()->InvalidatePos_(); + } + } + } + } + } + + if ( !bTst && nReal ) + { + SwTwips nTmp = aRectFnSet.GetHeight(pFrame->getFrameArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame); + aRectFnSet.SetHeight( aFrm, nTmp - nReal ); + + if( aRectFnSet.IsVert() && !aRectFnSet.IsVertL2R() ) + { + aFrm.Pos().AdjustX(nReal ); + } + } + + pFrame->InvalidatePrt(); + + if ( pFrame->GetNext() ) + pFrame->GetNext()->InvalidatePos_(); + + if( nReal < 0 && pFrame->IsInSct() ) + { + SwLayoutFrame* pUp = pFrame->GetUpper(); + if( pUp && nullptr != ( pUp = pUp->GetUpper() ) && pUp->IsSctFrame() && + !pUp->IsColLocked() ) + pUp->InvalidateSize(); + } + if( ( IsHeaderFrame() || IsFooterFrame() ) && pBoss->GetDrawObjs() ) + { + const SwSortedObjs &rObjs = *pBoss->GetDrawObjs(); + OSL_ENSURE( pBoss->IsPageFrame(), "Header/Footer out of page?" ); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + OSL_ENSURE( !pFly->IsFlyInContentFrame(), "FlyInCnt at Page?" ); + const SwFormatVertOrient &rVert = + pFly->GetFormat()->GetVertOrient(); + // When do we have to invalidate? + // If a frame is aligned on a PageTextArea and the header + // changes a TOP, MIDDLE or NONE aligned frame needs to + // recalculate it's position; if the footer changes a BOTTOM + // or MIDDLE aligned frame needs to recalculate it's + // position. + if( ( rVert.GetRelationOrient() == text::RelOrientation::PRINT_AREA || + rVert.GetRelationOrient() == text::RelOrientation::PAGE_PRINT_AREA ) && + ((IsHeaderFrame() && rVert.GetVertOrient()!=text::VertOrientation::BOTTOM) || + (IsFooterFrame() && rVert.GetVertOrient()!=text::VertOrientation::NONE && + rVert.GetVertOrient() != text::VertOrientation::TOP)) ) + { + pFly->InvalidatePos_(); + pFly->Invalidate_(); + } + } + } + } + } + return (nBrowseAdd + nReal + nAdd); +} + +/** method to perform additional actions on an invalidation (2004-05-19 #i28701#) */ +void SwFrame::ActionOnInvalidation( const InvalidationType ) +{ + // default behaviour is to perform no additional action +} + +/** method to determine, if an invalidation is allowed (2004-05-19 #i28701#) */ +bool SwFrame::InvalidationAllowed( const InvalidationType ) const +{ + // default behaviour is to allow invalidation + return true; +} + +void SwFrame::ImplInvalidateSize() +{ + if ( InvalidationAllowed( INVALID_SIZE ) ) + { + setFrameAreaSizeValid(false); + + if ( IsFlyFrame() ) + static_cast<SwFlyFrame*>(this)->Invalidate_(); + else + InvalidatePage(); + + // OD 2004-05-19 #i28701# + ActionOnInvalidation( INVALID_SIZE ); + } +} + +void SwFrame::ImplInvalidatePrt() +{ + if ( InvalidationAllowed( INVALID_PRTAREA ) ) + { + setFramePrintAreaValid(false); + + if ( IsFlyFrame() ) + static_cast<SwFlyFrame*>(this)->Invalidate_(); + else + InvalidatePage(); + + // OD 2004-05-19 #i28701# + ActionOnInvalidation( INVALID_PRTAREA ); + } +} + +void SwFrame::ImplInvalidatePos() +{ + if ( !InvalidationAllowed( INVALID_POS ) ) + return; + + setFrameAreaPositionValid(false); + + if ( IsFlyFrame() ) + { + static_cast<SwFlyFrame*>(this)->Invalidate_(); + } + else + { + InvalidatePage(); + } + + // OD 2004-05-19 #i28701# + ActionOnInvalidation( INVALID_POS ); +} + +void SwFrame::ImplInvalidateLineNum() +{ + if ( InvalidationAllowed( INVALID_LINENUM ) ) + { + mbValidLineNum = false; + OSL_ENSURE( IsTextFrame(), "line numbers are implemented for text only" ); + InvalidatePage(); + + // OD 2004-05-19 #i28701# + ActionOnInvalidation( INVALID_LINENUM ); + } +} + +void SwFrame::ReinitializeFrameSizeAttrFlags() +{ + const SwFormatFrameSize &rFormatSize = GetAttrSet()->GetFrameSize(); + if ( SwFrameSize::Variable == rFormatSize.GetHeightSizeType() || + SwFrameSize::Minimum == rFormatSize.GetHeightSizeType()) + { + mbFixSize = false; + if ( GetType() & (SwFrameType::Header | SwFrameType::Footer | SwFrameType::Row) ) + { + SwFrame *pFrame = static_cast<SwLayoutFrame*>(this)->Lower(); + while ( pFrame ) + { pFrame->InvalidateSize_(); + pFrame->InvalidatePrt_(); + pFrame = pFrame->GetNext(); + } + SwContentFrame *pCnt = static_cast<SwLayoutFrame*>(this)->ContainsContent(); + // #i36991# - be save. + // E.g., a row can contain *no* content. + if ( pCnt ) + { + pCnt->InvalidatePage(); + do + { + pCnt->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + pCnt->InvalidateSize_(); + pCnt = pCnt->GetNextContentFrame(); + } while ( static_cast<SwLayoutFrame*>(this)->IsAnLower( pCnt ) ); + } + } + } + else if ( rFormatSize.GetHeightSizeType() == SwFrameSize::Fixed ) + { + if( IsVertical() ) + ChgSize( Size( rFormatSize.GetWidth(), getFrameArea().Height())); + else + ChgSize( Size( getFrameArea().Width(), rFormatSize.GetHeight())); + } +} + +void SwFrame::ValidateThisAndAllLowers( const sal_uInt16 nStage ) +{ + // Stage 0: Only validate frames. Do not process any objects. + // Stage 1: Only validate fly frames and all of their contents. + // Stage 2: Validate all. + + const bool bOnlyObject = 1 == nStage; + const bool bIncludeObjects = 1 <= nStage; + + if ( !bOnlyObject || IsFlyFrame() ) + { + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + setFrameAreaPositionValid(true); + } + + if ( bIncludeObjects ) + { + const SwSortedObjs* pObjs = GetDrawObjs(); + if ( pObjs ) + { + const size_t nCnt = pObjs->size(); + for ( size_t i = 0; i < nCnt; ++i ) + { + SwAnchoredObject* pAnchObj = (*pObjs)[i]; + if ( auto pFlyFrame = pAnchObj->DynCastFlyFrame() ) + pFlyFrame->ValidateThisAndAllLowers( 2 ); + else if ( auto pAnchoredDrawObj = dynamic_cast<SwAnchoredDrawObject *>( pAnchObj ) ) + pAnchoredDrawObj->ValidateThis(); + } + } + } + + if ( IsLayoutFrame() ) + { + SwFrame* pLower = static_cast<SwLayoutFrame*>(this)->Lower(); + while ( pLower ) + { + pLower->ValidateThisAndAllLowers( nStage ); + pLower = pLower->GetNext(); + } + } +} + +SwTwips SwContentFrame::GrowFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + SwRectFnSet aRectFnSet(this); + + SwTwips nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if( nFrameHeight > 0 && + nDist > (LONG_MAX - nFrameHeight ) ) + nDist = LONG_MAX - nFrameHeight; + + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowse = pSh && pSh->GetViewOptions()->getBrowseMode(); + SwFrameType nTmpType = SwFrameType::Cell | SwFrameType::Column; + if (bBrowse) + nTmpType |= SwFrameType::Body; + if( !(GetUpper()->GetType() & nTmpType) && GetUpper()->HasFixSize() ) + { + if ( !bTst ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nFrameHeight + nDist ); + + if( IsVertical() && !IsVertLR() ) + { + aFrm.Pos().AdjustX( -nDist ); + } + } + + if ( GetNext() ) + { + GetNext()->InvalidatePos(); + } + // #i28701# - Due to the new object positioning the + // frame on the next page/column can flow backward (e.g. it was moved forward + // due to the positioning of its objects ). Thus, invalivate this next frame, + // if document compatibility option 'Consider wrapping style influence on + // object positioning' is ON. + else if ( GetUpper()->GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) + { + InvalidateNextPos(); + } + } + return 0; + } + + SwTwips nReal = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()); + SwFrame *pFrame = GetUpper()->Lower(); + while( pFrame && nReal > 0 ) + { nReal -= aRectFnSet.GetHeight(pFrame->getFrameArea()); + pFrame = pFrame->GetNext(); + } + + if ( !bTst ) + { + //Contents are always resized to the wished value. + tools::Long nOld = aRectFnSet.GetHeight(getFrameArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + + aRectFnSet.SetHeight( aFrm, nOld + nDist ); + + if( IsVertical()&& !IsVertLR() ) + { + aFrm.Pos().AdjustX( -nDist ); + } + } + + SwTabFrame *pTab = (nOld && IsInTab()) ? FindTabFrame() : nullptr; + if (pTab) + { + if ( pTab->GetTable()->GetHTMLTableLayout() && + !pTab->IsJoinLocked() && + !pTab->GetFormat()->GetDoc()->GetDocShell()->IsReadOnly() ) + { + pTab->InvalidatePos(); + pTab->SetResizeHTMLTable(); + } + } + } + + //Only grow Upper if necessary. + if ( nReal < nDist ) + { + if( GetUpper() ) + { + if( bTst || !GetUpper()->IsFooterFrame() ) + nReal = GetUpper()->Grow( nDist - std::max<tools::Long>(nReal, 0), + bTst, bInfo ); + else + { + nReal = 0; + GetUpper()->InvalidateSize(); + } + } + else + nReal = 0; + } + else + nReal = nDist; + + // #i28701# - Due to the new object positioning the + // frame on the next page/column can flow backward (e.g. it was moved forward + // due to the positioning of its objects ). Thus, invalivate this next frame, + // if document compatibility option 'Consider wrapping style influence on + // object positioning' is ON. + if ( !bTst ) + { + if ( GetNext() ) + { + GetNext()->InvalidatePos(); + } + else if ( GetUpper()->GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ) + { + InvalidateNextPos(); + } + } + + return nReal; +} + +SwTwips SwContentFrame::ShrinkFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + SwRectFnSet aRectFnSet(this); + OSL_ENSURE( nDist >= 0, "nDist < 0" ); + OSL_ENSURE( nDist <= aRectFnSet.GetHeight(getFrameArea()), + "nDist > than current size." ); + + if ( !bTst ) + { + SwTwips nRstHeight; + if( GetUpper() ) + nRstHeight = aRectFnSet.BottomDist( getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper()) ); + else + nRstHeight = 0; + if( nRstHeight < 0 ) + { + SwTwips nNextHeight = 0; + if( GetUpper()->IsSctFrame() && nDist > LONG_MAX/2 ) + { + SwFrame *pNxt = GetNext(); + while( pNxt ) + { + nNextHeight += aRectFnSet.GetHeight(pNxt->getFrameArea()); + pNxt = pNxt->GetNext(); + } + } + nRstHeight = nDist + nRstHeight - nNextHeight; + } + else + { + nRstHeight = nDist; + } + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, aRectFnSet.GetHeight(aFrm) - nDist ); + + if( IsVertical() && !IsVertLR() ) + { + aFrm.Pos().AdjustX(nDist ); + } + } + + nDist = nRstHeight; + SwTabFrame *pTab = IsInTab() ? FindTabFrame() : nullptr; + if (pTab) + { + if ( pTab->GetTable()->GetHTMLTableLayout() && + !pTab->IsJoinLocked() && + !pTab->GetFormat()->GetDoc()->GetDocShell()->IsReadOnly() ) + { + pTab->InvalidatePos(); + pTab->SetResizeHTMLTable(); + } + } + } + + SwTwips nReal; + if( GetUpper() && nDist > 0 ) + { + if( bTst || !GetUpper()->IsFooterFrame() ) + nReal = GetUpper()->Shrink( nDist, bTst, bInfo ); + else + { + nReal = 0; + + // #108745# Sorry, dear old footer friend, I'm not gonna invalidate you, + // if there are any objects anchored inside your content, which + // overlap with the shrinking frame. + // This may lead to a footer frame that is too big, but this is better + // than looping. + // #109722# : The fix for #108745# was too strict. + + bool bInvalidate = true; + const SwRect aRect( getFrameArea() ); + const SwPageFrame* pPage = FindPageFrame(); + const SwSortedObjs* pSorted = pPage ? pPage->GetSortedObjs() : nullptr; + if( pSorted ) + { + for (SwAnchoredObject* pAnchoredObj : *pSorted) + { + const SwRect aBound( pAnchoredObj->GetObjRectWithSpaces() ); + + if( aBound.Left() > aRect.Right() ) + continue; + + if( aBound.Overlaps( aRect ) ) + { + const SwFrameFormat& rFormat = pAnchoredObj->GetFrameFormat(); + if( css::text::WrapTextMode_THROUGH != rFormat.GetSurround().GetSurround() ) + { + const SwFrame* pAnchor = pAnchoredObj->GetAnchorFrame(); + if ( pAnchor && pAnchor->FindFooterOrHeader() == GetUpper() ) + { + bInvalidate = false; + break; + } + } + } + } + } + + if ( bInvalidate ) + GetUpper()->InvalidateSize(); + } + } + else + nReal = 0; + + if ( !bTst ) + { + //The position of the next Frame changes for sure. + InvalidateNextPos(); + + //If I don't have a successor I have to do the retouch by myself. + if ( !GetNext() ) + SetRetouche(); + } + return nReal; +} + +void SwContentFrame::SwClientNotify(const SwModify& rMod, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + SwContentFrameInvFlags eInvFlags = SwContentFrameInvFlags::NONE; + if(pLegacy->m_pNew && RES_ATTRSET_CHG == pLegacy->m_pNew->Which() && pLegacy->m_pOld) + { + auto& rOldSetChg = *static_cast<const SwAttrSetChg*>(pLegacy->m_pOld); + auto& rNewSetChg = *static_cast<const SwAttrSetChg*>(pLegacy->m_pNew); + SfxItemIter aOIter(*rOldSetChg.GetChgSet()); + SfxItemIter aNIter(*rNewSetChg.GetChgSet()); + const SfxPoolItem* pNItem = aNIter.GetCurItem(); + const SfxPoolItem* pOItem = aOIter.GetCurItem(); + SwAttrSetChg aOldSet(rOldSetChg); + SwAttrSetChg aNewSet(rNewSetChg); + do + { + UpdateAttr_(pOItem, pNItem, eInvFlags, &aOldSet, &aNewSet); + pNItem = aNIter.NextItem(); + pOItem = aOIter.NextItem(); + } while(pNItem); + if(aOldSet.Count() || aNewSet.Count()) + SwFrame::SwClientNotify(rMod, sw::LegacyModifyHint(&aOldSet, &aNewSet)); + } + else + UpdateAttr_(pLegacy->m_pOld, pLegacy->m_pNew, eInvFlags); + + if(eInvFlags == SwContentFrameInvFlags::NONE) + return; + + SwPageFrame* pPage = FindPageFrame(); + InvalidatePage(pPage); + if(eInvFlags & SwContentFrameInvFlags::SetCompletePaint) + SetCompletePaint(); + if(eInvFlags & SwContentFrameInvFlags::InvalidatePos) + InvalidatePos_(); + if(eInvFlags & SwContentFrameInvFlags::InvalidateSize) + InvalidateSize_(); + if(eInvFlags & (SwContentFrameInvFlags::InvalidateSectPrt | SwContentFrameInvFlags::SetNextCompletePaint)) + { + if(IsInSct() && !GetPrev()) + { + SwSectionFrame* pSect = FindSctFrame(); + if(pSect->ContainsAny() == this) + { + pSect->InvalidatePrt_(); + pSect->InvalidatePage(pPage); + } + } + InvalidatePrt_(); + } + SwFrame* pNextFrame = GetIndNext(); + if(pNextFrame && eInvFlags & SwContentFrameInvFlags::InvalidateNextPrt) + { + pNextFrame->InvalidatePrt_(); + pNextFrame->InvalidatePage(pPage); + } + if(pNextFrame && eInvFlags & SwContentFrameInvFlags::SetNextCompletePaint) + { + pNextFrame->SetCompletePaint(); + } + if(eInvFlags & SwContentFrameInvFlags::InvalidatePrevPrt) + { + SwFrame* pPrevFrame = GetPrev(); + if(pPrevFrame) + { + pPrevFrame->InvalidatePrt_(); + pPrevFrame->InvalidatePage(pPage); + } + } + if(eInvFlags & SwContentFrameInvFlags::InvalidateNextPos) + InvalidateNextPos(); +} + +void SwContentFrame::UpdateAttr_( const SfxPoolItem* pOld, const SfxPoolItem* pNew, + SwContentFrameInvFlags &rInvFlags, + SwAttrSetChg *pOldSet, SwAttrSetChg *pNewSet ) +{ + bool bClear = true; + sal_uInt16 nWhich = pOld ? pOld->Which() : pNew ? pNew->Which() : 0; + switch ( nWhich ) + { + case RES_FMT_CHG: + rInvFlags = SwContentFrameInvFlags::SetCompletePaint + | SwContentFrameInvFlags::InvalidatePos + | SwContentFrameInvFlags::InvalidateSize + | SwContentFrameInvFlags::InvalidateSectPrt + | SwContentFrameInvFlags::InvalidateNextPrt + | SwContentFrameInvFlags::InvalidatePrevPrt + | SwContentFrameInvFlags::InvalidateNextPos + | SwContentFrameInvFlags::SetNextCompletePaint; + [[fallthrough]]; + + case RES_PAGEDESC: //attribute changes (on/off) + if ( IsInDocBody() && !IsInTab() ) + { + rInvFlags |= SwContentFrameInvFlags::InvalidatePos; + SwPageFrame *pPage = FindPageFrame(); + if ( !GetPrev() ) + CheckPageDescs( pPage ); + if (GetPageDescItem().GetNumOffset()) + static_cast<SwRootFrame*>(pPage->GetUpper())->SetVirtPageNum( true ); + SwDocPosUpdate aMsgHint( pPage->getFrameArea().Top() ); + pPage->GetFormat()->GetDoc()->getIDocumentFieldsAccess().UpdatePageFields( &aMsgHint ); + } + break; + + case RES_UL_SPACE: + { + // OD 2004-02-18 #106629# - correction + // Invalidation of the printing area of next frame, not only + // for footnote content. + if ( !GetIndNext() ) + { + SwFrame* pNxt = FindNext(); + if ( pNxt ) + { + SwPageFrame* pPg = pNxt->FindPageFrame(); + pNxt->InvalidatePage( pPg ); + pNxt->InvalidatePrt_(); + if( pNxt->IsSctFrame() ) + { + SwFrame* pCnt = static_cast<SwSectionFrame*>(pNxt)->ContainsAny(); + if( pCnt ) + { + pCnt->InvalidatePrt_(); + pCnt->InvalidatePage( pPg ); + } + } + pNxt->SetCompletePaint(); + } + } + // OD 2004-03-17 #i11860# + if ( GetIndNext() && + !GetUpper()->GetFormat()->getIDocumentSettingAccess().get(DocumentSettingId::USE_FORMER_OBJECT_POS) ) + { + // OD 2004-07-01 #i28701# - use new method <InvalidateObjs(..)> + GetIndNext()->InvalidateObjs(); + } + Prepare( PrepareHint::ULSpaceChanged ); //TextFrame has to correct line spacing. + rInvFlags |= SwContentFrameInvFlags::SetNextCompletePaint; + [[fallthrough]]; + } + case RES_LR_SPACE: + case RES_BOX: + case RES_SHADOW: + { + Prepare( PrepareHint::FixSizeChanged ); + SwModify aMod; + SwFrame::SwClientNotify(aMod, sw::LegacyModifyHint(pOld, pNew)); + rInvFlags |= SwContentFrameInvFlags::InvalidateNextPrt | SwContentFrameInvFlags::InvalidatePrevPrt; + break; + } + case RES_BREAK: + { + rInvFlags |= SwContentFrameInvFlags::InvalidatePos | SwContentFrameInvFlags::InvalidateNextPos; + const IDocumentSettingAccess& rIDSA = GetUpper()->GetFormat()->getIDocumentSettingAccess(); + if( rIDSA.get(DocumentSettingId::PARA_SPACE_MAX) || + rIDSA.get(DocumentSettingId::PARA_SPACE_MAX_AT_PAGES) ) + { + rInvFlags |= SwContentFrameInvFlags::SetCompletePaint; + SwFrame* pNxt = FindNext(); + if( pNxt ) + { + SwPageFrame* pPg = pNxt->FindPageFrame(); + pNxt->InvalidatePage( pPg ); + pNxt->InvalidatePrt_(); + if( pNxt->IsSctFrame() ) + { + SwFrame* pCnt = static_cast<SwSectionFrame*>(pNxt)->ContainsAny(); + if( pCnt ) + { + pCnt->InvalidatePrt_(); + pCnt->InvalidatePage( pPg ); + } + } + pNxt->SetCompletePaint(); + } + } + } + break; + + // OD 2004-02-26 #i25029# + case RES_PARATR_CONNECT_BORDER: + { + rInvFlags |= SwContentFrameInvFlags::SetCompletePaint; + if ( IsTextFrame() ) + { + InvalidateNextPrtArea(); + } + if ( !GetIndNext() && IsInTab() && IsInSplitTableRow() ) + { + FindTabFrame()->InvalidateSize(); + } + } + break; + + case RES_PARATR_TABSTOP: + case RES_CHRATR_SHADOWED: + case RES_CHRATR_AUTOKERN: + case RES_CHRATR_UNDERLINE: + case RES_CHRATR_OVERLINE: + case RES_CHRATR_KERNING: + case RES_CHRATR_FONT: + case RES_CHRATR_FONTSIZE: + case RES_CHRATR_ESCAPEMENT: + case RES_CHRATR_CONTOUR: + case RES_PARATR_NUMRULE: + rInvFlags |= SwContentFrameInvFlags::SetCompletePaint; + break; + + case RES_FRM_SIZE: + rInvFlags |= SwContentFrameInvFlags::SetCompletePaint; + [[fallthrough]]; + + default: + bClear = false; + } + if ( !bClear ) + return; + + if ( pOldSet || pNewSet ) + { + if ( pOldSet ) + pOldSet->ClearItem( nWhich ); + if ( pNewSet ) + pNewSet->ClearItem( nWhich ); + } + else + { + SwModify aMod; + SwFrame::SwClientNotify(aMod, sw::LegacyModifyHint(pOld, pNew)); + } +} + +SwLayoutFrame::SwLayoutFrame(SwFrameFormat *const pFormat, SwFrame *const pSib) + : SwFrame(pFormat, pSib) + , m_pLower(nullptr) +{ + const SwFormatFrameSize &rFormatSize = pFormat->GetFrameSize(); + if ( rFormatSize.GetHeightSizeType() == SwFrameSize::Fixed ) + mbFixSize = true; +} + +// #i28701# + +SwTwips SwLayoutFrame::InnerHeight() const +{ + const SwFrame* pCnt = Lower(); + if (!pCnt) + return 0; + + SwRectFnSet aRectFnSet(this); + SwTwips nRet = 0; + if( pCnt->IsColumnFrame() || pCnt->IsCellFrame() ) + { + do + { + SwTwips nTmp = static_cast<const SwLayoutFrame*>(pCnt)->InnerHeight(); + if( pCnt->isFramePrintAreaValid() ) + nTmp += aRectFnSet.GetHeight(pCnt->getFrameArea()) - + aRectFnSet.GetHeight(pCnt->getFramePrintArea()); + if( nRet < nTmp ) + nRet = nTmp; + pCnt = pCnt->GetNext(); + } while ( pCnt ); + } + else + { + do + { + nRet += aRectFnSet.GetHeight(pCnt->getFrameArea()); + if( pCnt->IsContentFrame() && static_cast<const SwTextFrame*>(pCnt)->IsUndersized() ) + nRet += static_cast<const SwTextFrame*>(pCnt)->GetParHeight() - + aRectFnSet.GetHeight(pCnt->getFramePrintArea()); + if( pCnt->IsLayoutFrame() && !pCnt->IsTabFrame() ) + nRet += static_cast<const SwLayoutFrame*>(pCnt)->InnerHeight() - + aRectFnSet.GetHeight(pCnt->getFramePrintArea()); + pCnt = pCnt->GetNext(); + } while( pCnt ); + + } + return nRet; +} + +SwTwips SwLayoutFrame::GrowFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowse = pSh && pSh->GetViewOptions()->getBrowseMode(); + SwFrameType nTmpType = SwFrameType::Cell | SwFrameType::Column; + if (bBrowse) + nTmpType |= SwFrameType::Body; + if( !(GetType() & nTmpType) && HasFixSize() ) + return 0; + + SwRectFnSet aRectFnSet(this); + const SwTwips nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + const SwTwips nFramePos = getFrameArea().Pos().X(); + + if ( nFrameHeight > 0 && nDist > (LONG_MAX - nFrameHeight) ) + nDist = LONG_MAX - nFrameHeight; + + SwTwips nMin = 0; + if ( GetUpper() && !IsCellFrame() ) + { + SwFrame *pFrame = GetUpper()->Lower(); + while( pFrame ) + { nMin += aRectFnSet.GetHeight(pFrame->getFrameArea()); + pFrame = pFrame->GetNext(); + } + nMin = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()) - nMin; + if ( nMin < 0 ) + nMin = 0; + } + + SwRect aOldFrame( getFrameArea() ); + bool bMoveAccFrame = false; + + bool bChgPos = IsVertical(); + if ( !bTst ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nFrameHeight + nDist ); + + if( bChgPos && !IsVertLR() ) + { + aFrm.Pos().AdjustX( -nDist ); + } + + bMoveAccFrame = true; + } + + SwTwips nReal = nDist - nMin; + if ( nReal > 0 ) + { + if ( GetUpper() ) + { // AdjustNeighbourhood now only for the columns (but not in frames) + SwNeighbourAdjust nAdjust = GetUpper()->IsFootnoteBossFrame() ? + static_cast<SwFootnoteBossFrame*>(GetUpper())->NeighbourhoodAdjustment() + : SwNeighbourAdjust::GrowShrink; + if( SwNeighbourAdjust::OnlyAdjust == nAdjust ) + nReal = AdjustNeighbourhood( nReal, bTst ); + else + { + if( SwNeighbourAdjust::AdjustGrow == nAdjust ) + nReal += AdjustNeighbourhood( nReal, bTst ); + + SwTwips nGrow = 0; + if( 0 < nReal ) + { + SwFrame* pToGrow = GetUpper(); + // NEW TABLES + // A cell with a row span of > 1 is allowed to grow the + // line containing the end of the row span if it is + // located in the same table frame: + if (IsCellFrame()) + { + const SwCellFrame* pThisCell = static_cast<const SwCellFrame*>(this); + if ( pThisCell->GetLayoutRowSpan() > 1 ) + { + SwCellFrame& rEndCell = const_cast<SwCellFrame&>(pThisCell->FindStartEndOfRowSpanCell( false )); + if ( -1 == rEndCell.GetTabBox()->getRowSpan() ) + pToGrow = rEndCell.GetUpper(); + else + pToGrow = nullptr; + } + } + nGrow = pToGrow ? pToGrow->Grow( nReal, bTst, bInfo ) : 0; + } + + if( SwNeighbourAdjust::GrowAdjust == nAdjust && nGrow < nReal ) + nReal = o3tl::saturating_add(nReal, AdjustNeighbourhood( nReal - nGrow, bTst )); + + if ( IsFootnoteFrame() && (nGrow != nReal) && GetNext() ) + { + //Footnotes can replace their successor. + SwTwips nSpace = bTst ? 0 : -nDist; + const SwFrame *pFrame = GetUpper()->Lower(); + do + { nSpace += aRectFnSet.GetHeight(pFrame->getFrameArea()); + pFrame = pFrame->GetNext(); + } while ( pFrame != GetNext() ); + nSpace = aRectFnSet.GetHeight(GetUpper()->getFramePrintArea()) -nSpace; + if ( nSpace < 0 ) + nSpace = 0; + nSpace += nGrow; + if ( nReal > nSpace ) + nReal = nSpace; + if ( nReal && !bTst ) + static_cast<SwFootnoteFrame*>(this)->InvalidateNxtFootnoteCnts( FindPageFrame() ); + } + else + nReal = nGrow; + } + } + else + nReal = 0; + + nReal += nMin; + } + else + nReal = nDist; + + if ( !bTst ) + { + if( nReal != nDist && + // NEW TABLES + ( !IsCellFrame() || static_cast<SwCellFrame*>(this)->GetLayoutRowSpan() > 1 ) ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nFrameHeight + nReal ); + + if( bChgPos && !IsVertLR() ) + { + aFrm.Pos().setX( nFramePos - nReal ); + } + + bMoveAccFrame = true; + } + + if ( nReal ) + { + SwPageFrame *pPage = FindPageFrame(); + if ( GetNext() ) + { + GetNext()->InvalidatePos_(); + if (GetNext()->IsRowFrame()) + { // also invalidate first cell + static_cast<SwLayoutFrame*>(GetNext())->Lower()->InvalidatePos_(); + } + if ( GetNext()->IsContentFrame() ) + GetNext()->InvalidatePage( pPage ); + } + if ( !IsPageBodyFrame() ) + { + InvalidateAll_(); + InvalidatePage( pPage ); + } + if (!(GetType() & (SwFrameType::Row|SwFrameType::Tab|SwFrameType::FtnCont|SwFrameType::Page|SwFrameType::Root))) + NotifyLowerObjs(); + + if( IsCellFrame() ) + InvaPercentLowers( nReal ); + + std::unique_ptr<SvxBrushItem> aBack(GetFormat()->makeBackgroundBrushItem()); + const SvxGraphicPosition ePos = aBack->GetGraphicPos(); + if ( GPOS_NONE != ePos && GPOS_TILED != ePos ) + SetCompletePaint(); + } + } + +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if( bMoveAccFrame && IsAccessibleFrame() ) + { + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && + pRootFrame->GetCurrShell() ) + { + pRootFrame->GetCurrShell()->Imp()->MoveAccessibleFrame( this, aOldFrame ); + } + } +#else + (void)bMoveAccFrame; + (void)aOldFrame; +#endif + + return nReal; +} + +SwTwips SwLayoutFrame::ShrinkFrame( SwTwips nDist, bool bTst, bool bInfo ) +{ + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowse = pSh && pSh->GetViewOptions()->getBrowseMode(); + SwFrameType nTmpType = SwFrameType::Cell | SwFrameType::Column; + if (bBrowse) + nTmpType |= SwFrameType::Body; + + if (pSh && pSh->GetViewOptions()->IsWhitespaceHidden()) + { + if (IsBodyFrame()) + { + // Whitespace is hidden and this body frame will not shrink, as it + // has a fix size. + // Invalidate the page frame size, so in case the reason for the + // shrink was that there is more whitespace on this page, the size + // without whitespace will be recalculated correctly. + SwPageFrame* pPageFrame = FindPageFrame(); + pPageFrame->InvalidateSize(); + } + } + + if( !(GetType() & nTmpType) && HasFixSize() ) + return 0; + + OSL_ENSURE( nDist >= 0, "nDist < 0" ); + SwRectFnSet aRectFnSet(this); + SwTwips nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if ( nDist > nFrameHeight ) + nDist = nFrameHeight; + + SwTwips nMin = 0; + bool bChgPos = IsVertical(); + if ( Lower() ) + { + if( !Lower()->IsNeighbourFrame() ) + { const SwFrame *pFrame = Lower(); + const tools::Long nTmp = aRectFnSet.GetHeight(getFramePrintArea()); + while( pFrame && nMin < nTmp ) + { nMin += aRectFnSet.GetHeight(pFrame->getFrameArea()); + pFrame = pFrame->GetNext(); + } + } + } + SwTwips nReal = nDist; + SwTwips nMinDiff = aRectFnSet.GetHeight(getFramePrintArea()) - nMin; + if( nReal > nMinDiff ) + nReal = nMinDiff; + if( nReal <= 0 ) + return nDist; + + SwRect aOldFrame( getFrameArea() ); + bool bMoveAccFrame = false; + + SwTwips nRealDist = nReal; + if ( !bTst ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, nFrameHeight - nReal ); + + if( bChgPos && !IsVertLR() ) + { + aFrm.Pos().AdjustX(nReal ); + } + + bMoveAccFrame = true; + } + + SwNeighbourAdjust nAdjust = GetUpper() && GetUpper()->IsFootnoteBossFrame() ? + static_cast<SwFootnoteBossFrame*>(GetUpper())->NeighbourhoodAdjustment() + : SwNeighbourAdjust::GrowShrink; + + // AdjustNeighbourhood also in columns (but not in frames) + if( SwNeighbourAdjust::OnlyAdjust == nAdjust ) + { + if ( IsPageBodyFrame() && !bBrowse ) + nReal = nDist; + else + { nReal = AdjustNeighbourhood( -nReal, bTst ); + nReal *= -1; + if ( !bTst && IsBodyFrame() && nReal < nRealDist ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, aRectFnSet.GetHeight(aFrm) + nRealDist - nReal ); + + if( bChgPos && !IsVertLR() ) + { + aFrm.Pos().AdjustX(nRealDist - nReal ); + } + + OSL_ENSURE( !IsAccessibleFrame(), "bMoveAccFrame has to be set!" ); + } + } + } + else if( IsColumnFrame() || IsColBodyFrame() ) + { + SwTwips nTmp = GetUpper()->Shrink( nReal, bTst, bInfo ); + if ( nTmp != nReal ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight( aFrm, aRectFnSet.GetHeight(aFrm) + nReal - nTmp ); + + if( bChgPos && !IsVertLR() ) + { + aFrm.Pos().AdjustX(nTmp - nReal ); + } + + OSL_ENSURE( !IsAccessibleFrame(), "bMoveAccFrame has to be set!" ); + nReal = nTmp; + } + } + else + { + SwTwips nShrink = nReal; + SwFrame* pToShrink = GetUpper(); + // NEW TABLES + if ( IsCellFrame() ) + { + const SwCellFrame* pThisCell = static_cast<const SwCellFrame*>(this); + if ( pThisCell->GetLayoutRowSpan() > 1 ) + { + SwCellFrame& rEndCell = const_cast<SwCellFrame&>(pThisCell->FindStartEndOfRowSpanCell( false )); + pToShrink = rEndCell.GetUpper(); + } + } + + nReal = pToShrink ? pToShrink->Shrink( nShrink, bTst, bInfo ) : 0; + if( ( SwNeighbourAdjust::GrowAdjust == nAdjust || SwNeighbourAdjust::AdjustGrow == nAdjust ) + && nReal < nShrink ) + AdjustNeighbourhood( nReal - nShrink ); + } + +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if( bMoveAccFrame && IsAccessibleFrame() ) + { + SwRootFrame *pRootFrame = getRootFrame(); + if( pRootFrame && pRootFrame->IsAnyShellAccessible() && + pRootFrame->GetCurrShell() ) + { + pRootFrame->GetCurrShell()->Imp()->MoveAccessibleFrame( this, aOldFrame ); + } + } +#else + (void)aOldFrame; + (void)bMoveAccFrame; +#endif + + if ( !bTst && (IsCellFrame() || IsColumnFrame() ? nReal : nRealDist) ) + { + SwPageFrame *pPage = FindPageFrame(); + if ( GetNext() ) + { + GetNext()->InvalidatePos_(); + if ( GetNext()->IsContentFrame() ) + GetNext()->InvalidatePage( pPage ); + if ( IsTabFrame() ) + static_cast<SwTabFrame*>(this)->SetComplete(); + } + else + { if ( IsRetoucheFrame() ) + SetRetouche(); + if ( IsTabFrame() ) + { + static_cast<SwTabFrame*>(this)->SetComplete(); + if ( Lower() ) // Can also be in the Join and be empty! + InvalidateNextPos(); + } + } + if ( !IsBodyFrame() ) + { + InvalidateAll_(); + InvalidatePage( pPage ); + bool bCompletePaint = true; + const SwFrameFormat* pFormat = GetFormat(); + if (pFormat) + { + std::unique_ptr<SvxBrushItem> aBack(pFormat->makeBackgroundBrushItem()); + const SvxGraphicPosition ePos = aBack->GetGraphicPos(); + if ( GPOS_NONE == ePos || GPOS_TILED == ePos ) + bCompletePaint = false; + } + if (bCompletePaint) + SetCompletePaint(); + } + + if (!(GetType() & (SwFrameType::Row|SwFrameType::Tab|SwFrameType::FtnCont|SwFrameType::Page|SwFrameType::Root))) + NotifyLowerObjs(); + + if( IsCellFrame() ) + InvaPercentLowers( nReal ); + + SwContentFrame *pCnt; + if( IsFootnoteFrame() && !static_cast<SwFootnoteFrame*>(this)->GetAttr()->GetFootnote().IsEndNote() && + ( GetFormat()->GetDoc()->GetFootnoteInfo().m_ePos != FTNPOS_CHAPTER || + ( IsInSct() && FindSctFrame()->IsFootnoteAtEnd() ) ) && + nullptr != (pCnt = static_cast<SwFootnoteFrame*>(this)->GetRefFromAttr() ) ) + { + if ( pCnt->IsFollow() ) + { // If we are in another column/page than the frame with the + // reference, we don't need to invalidate its master. + SwFrame *pTmp = pCnt->FindFootnoteBossFrame(true) == FindFootnoteBossFrame(true) + ? &pCnt->FindMaster()->GetFrame() : pCnt; + pTmp->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + pTmp->InvalidateSize(); + } + else + { + if (pCnt->FindPageFrame() == FindPageFrame()) + { + pCnt->InvalidatePos(); + } + else + { + SAL_WARN("sw.layout", "footnote frame on different page than ref frame?"); + } + } + } + } + return nReal; +} + +/** + * Changes the size of the directly subsidiary Frame's that have a fixed size, proportionally to the + * size change of the PrtArea of the Frame's. + * + * The variable Frames are also proportionally adapted; they will grow/shrink again by themselves. + */ +void SwLayoutFrame::ChgLowersProp( const Size& rOldSize ) +{ + // no change of lower properties for root frame or if no lower exists. + if ( IsRootFrame() || !Lower() ) + return; + + // declare and init <SwFrame* pLowerFrame> with first lower + SwFrame *pLowerFrame = Lower(); + + // declare and init const booleans <bHeightChgd> and <bWidthChg> + const bool bHeightChgd = rOldSize.Height() != getFramePrintArea().Height(); + const bool bWidthChgd = rOldSize.Width() != getFramePrintArea().Width(); + + SwRectFnSet aRectFnSet(this); + + // This shortcut basically tries to handle only lower frames that + // are affected by the size change. Otherwise much more lower frames + // are invalidated. + if ( !( aRectFnSet.IsVert() ? bHeightChgd : bWidthChgd ) && + ! Lower()->IsColumnFrame() && + ( ( IsBodyFrame() && IsInDocBody() && ( !IsInSct() || !FindSctFrame()->IsColLocked() ) ) || + // #i10826# Section frames without columns should not + // invalidate all lowers! + IsSctFrame() ) ) + { + // Determine page frame the body frame resp. the section frame belongs to. + SwPageFrame *pPage = FindPageFrame(); + // Determine last lower by traveling through them using <GetNext()>. + // During travel check each section frame, if it will be sized to + // maximum. If Yes, invalidate size of section frame and set + // corresponding flags at the page. + do + { + if( pLowerFrame->IsSctFrame() && static_cast<SwSectionFrame*>(pLowerFrame)->ToMaximize_() ) + { + pLowerFrame->InvalidateSize_(); + pLowerFrame->InvalidatePage( pPage ); + } + if( pLowerFrame->GetNext() ) + pLowerFrame = pLowerFrame->GetNext(); + else + break; + } while( true ); + // If found last lower is a section frame containing no section + // (section frame isn't valid and will be deleted in the future), + // travel backwards. + while( pLowerFrame->IsSctFrame() && !static_cast<SwSectionFrame*>(pLowerFrame)->GetSection() && + pLowerFrame->GetPrev() ) + pLowerFrame = pLowerFrame->GetPrev(); + // If found last lower is a section frame, set <pLowerFrame> to its last + // content, if the section frame is valid and is not sized to maximum. + // Otherwise set <pLowerFrame> to NULL - In this case body frame only + // contains invalid section frames. + if( pLowerFrame->IsSctFrame() ) + pLowerFrame = static_cast<SwSectionFrame*>(pLowerFrame)->GetSection() && + !static_cast<SwSectionFrame*>(pLowerFrame)->ToMaximize( false ) ? + static_cast<SwSectionFrame*>(pLowerFrame)->FindLastContent() : nullptr; + + // continue with found last lower, probably the last content of a section + if ( pLowerFrame ) + { + // If <pLowerFrame> is in a table frame, set <pLowerFrame> to this table + // frame and continue. + if ( pLowerFrame->IsInTab() ) + { + // OD 28.10.2002 #97265# - safeguard for setting <pLowerFrame> to + // its table frame - check, if the table frame is also a lower + // of the body frame, in order to assure that <pLowerFrame> is not + // set to a frame, which is an *upper* of the body frame. + SwFrame* pTableFrame = pLowerFrame->FindTabFrame(); + if ( IsAnLower( pTableFrame ) ) + { + pLowerFrame = pTableFrame; + } + } + // Check, if variable size of body frame resp. section frame has grown + // OD 28.10.2002 #97265# - correct check, if variable size has grown. + SwTwips nOldHeight = aRectFnSet.IsVert() ? rOldSize.Width() : rOldSize.Height(); + if( nOldHeight < aRectFnSet.GetHeight(getFramePrintArea()) ) + { + // If variable size of body|section frame has grown, only found + // last lower and the position of the its next have to be invalidated. + pLowerFrame->InvalidateAll_(); + pLowerFrame->InvalidatePage( pPage ); + if( !pLowerFrame->IsFlowFrame() || + !SwFlowFrame::CastFlowFrame( pLowerFrame )->HasFollow() ) + pLowerFrame->InvalidateNextPos( true ); + if ( pLowerFrame->IsTextFrame() ) + static_cast<SwContentFrame*>(pLowerFrame)->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + } + else + { + // variable size of body|section frame has shrunk. Thus, + // invalidate all lowers not matching the new body|section size + // and the dedicated new last lower. + if( aRectFnSet.IsVert() ) + { + SwTwips nBot = getFrameArea().Left() + getFramePrintArea().Left(); + while ( pLowerFrame && pLowerFrame->GetPrev() && pLowerFrame->getFrameArea().Left() < nBot ) + { + pLowerFrame->InvalidateAll_(); + pLowerFrame->InvalidatePage( pPage ); + pLowerFrame = pLowerFrame->GetPrev(); + } + } + else + { + SwTwips nBot = getFrameArea().Top() + getFramePrintArea().Bottom(); + while ( pLowerFrame && pLowerFrame->GetPrev() && pLowerFrame->getFrameArea().Top() > nBot ) + { + pLowerFrame->InvalidateAll_(); + pLowerFrame->InvalidatePage( pPage ); + pLowerFrame = pLowerFrame->GetPrev(); + } + } + if ( pLowerFrame ) + { + pLowerFrame->InvalidateSize_(); + pLowerFrame->InvalidatePage( pPage ); + if ( pLowerFrame->IsTextFrame() ) + static_cast<SwContentFrame*>(pLowerFrame)->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + } + } + // #i41694# - improvement by removing duplicates + if ( pLowerFrame ) + { + if ( pLowerFrame->IsInSct() ) + { + // #i41694# - follow-up of issue #i10826# + // No invalidation of section frame, if it's the this. + SwFrame* pSectFrame = pLowerFrame->FindSctFrame(); + if( pSectFrame != this && IsAnLower( pSectFrame ) ) + { + pSectFrame->InvalidateSize_(); + pSectFrame->InvalidatePage( pPage ); + } + } + } + } + return; + } // end of { special case } + + // Invalidate page for content only once. + bool bInvaPageForContent = true; + + // Declare booleans <bFixChgd> and <bVarChgd>, indicating for text frame + // adjustment, if fixed/variable size has changed. + bool bFixChgd, bVarChgd; + if( aRectFnSet.IsVert() == pLowerFrame->IsNeighbourFrame() ) + { + bFixChgd = bWidthChgd; + bVarChgd = bHeightChgd; + } + else + { + bFixChgd = bHeightChgd; + bVarChgd = bWidthChgd; + } + + // Declare const unsigned short <nFixWidth> and init it this frame types + // which has fixed width in vertical respectively horizontal layout. + // In vertical layout these are neighbour frames (cell and column frames), + // header frames and footer frames. + // In horizontal layout these are all frames, which aren't neighbour frames. + const SwFrameType nFixWidth = aRectFnSet.IsVert() ? (FRM_NEIGHBOUR | FRM_HEADFOOT) + : ~SwFrameType(FRM_NEIGHBOUR); + + // Declare const unsigned short <nFixHeight> and init it this frame types + // which has fixed height in vertical respectively horizontal layout. + // In vertical layout these are all frames, which aren't neighbour frames, + // header frames, footer frames, body frames or foot note container frames. + // In horizontal layout these are neighbour frames. + const SwFrameType nFixHeight = aRectFnSet.IsVert() ? ~SwFrameType(FRM_NEIGHBOUR | FRM_HEADFOOT | FRM_BODYFTNC) + : FRM_NEIGHBOUR; + + // Travel through all lowers using <GetNext()> + while ( pLowerFrame ) + { + if ( pLowerFrame->IsTextFrame() ) + { + // Text frames will only be invalidated - prepare invalidation + if ( bFixChgd ) + static_cast<SwContentFrame*>(pLowerFrame)->Prepare( PrepareHint::FixSizeChanged ); + if ( bVarChgd ) + static_cast<SwContentFrame*>(pLowerFrame)->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); + } + else + { + // If lower isn't a table, row, cell or section frame, adjust its + // frame size. + const SwFrameType nLowerType = pLowerFrame->GetType(); + if ( !(nLowerType & (SwFrameType::Tab|SwFrameType::Row|SwFrameType::Cell|SwFrameType::Section)) ) + { + if ( bWidthChgd ) + { + if( nLowerType & nFixWidth ) + { + // Considering previous conditions: + // In vertical layout set width of column, header and + // footer frames to its upper width. + // In horizontal layout set width of header, footer, + // foot note container, foot note, body and no-text + // frames to its upper width. + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pLowerFrame); + aFrm.Width( getFramePrintArea().Width() ); + } + else if( rOldSize.Width() && !pLowerFrame->IsFootnoteFrame() ) + { + // Adjust frame width proportional, if lower isn't a + // foot note frame and condition <nLowerType & nFixWidth> + // isn't true. + // Considering previous conditions: + // In vertical layout these are foot note container, + // body and no-text frames. + // In horizontal layout these are column and no-text frames. + // OD 24.10.2002 #97265# - <double> calculation + // Perform <double> calculation of new width, if + // one of the coefficients is greater than 50000 + SwTwips nNewWidth; + if ( (pLowerFrame->getFrameArea().Width() > 50000) || + (getFramePrintArea().Width() > 50000) ) + { + double nNewWidthTmp = + ( double(pLowerFrame->getFrameArea().Width()) + * double(getFramePrintArea().Width()) ) + / double(rOldSize.Width()); + nNewWidth = SwTwips(nNewWidthTmp); + } + else + { + nNewWidth = + (pLowerFrame->getFrameArea().Width() * getFramePrintArea().Width()) / rOldSize.Width(); + } + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pLowerFrame); + aFrm.Width( nNewWidth ); + } + } + if ( bHeightChgd ) + { + if( nLowerType & nFixHeight ) + { + // Considering previous conditions: + // In vertical layout set height of foot note and + // no-text frames to its upper height. + // In horizontal layout set height of column frames + // to its upper height. + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pLowerFrame); + aFrm.Height( getFramePrintArea().Height() ); + } + // OD 01.10.2002 #102211# + // add conditions <!pLowerFrame->IsHeaderFrame()> and + // <!pLowerFrame->IsFooterFrame()> in order to avoid that + // the <Grow> of header or footer are overwritten. + // NOTE: Height of header/footer frame is determined by contents. + else if ( rOldSize.Height() && + !pLowerFrame->IsFootnoteFrame() && + !pLowerFrame->IsHeaderFrame() && + !pLowerFrame->IsFooterFrame() + ) + { + // Adjust frame height proportional, if lower isn't a + // foot note, a header or a footer frame and + // condition <nLowerType & nFixHeight> isn't true. + // Considering previous conditions: + // In vertical layout these are column, foot note container, + // body and no-text frames. + // In horizontal layout these are column, foot note + // container, body and no-text frames. + + // OD 29.10.2002 #97265# - special case for page lowers + // The page lowers that have to be adjusted on page height + // change are the body frame and the foot note container + // frame. + // In vertical layout the height of both is directly + // adjusted to the page height change. + // In horizontal layout the height of the body frame is + // directly adjusted to the page height change and the + // foot note frame height isn't touched, because its + // determined by its content. + // OD 31.03.2003 #108446# - apply special case for page + // lowers - see description above - also for section columns. + if ( IsPageFrame() || + ( IsColumnFrame() && IsInSct() ) + ) + { + OSL_ENSURE( pLowerFrame->IsBodyFrame() || pLowerFrame->IsFootnoteContFrame(), + "ChgLowersProp - only for body or foot note container" ); + if ( pLowerFrame->IsBodyFrame() || pLowerFrame->IsFootnoteContFrame() ) + { + if ( IsVertical() || pLowerFrame->IsBodyFrame() ) + { + SwTwips nNewHeight = + pLowerFrame->getFrameArea().Height() + + ( getFramePrintArea().Height() - rOldSize.Height() ); + if ( nNewHeight < 0) + { + // OD 01.04.2003 #108446# - adjust assertion condition and text + OSL_ENSURE( !( IsPageFrame() && + (pLowerFrame->getFrameArea().Height()>0) && + (pLowerFrame->isFrameAreaDefinitionValid()) ), + "ChgLowersProg - negative height for lower."); + nNewHeight = 0; + } + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pLowerFrame); + aFrm.Height( nNewHeight ); + } + } + } + else + { + SwTwips nNewHeight; + // OD 24.10.2002 #97265# - <double> calculation + // Perform <double> calculation of new height, if + // one of the coefficients is greater than 50000 + if ( (pLowerFrame->getFrameArea().Height() > 50000) || + (getFramePrintArea().Height() > 50000) ) + { + double nNewHeightTmp = + ( double(pLowerFrame->getFrameArea().Height()) + * double(getFramePrintArea().Height()) ) + / double(rOldSize.Height()); + nNewHeight = SwTwips(nNewHeightTmp); + } + else + { + nNewHeight = ( pLowerFrame->getFrameArea().Height() + * getFramePrintArea().Height() ) / rOldSize.Height(); + } + if( !pLowerFrame->GetNext() ) + { + SwTwips nSum = getFramePrintArea().Height(); + SwFrame* pTmp = Lower(); + while( pTmp->GetNext() ) + { + if( !pTmp->IsFootnoteContFrame() || !pTmp->IsVertical() ) + nSum -= pTmp->getFrameArea().Height(); + pTmp = pTmp->GetNext(); + } + if( nSum - nNewHeight == 1 && + nSum == pLowerFrame->getFrameArea().Height() ) + nNewHeight = nSum; + } + + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pLowerFrame); + aFrm.Height( nNewHeight ); + } + } + } + } + } // end of else { NOT text frame } + + pLowerFrame->InvalidateAll_(); + if ( bInvaPageForContent && pLowerFrame->IsContentFrame() ) + { + pLowerFrame->InvalidatePage(); + bInvaPageForContent = false; + } + + if ( !pLowerFrame->GetNext() && pLowerFrame->IsRetoucheFrame() ) + { + //If a growth took place and the subordinate elements can retouch + //itself (currently Tabs, Sections and Content) we trigger it. + if ( rOldSize.Height() < getFramePrintArea().SSize().Height() || + rOldSize.Width() < getFramePrintArea().SSize().Width() ) + pLowerFrame->SetRetouche(); + } + pLowerFrame = pLowerFrame->GetNext(); + } + + // Finally adjust the columns if width is set to auto + // Possible optimization: execute this code earlier in this function and + // return??? + if ( !(( (aRectFnSet.IsVert() && bHeightChgd) || (! aRectFnSet.IsVert() && bWidthChgd) ) && + Lower()->IsColumnFrame()) ) + return; + + // get column attribute + const SwFormatCol* pColAttr = nullptr; + if ( IsPageBodyFrame() ) + { + OSL_ENSURE( GetUpper()->IsPageFrame(), "Upper is not page frame" ); + pColAttr = &GetUpper()->GetFormat()->GetCol(); + } + else + { + OSL_ENSURE( IsFlyFrame() || IsSctFrame(), "Columns not in fly or section" ); + pColAttr = &GetFormat()->GetCol(); + } + + if ( pColAttr->IsOrtho() && pColAttr->GetNumCols() > 1 ) + AdjustColumns( pColAttr, false ); +} + +/** "Formats" the Frame; Frame and PrtArea. + * + * The Fixsize is not set here. + */ +void SwLayoutFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs *pAttrs ) +{ + OSL_ENSURE( pAttrs, "LayoutFrame::Format, pAttrs is 0." ); + + if ( isFramePrintAreaValid() && isFrameAreaSizeValid() ) + return; + + bool bHideWhitespace = false; + if (IsPageFrame()) + { + SwViewShell* pShell = getRootFrame()->GetCurrShell(); + if (pShell && pShell->GetViewOptions()->IsWhitespaceHidden()) + { + // This is needed so that no space is reserved for the margin on + // the last page of the document. Other pages would have no margin + // set even without this, as their frame height is the content + // height already. + bHideWhitespace = true; + } + } + + const sal_uInt16 nLeft = o3tl::narrowing<sal_uInt16>(pAttrs->CalcLeft(this)); + const sal_uInt16 nUpper = bHideWhitespace ? 0 : pAttrs->CalcTop(); + + const sal_uInt16 nRight = o3tl::narrowing<sal_uInt16>(pAttrs->CalcRight(this)); + const sal_uInt16 nLower = bHideWhitespace ? 0 : pAttrs->CalcBottom(); + + const bool bVert = IsVertical() && !IsPageFrame(); + SwRectFn fnRect = bVert ? ( IsVertLR() ? (IsVertLRBT() ? fnRectVertL2RB2T : fnRectVertL2R) : fnRectVert ) : fnRectHori; + if ( !isFramePrintAreaValid() ) + { + setFramePrintAreaValid(true); + (this->*fnRect->fnSetXMargins)( nLeft, nRight ); + (this->*fnRect->fnSetYMargins)( nUpper, nLower ); + } + + if ( isFrameAreaSizeValid() ) + return; + + if ( !HasFixSize() ) + { + const SwTwips nBorder = nUpper + nLower; + const SwFormatFrameSize &rSz = GetFormat()->GetFrameSize(); + SwTwips nMinHeight = rSz.GetHeightSizeType() == SwFrameSize::Minimum ? rSz.GetHeight() : 0; + do + { + setFrameAreaSizeValid(true); + + //The size in VarSize is calculated using the content plus the + // borders. + SwTwips nRemaining = 0; + SwFrame *pFrame = Lower(); + while ( pFrame ) + { nRemaining += (pFrame->getFrameArea().*fnRect->fnGetHeight)(); + if( pFrame->IsTextFrame() && static_cast<SwTextFrame*>(pFrame)->IsUndersized() ) + // This TextFrame would like to be a bit bigger + nRemaining += static_cast<SwTextFrame*>(pFrame)->GetParHeight() + - (pFrame->getFramePrintArea().*fnRect->fnGetHeight)(); + else if( pFrame->IsSctFrame() && static_cast<SwSectionFrame*>(pFrame)->IsUndersized() ) + nRemaining += static_cast<SwSectionFrame*>(pFrame)->Undersize(); + pFrame = pFrame->GetNext(); + } + nRemaining += nBorder; + nRemaining = std::max( nRemaining, nMinHeight ); + const SwTwips nDiff = nRemaining-(getFrameArea().*fnRect->fnGetHeight)(); + const tools::Long nOldLeft = (getFrameArea().*fnRect->fnGetLeft)(); + const tools::Long nOldTop = (getFrameArea().*fnRect->fnGetTop)(); + if ( nDiff ) + { + if ( nDiff > 0 ) + Grow( nDiff ); + else + Shrink( -nDiff ); + //Updates the positions using the fast channel. + MakePos(); + } + //Don't exceed the bottom edge of the Upper. + if ( GetUpper() && (getFrameArea().*fnRect->fnGetHeight)() ) + { + const SwTwips nLimit = (GetUpper()->*fnRect->fnGetPrtBottom)(); + if( (this->*fnRect->fnSetLimit)( nLimit ) && + nOldLeft == (getFrameArea().*fnRect->fnGetLeft)() && + nOldTop == (getFrameArea().*fnRect->fnGetTop)() ) + { + setFrameAreaSizeValid(true); + setFramePrintAreaValid(true); + } + } + } while ( !isFrameAreaSizeValid() ); + } + else if (GetType() & FRM_HEADFOOT) + { + do + { if ( getFrameArea().Height() != pAttrs->GetSize().Height() ) + { + ChgSize( Size( getFrameArea().Width(), pAttrs->GetSize().Height())); + } + + setFrameAreaSizeValid(true); + MakePos(); + } while ( !isFrameAreaSizeValid() ); + } + else + { + setFrameAreaSizeValid(true); + } + + // While updating the size, PrtArea might be invalidated. + if (!isFramePrintAreaValid()) + { + setFramePrintAreaValid(true); + (this->*fnRect->fnSetXMargins)(nLeft, nRight); + (this->*fnRect->fnSetYMargins)(nUpper, nLower); + } +} + +static void InvaPercentFlys( SwFrame *pFrame, SwTwips nDiff ) +{ + OSL_ENSURE( pFrame->GetDrawObjs(), "Can't find any Objects" ); + for (SwAnchoredObject* pAnchoredObj : *pFrame->GetDrawObjs()) + { + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + const SwFormatFrameSize &rSz = pFly->GetFormat()->GetFrameSize(); + if ( rSz.GetWidthPercent() || rSz.GetHeightPercent() ) + { + bool bNotify = true; + // If we've a fly with more than 90% relative height... + if( rSz.GetHeightPercent() > 90 && pFly->GetAnchorFrame() && + rSz.GetHeightPercent() != SwFormatFrameSize::SYNCED && nDiff ) + { + const SwFrame *pRel = pFly->IsFlyLayFrame() ? pFly->GetAnchorFrame(): + pFly->GetAnchorFrame()->GetUpper(); + // ... and we have already more than 90% height and we + // not allow the text to go through... + // then a notification could cause an endless loop, e.g. + // 100% height and no text wrap inside a cell of a table. + if( pFly->getFrameArea().Height()*10 > + ( nDiff + pRel->getFramePrintArea().Height() )*9 && + pFly->GetFormat()->GetSurround().GetSurround() != + css::text::WrapTextMode_THROUGH ) + bNotify = false; + } + if( bNotify ) + pFly->InvalidateSize(); + } + } + } +} + +void SwLayoutFrame::InvaPercentLowers( SwTwips nDiff ) +{ + if ( GetDrawObjs() ) + ::InvaPercentFlys( this, nDiff ); + + SwFrame *pFrame = ContainsContent(); + if ( !pFrame ) + return; + + do + { + if ( pFrame->IsInTab() && !IsTabFrame() ) + { + SwFrame *pTmp = pFrame->FindTabFrame(); + OSL_ENSURE( pTmp, "Where's my TabFrame?" ); + if( IsAnLower( pTmp ) ) + pFrame = pTmp; + } + + if ( pFrame->IsTabFrame() ) + { + const SwFormatFrameSize &rSz = static_cast<SwLayoutFrame*>(pFrame)->GetFormat()->GetFrameSize(); + if ( rSz.GetWidthPercent() || rSz.GetHeightPercent() ) + pFrame->InvalidatePrt(); + } + else if ( pFrame->GetDrawObjs() ) + ::InvaPercentFlys( pFrame, nDiff ); + pFrame = pFrame->FindNextCnt(); + } while ( pFrame && IsAnLower( pFrame ) ) ; +} + +tools::Long SwLayoutFrame::CalcRel( const SwFormatFrameSize &rSz ) const +{ + tools::Long nRet = rSz.GetWidth(), + nPercent = rSz.GetWidthPercent(); + + if ( nPercent ) + { + const SwFrame *pRel = GetUpper(); + tools::Long nRel = LONG_MAX; + const SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const bool bBrowseMode = pSh && pSh->GetViewOptions()->getBrowseMode(); + if( pRel->IsPageBodyFrame() && pSh && bBrowseMode && pSh->VisArea().Width() ) + { + nRel = pSh->GetBrowseWidth(); + tools::Long nDiff = nRel - pRel->getFramePrintArea().Width(); + if ( nDiff > 0 ) + nRel -= nDiff; + } + nRel = std::min( nRel, pRel->getFramePrintArea().Width() ); + nRet = nRel * nPercent / 100; + } + return nRet; +} + +// Local helpers for SwLayoutFrame::FormatWidthCols() + +static tools::Long lcl_CalcMinColDiff( SwLayoutFrame *pLayFrame ) +{ + tools::Long nDiff = 0, nFirstDiff = 0; + SwLayoutFrame *pCol = static_cast<SwLayoutFrame*>(pLayFrame->Lower()); + OSL_ENSURE( pCol, "Where's the columnframe?" ); + SwFrame *pFrame = pCol->Lower(); + do + { + if( pFrame && pFrame->IsBodyFrame() ) + pFrame = static_cast<SwBodyFrame*>(pFrame)->Lower(); + if ( pFrame && pFrame->IsTextFrame() ) + { + const tools::Long nTmp = static_cast<SwTextFrame*>(pFrame)->FirstLineHeight(); + if ( nTmp != USHRT_MAX ) + { + if ( pCol == pLayFrame->Lower() ) + nFirstDiff = nTmp; + else + nDiff = nDiff ? std::min( nDiff, nTmp ) : nTmp; + } + } + //Skip empty columns! + pCol = static_cast<SwLayoutFrame*>(pCol->GetNext()); + while ( pCol && nullptr == (pFrame = pCol->Lower()) ) + pCol = static_cast<SwLayoutFrame*>(pCol->GetNext()); + + } while ( pFrame && pCol ); + + return nDiff ? nDiff : nFirstDiff ? nFirstDiff : 240; +} + +static bool lcl_IsFlyHeightClipped( SwLayoutFrame *pLay ) +{ + SwFrame *pFrame = pLay->ContainsContent(); + while ( pFrame ) + { + if ( pFrame->IsInTab() ) + pFrame = pFrame->FindTabFrame(); + + if ( pFrame->GetDrawObjs() ) + { + const size_t nCnt = pFrame->GetDrawObjs()->size(); + for ( size_t i = 0; i < nCnt; ++i ) + { + SwAnchoredObject* pAnchoredObj = (*pFrame->GetDrawObjs())[i]; + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + if ( pFly->IsHeightClipped() && + ( !pFly->IsFlyFreeFrame() || pFly->GetPageFrame() ) ) + return true; + } + } + } + pFrame = pFrame->FindNextCnt(); + } + return false; +} + +void SwLayoutFrame::FormatWidthCols( const SwBorderAttrs &rAttrs, + const SwTwips nBorder, const SwTwips nMinHeight ) +{ + //If there are columns involved, the size is adjusted using the last column. + //1. Format content. + //2. Calculate height of the last column: if it's too big, the Fly has to + // grow. The amount by which the Fly grows is not the amount of the + // overhang because we have to act on the assumption that some text flows + // back which will generate some more space. + // The amount which we grow by equals the overhang + // divided by the amount of columns or the overhang itself if it's smaller + // than the amount of columns. + //3. Go back to 1. until everything is stable. + + const SwFormatCol &rCol = rAttrs.GetAttrSet().GetCol(); + const sal_uInt16 nNumCols = rCol.GetNumCols(); + + bool bEnd = false; + bool bBackLock = false; + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + SwViewShellImp *pImp = pSh ? pSh->Imp() : nullptr; + vcl::RenderContext* pRenderContext = pSh ? pSh->GetOut() : nullptr; + { + // Underlying algorithm + // We try to find the optimal height for the column. + // nMinimum starts with the passed minimum height and is then remembered + // as the maximum height on which column content still juts out of a + // column. + // nMaximum starts with LONG_MAX and is then remembered as the minimum + // width on which the content fitted. + // In column based sections nMaximum starts at the maximum value which + // the surrounding defines, this can certainly be a value on which + // content still juts out. + // The columns are formatted. If content still juts out, nMinimum is + // adjusted accordingly, then we grow, at least by uMinDiff but not + // over a certain nMaximum. If no content juts out but there is still + // some space left in the column, shrinking is done accordingly, at + // least by nMindIff but not below the nMinimum. + // Cancel as soon as no content juts out and the difference from minimum + // to maximum is less than MinDiff or the maximum which was defined by + // the surrounding is reached even if some content still juts out. + + // Criticism of this implementation + // 1. Theoretically situations are possible in which the content fits in + // a lower height but not in a higher height. To ensure that the code + // handles such situations the code contains a few checks concerning + // minimum and maximum which probably are never triggered. + // 2. We use the same nMinDiff for shrinking and growing, but nMinDiff + // is more or less the smallest first line height and doesn't seem ideal + // as minimum value. + + tools::Long nMinimum = nMinHeight; + tools::Long nMaximum; + bool bNoBalance = false; + SwRectFnSet aRectFnSet(this); + if( IsSctFrame() ) + { + nMaximum = aRectFnSet.GetHeight(getFrameArea()) - nBorder + + aRectFnSet.BottomDist(getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper())); + nMaximum += GetUpper()->Grow( LONG_MAX, true ); + if( nMaximum < nMinimum ) + { + if( nMaximum < 0 ) + nMinimum = nMaximum = 0; + else + nMinimum = nMaximum; + } + if( nMaximum > BROWSE_HEIGHT ) + nMaximum = BROWSE_HEIGHT; + + bNoBalance = static_cast<SwSectionFrame*>(this)->GetSection()->GetFormat()-> + GetBalancedColumns().GetValue(); + SwFrame* pAny = ContainsAny(); + if( bNoBalance || + ( !aRectFnSet.GetHeight(getFrameArea()) && pAny ) ) + { + tools::Long nTop = aRectFnSet.GetTopMargin(*this); + // #i23129# - correction + // to the calculated maximum height. + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nMaximum - aRectFnSet.GetHeight(getFrameArea()) ); + } + + if( nTop > nMaximum ) + nTop = nMaximum; + aRectFnSet.SetYMargins( *this, nTop, 0 ); + } + if( !pAny && !static_cast<SwSectionFrame*>(this)->IsFootnoteLock() ) + { + SwFootnoteContFrame* pFootnoteCont = static_cast<SwSectionFrame*>(this)->ContainsFootnoteCont(); + if( pFootnoteCont ) + { + SwFrame* pFootnoteAny = pFootnoteCont->ContainsAny(); + if( pFootnoteAny && pFootnoteAny->isFrameAreaDefinitionValid() ) + { + bBackLock = true; + static_cast<SwSectionFrame*>(this)->SetFootnoteLock( true ); + } + } + } + } + else + nMaximum = LONG_MAX; + + // #i3317# - reset temporarily consideration + // of wrapping style influence + SwPageFrame* pPageFrame = FindPageFrame(); + SwSortedObjs* pObjs = pPageFrame ? pPageFrame->GetSortedObjs() : nullptr; + if ( pObjs ) + { + for (SwAnchoredObject* pAnchoredObj : *pObjs) + { + if ( IsAnLower( pAnchoredObj->GetAnchorFrame() ) ) + { + pAnchoredObj->SetTmpConsiderWrapInfluence( false ); + } + } + } + do + { + //Could take a while therefore check for Waitcrsr here. + if ( pImp ) + pImp->CheckWaitCursor(); + + setFrameAreaSizeValid(true); + //First format the column as this will relieve the stack a bit. + //Also set width and height of the column (if they are wrong) + //while we are at it. + SwLayoutFrame *pCol = static_cast<SwLayoutFrame*>(Lower()); + + // #i27399# + // Simply setting the column width based on the values returned by + // CalcColWidth does not work for automatic column width. + AdjustColumns( &rCol, false ); + + for ( sal_uInt16 i = 0; i < nNumCols; ++i ) + { + pCol->Calc(pRenderContext); + // ColumnFrames have a BodyFrame now, which needs to be calculated + pCol->Lower()->Calc(pRenderContext); + if( pCol->Lower()->GetNext() ) + pCol->Lower()->GetNext()->Calc(pRenderContext); // SwFootnoteCont + pCol = static_cast<SwLayoutFrame*>(pCol->GetNext()); + } + + ::CalcContent( this ); + + pCol = static_cast<SwLayoutFrame*>(Lower()); + OSL_ENSURE( pCol && pCol->GetNext(), ":-( column making holidays?"); + // set bMinDiff if no empty columns exist + bool bMinDiff = true; + // OD 28.03.2003 #108446# - check for all column content and all columns + while ( bMinDiff && pCol ) + { + bMinDiff = nullptr != pCol->ContainsContent(); + pCol = static_cast<SwLayoutFrame*>(pCol->GetNext()); + } + pCol = static_cast<SwLayoutFrame*>(Lower()); + // OD 28.03.2003 #108446# - initialize local variable + SwTwips nDiff = 0; + SwTwips nMaxFree = 0; + SwTwips nAllFree = LONG_MAX; + // set bFoundLower if there is at least one non-empty column + bool bFoundLower = false; + while( pCol ) + { + SwLayoutFrame* pLay = static_cast<SwLayoutFrame*>(pCol->Lower()); + SwTwips nInnerHeight = aRectFnSet.GetHeight(pLay->getFrameArea()) - + aRectFnSet.GetHeight(pLay->getFramePrintArea()); + if( pLay->Lower() ) + { + bFoundLower = true; + nInnerHeight += pLay->InnerHeight(); + } + else if( nInnerHeight < 0 ) + nInnerHeight = 0; + + if( pLay->GetNext() ) + { + bFoundLower = true; + pLay = static_cast<SwLayoutFrame*>(pLay->GetNext()); + OSL_ENSURE( pLay->IsFootnoteContFrame(),"FootnoteContainer expected" ); + nInnerHeight += pLay->InnerHeight(); + nInnerHeight += aRectFnSet.GetHeight(pLay->getFrameArea()) - + aRectFnSet.GetHeight(pLay->getFramePrintArea()); + } + nInnerHeight -= aRectFnSet.GetHeight(pCol->getFramePrintArea()); + if( nInnerHeight > nDiff ) + { + nDiff = nInnerHeight; + nAllFree = 0; + } + else + { + if( nMaxFree < -nInnerHeight ) + nMaxFree = -nInnerHeight; + if( nAllFree > -nInnerHeight ) + nAllFree = -nInnerHeight; + } + pCol = static_cast<SwLayoutFrame*>(pCol->GetNext()); + } + + if ( bFoundLower || ( IsSctFrame() && static_cast<SwSectionFrame*>(this)->HasFollow() ) ) + { + SwTwips nMinDiff = ::lcl_CalcMinColDiff( this ); + // Here we decide if growing is needed - this is the case, if + // column content (nDiff) or a Fly juts over. + // In sections with columns we take into account to set the size + // when having a non-empty Follow. + if ( nDiff || ::lcl_IsFlyHeightClipped( this ) || + ( IsSctFrame() && static_cast<SwSectionFrame*>(this)->CalcMinDiff( nMinDiff ) ) ) + { + tools::Long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + // The minimum must not be smaller than our PrtHeight as + // long as something juts over. + if( nMinimum < nPrtHeight ) + nMinimum = nPrtHeight; + // The maximum must not be smaller than PrtHeight if + // something still juts over. + if( nMaximum < nPrtHeight ) + nMaximum = nPrtHeight; // Robust, but will this ever happen? + if( !nDiff ) // If only Flys jut over, we grow by nMinDiff + nDiff = nMinDiff; + // If we should grow more than by nMinDiff we split it over + // the columns + if ( std::abs(nDiff - nMinDiff) > nNumCols && nDiff > static_cast<tools::Long>(nNumCols) ) + nDiff /= nNumCols; + + if ( bMinDiff ) + { // If no empty column exists, we want to grow at least + // by nMinDiff. Special case: If we are smaller than the + // minimal FrameHeight and PrtHeight is smaller than + // nMindiff we grow in a way that PrtHeight is exactly + // nMinDiff afterwards. + tools::Long nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + if ( nFrameHeight > nMinHeight || nPrtHeight >= nMinDiff ) + nDiff = std::max( nDiff, nMinDiff ); + else if( nDiff < nMinDiff ) + nDiff = nMinDiff - nPrtHeight + 1; + } + // nMaximum has a size which fits the content or the + // requested value from the surrounding therefore we don't + // need to exceed this value. + if( nDiff + nPrtHeight > nMaximum ) + nDiff = nMaximum - nPrtHeight; + } + else if( nMaximum > nMinimum ) // We fit, do we still have some margin? + { + tools::Long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + if ( nMaximum < nPrtHeight ) + nDiff = nMaximum - nPrtHeight; // We grew over a working + // height and shrink back to it, but will this ever + // happen? + else + { // We have a new maximum, a size which fits for the content. + nMaximum = nPrtHeight; + // If the margin in the column is bigger than nMinDiff + // and we therefore drop under the minimum, we deflate + // a bit. + if ( !bNoBalance && + // #i23129# - <nMinDiff> can be + // big, because of an object at the beginning of + // a column. Thus, decrease optimization here. + //nMaxFree >= nMinDiff && + nMaxFree > 0 && + ( !nAllFree || + nMinimum < nPrtHeight - nMinDiff ) ) + { + nMaxFree /= nNumCols; // disperse over the columns + nDiff = nMaxFree < nMinDiff ? -nMinDiff : -nMaxFree; // min nMinDiff + if( nPrtHeight + nDiff <= nMinimum ) // below the minimum? + nDiff = ( nMinimum - nMaximum ) / 2; // Take the center + } + else if( nAllFree ) + { + nDiff = -nAllFree; + if( nPrtHeight + nDiff <= nMinimum ) // Less than minimum? + nDiff = ( nMinimum - nMaximum ) / 2; // Take the center + } + } + } + if( nDiff ) // now we shrink or grow... + { + Size aOldSz( getFramePrintArea().SSize() ); + tools::Long nTop = aRectFnSet.GetTopMargin(*this); + nDiff = aRectFnSet.GetHeight(getFramePrintArea()) + nDiff + nBorder - aRectFnSet.GetHeight(getFrameArea()); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nDiff ); + } + + // #i68520# + SwFlyFrame *pFlyFrame = IsFlyFrame() ? static_cast<SwFlyFrame*>(this) : nullptr; + if (pFlyFrame) + { + pFlyFrame->InvalidateObjRectWithSpaces(); + } + aRectFnSet.SetYMargins( *this, nTop, nBorder - nTop ); + ChgLowersProp( aOldSz ); + NotifyLowerObjs(); + + // #i3317# - reset temporarily consideration + // of wrapping style influence + SwPageFrame* pTmpPageFrame = FindPageFrame(); + SwSortedObjs* pTmpObjs = pTmpPageFrame ? pTmpPageFrame->GetSortedObjs() : nullptr; + if ( pTmpObjs ) + { + for (SwAnchoredObject* pAnchoredObj : *pTmpObjs) + { + if ( IsAnLower( pAnchoredObj->GetAnchorFrame() ) ) + { + pAnchoredObj->SetTmpConsiderWrapInfluence( false ); + } + } + } + //Invalidate suitable to nicely balance the Frames. + //- Every first one after the second column gets a + // InvalidatePos(); + pCol = static_cast<SwLayoutFrame*>(Lower()->GetNext()); + while ( pCol ) + { + SwFrame *pLow = pCol->Lower(); + if ( pLow ) + pLow->InvalidatePos_(); + pCol = static_cast<SwLayoutFrame*>(pCol->GetNext()); + } + if( IsSctFrame() && static_cast<SwSectionFrame*>(this)->HasFollow() ) + { + // If we created a Follow, we need to give its content + // the opportunity to flow back inside the CalcContent + SwContentFrame* pTmpContent = + static_cast<SwSectionFrame*>(this)->GetFollow()->ContainsContent(); + if( pTmpContent ) + pTmpContent->InvalidatePos_(); + } + } + else + bEnd = true; + } + else + bEnd = true; + + } while ( !bEnd || !isFrameAreaSizeValid() ); + } + // OD 01.04.2003 #108446# - Don't collect endnotes for sections. Thus, set + // 2nd parameter to <true>. + ::CalcContent( this, true ); + if( IsSctFrame() ) + { + // OD 14.03.2003 #i11760# - adjust 2nd parameter - sal_True --> true + setFrameAreaSizeValid(true); + ::CalcContent( this, true ); + if( bBackLock ) + static_cast<SwSectionFrame*>(this)->SetFootnoteLock( false ); + } +} + +static SwContentFrame* lcl_InvalidateSection( SwFrame *pCnt, SwInvalidateFlags nInv ) +{ + SwSectionFrame* pSect = pCnt->FindSctFrame(); + // If our ContentFrame is placed inside a table or a footnote, only sections + // which are also placed inside are meant. + // Exception: If a table is directly passed. + if( ( ( pCnt->IsInTab() && !pSect->IsInTab() ) || + ( pCnt->IsInFootnote() && !pSect->IsInFootnote() ) ) && !pCnt->IsTabFrame() ) + return nullptr; + if( nInv & SwInvalidateFlags::Size ) + pSect->InvalidateSize_(); + if( nInv & SwInvalidateFlags::Pos ) + pSect->InvalidatePos_(); + if( nInv & SwInvalidateFlags::PrtArea ) + pSect->InvalidatePrt_(); + SwFlowFrame *pFoll = pSect->GetFollow(); + // Temporary separation from follow + pSect->SetFollow( nullptr ); + SwContentFrame* pRet = pSect->FindLastContent(); + pSect->SetFollow( pFoll ); + return pRet; +} + +static SwContentFrame* lcl_InvalidateTable( SwTabFrame *pTable, SwInvalidateFlags nInv ) +{ + if( ( nInv & SwInvalidateFlags::Section ) && pTable->IsInSct() ) + lcl_InvalidateSection( pTable, nInv ); + if( nInv & SwInvalidateFlags::Size ) + pTable->InvalidateSize_(); + if( nInv & SwInvalidateFlags::Pos ) + pTable->InvalidatePos_(); + if( nInv & SwInvalidateFlags::PrtArea ) + pTable->InvalidatePrt_(); + return pTable->FindLastContent(); +} + +static void lcl_InvalidateAllContent( SwContentFrame *pCnt, SwInvalidateFlags nInv ); + +static void lcl_InvalidateContent( SwContentFrame *pCnt, SwInvalidateFlags nInv ) +{ + SwContentFrame *pLastTabCnt = nullptr; + SwContentFrame *pLastSctCnt = nullptr; + while ( pCnt ) + { + if( nInv & SwInvalidateFlags::Section ) + { + if( pCnt->IsInSct() ) + { + // See above at tables + if( !pLastSctCnt ) + pLastSctCnt = lcl_InvalidateSection( pCnt, nInv ); + if( pLastSctCnt == pCnt ) + pLastSctCnt = nullptr; + } +#if OSL_DEBUG_LEVEL > 0 + else + OSL_ENSURE( !pLastSctCnt, "Where's the last SctContent?" ); +#endif + } + if( nInv & SwInvalidateFlags::Table ) + { + if( pCnt->IsInTab() ) + { + // To not call FindTabFrame() for each ContentFrame of a table and + // then invalidate the table, we remember the last ContentFrame of + // the table and ignore IsInTab() until we are past it. + // When entering the table, LastSctCnt is set to null, so + // sections inside the table are correctly invalidated. + // If the table itself is in a section the + // invalidation is done three times, which is acceptable. + if( !pLastTabCnt ) + { + pLastTabCnt = lcl_InvalidateTable( pCnt->FindTabFrame(), nInv ); + pLastSctCnt = nullptr; + } + if( pLastTabCnt == pCnt ) + { + pLastTabCnt = nullptr; + pLastSctCnt = nullptr; + } + } +#if OSL_DEBUG_LEVEL > 0 + else + OSL_ENSURE( !pLastTabCnt, "Where's the last TabContent?" ); +#endif + } + + if( nInv & SwInvalidateFlags::Size ) + pCnt->Prepare( PrepareHint::Clear, nullptr, false ); + if( nInv & SwInvalidateFlags::Pos ) + pCnt->InvalidatePos_(); + if( nInv & SwInvalidateFlags::PrtArea ) + pCnt->InvalidatePrt_(); + if ( nInv & SwInvalidateFlags::LineNum ) + pCnt->InvalidateLineNum(); + if ( pCnt->GetDrawObjs() ) + lcl_InvalidateAllContent( pCnt, nInv ); + pCnt = pCnt->GetNextContentFrame(); + } +} + +static void lcl_InvalidateAllContent( SwContentFrame *pCnt, SwInvalidateFlags nInv ) +{ + SwSortedObjs &rObjs = *pCnt->GetDrawObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + if ( pFly->IsFlyInContentFrame() ) + { + ::lcl_InvalidateContent( pFly->ContainsContent(), nInv ); + if( nInv & SwInvalidateFlags::Direction ) + pFly->CheckDirChange(); + } + } + } +} + +void SwRootFrame::InvalidateAllContent( SwInvalidateFlags nInv ) +{ + // First process all page bound FlyFrames. + SwPageFrame *pPage = static_cast<SwPageFrame*>(Lower()); + while( pPage ) + { + pPage->InvalidateFlyLayout(); + pPage->InvalidateFlyContent(); + pPage->InvalidateFlyInCnt(); + pPage->InvalidateLayout(); + pPage->InvalidateContent(); + pPage->InvalidatePage( pPage ); // So even the Turbo disappears if applicable + + if ( pPage->GetSortedObjs() ) + { + const SwSortedObjs &rObjs = *pPage->GetSortedObjs(); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) + { + ::lcl_InvalidateContent( pFly->ContainsContent(), nInv ); + if ( nInv & SwInvalidateFlags::Direction ) + pFly->CheckDirChange(); + } + } + } + if( nInv & SwInvalidateFlags::Direction ) + pPage->CheckDirChange(); + pPage = static_cast<SwPageFrame*>(pPage->GetNext()); + } + + //Invalidate the whole document content and the character bound Flys here. + ::lcl_InvalidateContent( ContainsContent(), nInv ); + + if( nInv & SwInvalidateFlags::PrtArea ) + { + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + if( pSh ) + pSh->InvalidateWindows( getFrameArea() ); + } +} + +/** + * Invalidate/re-calculate the position of all floating screen objects (Writer fly frames and + * drawing objects), that are anchored to paragraph or to character. (2004-03-16 #i11860#) + */ +void SwRootFrame::InvalidateAllObjPos() +{ + const SwPageFrame* pPageFrame = static_cast<const SwPageFrame*>(Lower()); + while( pPageFrame ) + { + pPageFrame->InvalidateFlyLayout(); + + if ( pPageFrame->GetSortedObjs() ) + { + const SwSortedObjs& rObjs = *(pPageFrame->GetSortedObjs()); + for (SwAnchoredObject* pAnchoredObj : rObjs) + { + const SwFormatAnchor& rAnch = pAnchoredObj->GetFrameFormat().GetAnchor(); + if ((rAnch.GetAnchorId() != RndStdIds::FLY_AT_PARA) && + (rAnch.GetAnchorId() != RndStdIds::FLY_AT_CHAR)) + { + // only to paragraph and to character anchored objects are considered. + continue; + } + // #i28701# - special invalidation for anchored + // objects, whose wrapping style influence has to be considered. + if ( pAnchoredObj->ConsiderObjWrapInfluenceOnObjPos() ) + pAnchoredObj->InvalidateObjPosForConsiderWrapInfluence(); + else + pAnchoredObj->InvalidateObjPos(); + } + } + + pPageFrame = static_cast<const SwPageFrame*>(pPageFrame->GetNext()); + } +} + +static void AddRemoveFlysForNode( + SwTextFrame & rFrame, SwTextNode & rTextNode, + std::set<SwNodeOffset> *const pSkipped, + const SwFrameFormats & rTable, + SwPageFrame *const pPage, + SwTextNode const*const pNode, + std::vector<sw::Extent>::const_iterator const& rIterFirst, + std::vector<sw::Extent>::const_iterator const& rIterEnd, + SwTextNode const*const pFirstNode, SwTextNode const*const pLastNode) +{ + if (pNode == &rTextNode) + { // remove existing hidden at-char anchored flys + RemoveHiddenObjsOfNode(rTextNode, &rIterFirst, &rIterEnd, pFirstNode, pLastNode); + } + else if (rTextNode.GetIndex() < pNode->GetIndex()) + { + // pNode's frame has been deleted by CheckParaRedlineMerge() + AppendObjsOfNode(&rTable, + pNode->GetIndex(), &rFrame, pPage, &rTextNode.GetDoc(), + &rIterFirst, &rIterEnd, pFirstNode, pLastNode); + if (pSkipped) + { + // if a fly has been added by AppendObjsOfNode, it must be skipped; if not, then it doesn't matter if it's skipped or not because it has no frames and because of that it would be skipped anyway + for (auto const pFly : pNode->GetAnchoredFlys()) + { + if (pFly->Which() != RES_DRAWFRMFMT) + { + pSkipped->insert(pFly->GetContent().GetContentIdx()->GetIndex()); + } + } + } + } +} + +namespace sw { + +/// rTextNode is the first one of the "new" merge - if rTextNode isn't the same +/// as MergedPara::pFirstNode, then nodes before rTextNode have their flys +/// already properly attached, so only the other nodes need handling here. +void AddRemoveFlysAnchoredToFrameStartingAtNode( + SwTextFrame & rFrame, SwTextNode & rTextNode, + std::set<SwNodeOffset> *const pSkipped) +{ + auto const pMerged(rFrame.GetMergedPara()); + if (!pMerged + // do this only *once*, for the *last* frame + // otherwise AppendObj would create multiple frames for fly-frames! + || rFrame.GetFollow()) + return; + + assert(pMerged->pFirstNode->GetIndex() <= rTextNode.GetIndex() + && rTextNode.GetIndex() <= pMerged->pLastNode->GetIndex()); + // add visible flys in non-first node to merged frame + // (hidden flys remain and are deleted via DelFrames()) + SwFrameFormats& rTable(*rTextNode.GetDoc().GetSpzFrameFormats()); + SwPageFrame *const pPage(rFrame.FindPageFrame()); + std::vector<sw::Extent>::const_iterator iterFirst(pMerged->extents.begin()); + std::vector<sw::Extent>::const_iterator iter(iterFirst); + SwTextNode const* pNode(pMerged->pFirstNode); + for ( ; ; ++iter) + { + if (iter == pMerged->extents.end() + || iter->pNode != pNode) + { + AddRemoveFlysForNode(rFrame, rTextNode, pSkipped, rTable, pPage, + pNode, iterFirst, iter, + pMerged->pFirstNode, pMerged->pLastNode); + SwNodeOffset const until = iter == pMerged->extents.end() + ? pMerged->pLastNode->GetIndex() + 1 + : iter->pNode->GetIndex(); + for (SwNodeOffset i = pNode->GetIndex() + 1; i < until; ++i) + { + // let's show at-para flys on nodes that contain start/end of + // redline too, even if there's no text there + SwNode const*const pTmp(pNode->GetNodes()[i]); + if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst) + { + AddRemoveFlysForNode(rFrame, rTextNode, pSkipped, + rTable, pPage, pTmp->GetTextNode(), iter, iter, + pMerged->pFirstNode, pMerged->pLastNode); + } + } + if (iter == pMerged->extents.end()) + { + break; + } + pNode = iter->pNode; + iterFirst = iter; + } + } +} + +} // namespace sw + +static void UnHideRedlines(SwRootFrame & rLayout, + SwNodes & rNodes, SwNode const& rEndOfSectionNode, + std::set<SwNodeOffset> *const pSkipped) +{ + assert(rEndOfSectionNode.IsEndNode()); + assert(rNodes[rEndOfSectionNode.StartOfSectionNode()->GetIndex() + 1]->IsCreateFrameWhenHidingRedlines()); // first node is never hidden + for (SwNodeOffset i = rEndOfSectionNode.StartOfSectionNode()->GetIndex() + 1; + i < rEndOfSectionNode.GetIndex(); ++i) + { + SwNode & rNode(*rNodes[i]); + if (rNode.IsTextNode()) // only text nodes are 1st node of a merge + { + SwTextNode & rTextNode(*rNode.GetTextNode()); + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rTextNode); + std::vector<SwTextFrame*> frames; + for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame() == &rLayout) + { + if (pFrame->IsFollow()) + { + frames.push_back(pFrame); + } // when hiding, the loop must remove the anchored flys + else // *before* resetting SetMergedPara anywhere - else + { // the fly deletion code will access multiple of the + // frames with inconsistent MergedPara and assert + frames.insert(frames.begin(), pFrame); + } + } + } + // this messes with pRegisteredIn so do it outside SwIterator + auto eMode(sw::FrameMode::Existing); + for (SwTextFrame * pFrame : frames) + { + if (rLayout.HasMergedParas()) + { + assert(!pFrame->GetMergedPara() || + !rNode.IsCreateFrameWhenHidingRedlines() || + // FIXME: skip this assert in tables with deleted rows + pFrame->IsInTab()); + if (rNode.IsCreateFrameWhenHidingRedlines()) + { + { + auto pMerged(CheckParaRedlineMerge(*pFrame, + rTextNode, eMode)); + pFrame->SetMergedPara(std::move(pMerged)); + } + auto const pMerged(pFrame->GetMergedPara()); + if (pMerged) + { + // invalidate SwInvalidateFlags::Size + pFrame->Prepare(PrepareHint::Clear, nullptr, false); + pFrame->InvalidatePage(); + if (auto const pObjs = pFrame->GetDrawObjs()) + { // also invalidate position of existing flys + // because they may need to be moved + for (auto const pObject : *pObjs) + { + pObject->InvalidateObjPos(); + } + } + } + sw::AddRemoveFlysAnchoredToFrameStartingAtNode(*pFrame, rTextNode, pSkipped); + // only *first* frame of node gets Existing because it + eMode = sw::FrameMode::New; // is not idempotent! + } + } + else + { + if (auto const& pMergedPara = pFrame->GetMergedPara()) + { + // invalidate SwInvalidateFlags::Size + pFrame->Prepare(PrepareHint::Clear, nullptr, false); + pFrame->InvalidatePage(); + if (auto const pObjs = pFrame->GetDrawObjs()) + { // also invalidate position of existing flys + for (auto const pObject : *pObjs) + { + pObject->InvalidateObjPos(); + } + } + // SwFlyAtContentFrame::SwClientNotify() always appends to + // the master frame, so do the same here. + // (RemoveFootnotesForNode must be called at least once) + if (!pFrame->IsFollow()) + { + // the new text frames don't exist yet, so at this point + // we can only delete the footnote frames so they don't + // point to the merged SwTextFrame any more... + assert(&rTextNode == pMergedPara->pFirstNode); + // iterate over nodes, not extents: if a node has + // no extents now but did have extents initially, + // its flys need their frames deleted too! + for (SwNodeOffset j = rTextNode.GetIndex() + 1; + j <= pMergedPara->pLastNode->GetIndex(); ++j) + { + SwNode *const pNode(rTextNode.GetNodes()[j]); + assert(!pNode->IsEndNode()); + if (pNode->IsStartNode()) + { + j = pNode->EndOfSectionIndex(); + } + else if (pNode->IsTextNode()) + { + sw::RemoveFootnotesForNode(rLayout, *pNode->GetTextNode(), nullptr); + // similarly, remove the anchored flys + for (SwFrameFormat * pFormat : pNode->GetAnchoredFlys()) + { + pFormat->DelFrames(/*&rLayout*/); + } + } + } + // rely on AppendAllObjs call at the end to add + // all flys in first node that are hidden + } + pFrame->SetMergedPara(nullptr); + } + } + pFrame->Broadcast(SfxHint()); // notify SwAccessibleParagraph + } + // all nodes, not just merged ones! it may be in the same list as + if (rTextNode.IsNumbered(nullptr)) // a preceding merged one... + { // notify frames so they reformat numbering portions + rTextNode.NumRuleChgd(); + } + } + else if (rNode.IsTableNode() && rLayout.IsHideRedlines()) + { + SwTableNode * pTableNd = rNode.GetTableNode(); + SwPosition const tmp(rNode); + SwRangeRedline const*const pRedline( + rLayout.GetFormat()->GetDoc()->getIDocumentRedlineAccess().GetRedline(tmp, nullptr)); + // pathology: redline that starts on a TableNode; cannot + // be created in UI but by import filters... + if (pRedline + && pRedline->GetType() == RedlineType::Delete + && &pRedline->Start()->nNode.GetNode() == &rNode) + { + for (SwNodeOffset j = rNode.GetIndex(); j <= rNode.EndOfSectionIndex(); ++j) + { + rNode.GetNodes()[j]->SetRedlineMergeFlag(SwNode::Merge::Hidden); + } + pTableNd->DelFrames(&rLayout); + } + else if ( pTableNd->GetTable().HasDeletedRow() ) + { + pTableNd->DelFrames(&rLayout); + if ( !pTableNd->GetTable().IsDeleted() ) + { + SwNodeIndex aIdx( *pTableNd->EndOfSectionNode(), 1 ); + pTableNd->MakeOwnFrames(&aIdx); + } + } + } + else if (rNode.IsTableNode() && !rLayout.IsHideRedlines() && + rNode.GetTableNode()->GetTable().HasDeletedRow() ) + { + SwTableNode * pTableNd = rNode.GetTableNode(); + pTableNd->DelFrames(&rLayout); + SwNodeIndex aIdx( *pTableNd->EndOfSectionNode(), 1 ); + pTableNd->MakeOwnFrames(&aIdx); + } + + if (!rNode.IsCreateFrameWhenHidingRedlines()) + { + if (rLayout.HasMergedParas()) + { + if (rNode.IsContentNode()) + { + // note: nothing to do here, already done +#ifndef NDEBUG + auto const pFrame(static_cast<SwContentNode&>(rNode).getLayoutFrame(&rLayout)); + assert(!pFrame || static_cast<SwTextFrame*>(pFrame)->GetMergedPara()->pFirstNode != &rNode); +#endif + } + } + else + { + assert(!rNode.IsContentNode() || !rNode.GetContentNode()->getLayoutFrame(&rLayout) || + // FIXME: skip this assert in tables with deleted rows + rNode.GetContentNode()->getLayoutFrame(&rLayout)->IsInTab()); + SwNodeOffset j = i + 1; + for ( ; j < rEndOfSectionNode.GetIndex(); ++j) + { + if (rNodes[j]->IsCreateFrameWhenHidingRedlines()) + { + break; + } + } + // call MakeFrames once, because sections/tables + // InsertCnt_ also checks for hidden sections + SwNodeIndex const start(rNodes, i); + SwNodeIndex const end(rNodes, j); + { + sw::FlyCreationSuppressor aSuppressor(false); + ::MakeFrames(rLayout.GetFormat()->GetDoc(), start, end); + } + i = j - 1; // will be incremented again + } + } + } +} + +static void UnHideRedlinesExtras(SwRootFrame & rLayout, + SwNodes & rNodes, SwNode const& rEndOfExtraSectionNode, + std::set<SwNodeOffset> *const pSkipped) +{ + assert(rEndOfExtraSectionNode.IsEndNode()); + for (SwNodeOffset i = rEndOfExtraSectionNode.StartOfSectionNode()->GetIndex() + + 1; i < rEndOfExtraSectionNode.GetIndex(); ++i) + { + SwNode const& rStartNode(*rNodes[i]); + assert(rStartNode.IsStartNode()); + assert(rStartNode.GetRedlineMergeFlag() == SwNode::Merge::None); + SwNode const& rEndNode(*rStartNode.EndOfSectionNode()); + bool bSkip(pSkipped && pSkipped->find(i) != pSkipped->end()); + i = rEndNode.GetIndex(); + for (SwNodeOffset j = rStartNode.GetIndex() + 1; j < i; ++j) + { + // note: SwStartNode has no way to access the frames, so check + // whether the first content-node inside the section has frames + SwNode const& rNode(*rNodes[j]); + if (rNode.IsSectionNode() && + static_cast<SwSectionNode const&>(rNode).GetSection().IsHiddenFlag()) + { // skip hidden sections - they can be inserted in fly-frames :( + j = rNode.EndOfSectionNode()->GetIndex(); + continue; + } + if (rNode.IsContentNode()) + { + SwContentNode const& rCNode(static_cast<SwContentNode const&>(rNode)); + if (!rCNode.getLayoutFrame(&rLayout)) + { // ignore footnote/fly/header/footer with no layout frame + bSkip = true; // they will be created from scratch later if needed + } + break; + } + } + if (!bSkip) + { + UnHideRedlines(rLayout, rNodes, rEndNode, pSkipped); + } + } +} + +static void UnHide(SwRootFrame & rLayout) +{ + assert(rLayout.GetCurrShell()->ActionPend()); // tdf#125754 avoid recursive layout + SwDoc & rDoc(*rLayout.GetFormat()->GetDoc()); + // don't do early return if there are no redlines: + // Show->Hide must init hidden number trees + // Hide->Show may be called after all redlines have been deleted but there + // may still be MergedParas because those aren't deleted yet... +#if 0 + if (!bHideRedlines + && rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) + { + return; + } +#endif + // Hide->Show: clear MergedPara, create frames + // Show->Hide: call CheckParaRedlineMerge, delete frames + // Traverse the document via the nodes-array; traversing via the layout + // wouldn't find the nodes that don't have frames in the ->Show case. + // In-order traversal of each nodes array section should init the flags + // in nodes before they are iterated. + // Actual creation of frames should be done with existing functions + // if possible, particularly InsertCnt_() or its wrapper ::MakeFrames(). + SwNodes /*const*/& rNodes(rDoc.GetNodes()); + // Flys/footnotes: must iterate and find all the ones that already exist + // with frames and have redlines inside them; if any don't have frames at + // all, they will be created (if necessary) from scratch and completely by + // MakeFrames(). + // + // Flys before footnotes: because footnotes may contain flys but not + // vice-versa; alas flys may contain flys, so we skip some of them + // if they have already been created from scratch via their anchor flys. + std::set<SwNodeOffset> skippedFlys; + UnHideRedlinesExtras(rLayout, rNodes, rNodes.GetEndOfAutotext(), + // when un-hiding, delay all fly frame creation to AppendAllObjs below + rLayout.HasMergedParas() ? &skippedFlys : nullptr); + // Footnotes are created automatically (after invalidation etc.) by + // ConnectFootnote(), but need to be deleted manually. Footnotes do not + // occur in flys or headers/footers. + UnHideRedlinesExtras(rLayout, rNodes, rNodes.GetEndOfInserts(), nullptr); + UnHideRedlines(rLayout, rNodes, rNodes.GetEndOfContent(), nullptr); + + if (!rLayout.HasMergedParas()) + { // create all previously hidden flys at once: + // * Flys on first node of pre-existing merged frames that are hidden + // (in delete redline), to be added to the existing frame + // * Flys on non-first (hidden/merged) nodes of pre-existing merged + // frames, to be added to the new frame of their node + // * Flys anchored in other flys that are hidden + AppendAllObjs(rDoc.GetSpzFrameFormats(), &rLayout); + } + + const bool bIsShowChangesInMargin = rLayout.GetCurrShell()->GetViewOptions()->IsShowChangesInMargin(); + for (auto const pRedline : rDoc.getIDocumentRedlineAccess().GetRedlineTable()) + { // DELETE are handled by the code above; for other types, need to + // trigger repaint of text frames to add/remove the redline color font + // (handle deletions showed in margin also here) + if (bIsShowChangesInMargin || pRedline->GetType() != RedlineType::Delete) + { + pRedline->InvalidateRange(SwRangeRedline::Invalidation::Add); + } + } + + SwFootnoteIdxs & rFootnotes(rDoc.GetFootnoteIdxs()); + if (rDoc.GetFootnoteInfo().m_eNum == FTNNUM_CHAPTER) + { + // sadly determining which node is outline node requires hidden layout + rFootnotes.UpdateAllFootnote(); + } + // invalidate all footnotes to reformat their numbers + for (SwTextFootnote *const pFootnote : rFootnotes) + { + SwFormatFootnote const& rFootnote(pFootnote->GetFootnote()); + if (rFootnote.GetNumber() != rFootnote.GetNumberRLHidden() + && rFootnote.GetNumStr().isEmpty()) + { + pFootnote->InvalidateNumberInLayout(); + } + } + // update various fields to re-expand them with the new layout + IDocumentFieldsAccess & rIDFA(rDoc.getIDocumentFieldsAccess()); + auto const pAuthType(rIDFA.GetFieldType( + SwFieldIds::TableOfAuthorities, OUString(), false)); + if (pAuthType) // created on demand... + { // calling DelSequenceArray() should be unnecessary here since the + // sequence doesn't depend on frames + pAuthType->UpdateFields(); + } + rIDFA.GetFieldType(SwFieldIds::RefPageGet, OUString(), false)->UpdateFields(); + rIDFA.GetSysFieldType(SwFieldIds::Chapter)->UpdateFields(); + rIDFA.UpdateExpFields(nullptr, false); + rIDFA.UpdateRefFields(); + + // update SwPostItMgr / notes in the margin + // note: as long as all shells share layout, broadcast to all shells! + rDoc.GetDocShell()->Broadcast( SwFormatFieldHint(nullptr, rLayout.HasMergedParas() + ? SwFormatFieldHintWhich::REMOVED + : SwFormatFieldHintWhich::INSERTED) ); + + +// InvalidateAllContent(SwInvalidateFlags::Size); // ??? TODO what to invalidate? this is the big hammer +} + +void SwRootFrame::SetHideRedlines(bool const bHideRedlines) +{ + if (bHideRedlines == mbHideRedlines) + { + return; + } + // TODO: remove temporary ShowBoth + sw::FieldmarkMode const eMode(m_FieldmarkMode); + if (HasMergedParas()) + { + m_FieldmarkMode = sw::FieldmarkMode::ShowBoth; + mbHideRedlines = false; + UnHide(*this); + } + if (bHideRedlines || eMode != m_FieldmarkMode) + { + m_FieldmarkMode = eMode; + mbHideRedlines = bHideRedlines; + UnHide(*this); + } +} + +void SwRootFrame::SetFieldmarkMode(sw::FieldmarkMode const eMode) +{ + if (eMode == m_FieldmarkMode) + { + return; + } + // TODO: remove temporary ShowBoth + bool const isHideRedlines(mbHideRedlines); + if (HasMergedParas()) + { + mbHideRedlines = false; + m_FieldmarkMode = sw::FieldmarkMode::ShowBoth; + UnHide(*this); + } + if (eMode != sw::FieldmarkMode::ShowBoth || isHideRedlines) + { + mbHideRedlines = isHideRedlines; + m_FieldmarkMode = eMode; + UnHide(*this); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |