diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sw/source/core/text/frmform.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/core/text/frmform.cxx')
-rw-r--r-- | sw/source/core/text/frmform.cxx | 2299 |
1 files changed, 2299 insertions, 0 deletions
diff --git a/sw/source/core/text/frmform.cxx b/sw/source/core/text/frmform.cxx new file mode 100644 index 0000000000..76a1df5621 --- /dev/null +++ b/sw/source/core/text/frmform.cxx @@ -0,0 +1,2299 @@ +/* -*- 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 <IDocumentRedlineAccess.hxx> +#include <anchoredobject.hxx> +#include <bodyfrm.hxx> +#include <hintids.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/hyphenzoneitem.hxx> +#include <pagefrm.hxx> +#include <ndtxt.hxx> +#include <ftnfrm.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <paratr.hxx> +#include <viewopt.hxx> +#include <viewsh.hxx> +#include <frmatr.hxx> +#include <pam.hxx> +#include <fmtanchr.hxx> +#include "itrform2.hxx" +#include "widorp.hxx" +#include "txtcache.hxx" +#include <sectfrm.hxx> +#include <rootfrm.hxx> +#include <frmfmt.hxx> +#include <sortedobjs.hxx> +#include <editeng/tstpitem.hxx> +#include <redline.hxx> +#include <comphelper/lok.hxx> +#include <flyfrms.hxx> +#include <frmtool.hxx> +#include <layouter.hxx> + +// Tolerance in formatting and text output +#define SLOPPY_TWIPS 5 + +namespace { + +class FormatLevel +{ + static sal_uInt16 s_nLevel; +public: + FormatLevel() { ++s_nLevel; } + ~FormatLevel() { --s_nLevel; } + static sal_uInt16 GetLevel() { return s_nLevel; } + static bool LastLevel() { return 10 < s_nLevel; } +}; + +} + +sal_uInt16 FormatLevel::s_nLevel = 0; + +void ValidateText( SwFrame *pFrame ) // Friend of frame +{ + if ( ( ! pFrame->IsVertical() && + pFrame->getFrameArea().Width() == pFrame->GetUpper()->getFramePrintArea().Width() ) || + ( pFrame->IsVertical() && + pFrame->getFrameArea().Height() == pFrame->GetUpper()->getFramePrintArea().Height() ) ) + { + pFrame->setFrameAreaSizeValid(true); + } +} + +void SwTextFrame::ValidateFrame() +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + // Validate surroundings to avoid oscillation + SwSwapIfSwapped swap( this ); + + if ( !IsInFly() && !IsInTab() ) + { // Only validate 'this' when inside a fly, the rest should actually only be + // needed for footnotes, which do not exist in flys. + SwSectionFrame* pSct = FindSctFrame(); + if( pSct ) + { + if( !pSct->IsColLocked() ) + pSct->ColLock(); + else + pSct = nullptr; + } + + SwFrame *pUp = GetUpper(); + pUp->Calc(pRenderContext); + if( pSct ) + pSct->ColUnlock(); + } + ValidateText( this ); + + // We at least have to save the MustFit flag! + assert(HasPara() && "ResetPreps(), missing ParaPortion, SwCache bug?"); + SwParaPortion *pPara = GetPara(); + const bool bMustFit = pPara->IsPrepMustFit(); + ResetPreps(); + pPara->SetPrepMustFit( bMustFit ); +} + +// After a RemoveFootnote the BodyFrame and all Frames contained within it, need to be +// recalculated, so that the DeadLine is right. +// First we search outwards, on the way back we calculate everything. +static void ValidateBodyFrame_( SwFrame *pFrame ) +{ + vcl::RenderContext* pRenderContext = pFrame ? pFrame->getRootFrame()->GetCurrShell()->GetOut() : nullptr; + if( !pFrame || pFrame->IsCellFrame() ) + return; + + if( !pFrame->IsBodyFrame() && pFrame->GetUpper() ) + ValidateBodyFrame_( pFrame->GetUpper() ); + if( !pFrame->IsSctFrame() ) + pFrame->Calc(pRenderContext); + else + { + const bool bOld = static_cast<SwSectionFrame*>(pFrame)->IsContentLocked(); + static_cast<SwSectionFrame*>(pFrame)->SetContentLock( true ); + pFrame->Calc(pRenderContext); + if( !bOld ) + static_cast<SwSectionFrame*>(pFrame)->SetContentLock( false ); + } +} + +void SwTextFrame::ValidateBodyFrame() +{ + SwSwapIfSwapped swap( this ); + + // See comment in ValidateFrame() + if ( !IsInFly() && !IsInTab() && + !( IsInSct() && FindSctFrame()->Lower()->IsColumnFrame() ) ) + ValidateBodyFrame_( GetUpper() ); +} + +bool SwTextFrame::GetDropRect_( SwRect &rRect ) const +{ + SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this)); + + OSL_ENSURE( HasPara(), "SwTextFrame::GetDropRect_: try again next year." ); + SwTextSizeInfo aInf( const_cast<SwTextFrame*>(this) ); + SwTextMargin aLine( const_cast<SwTextFrame*>(this), &aInf ); + if( aLine.GetDropLines() ) + { + rRect.Top( aLine.Y() ); + rRect.Left( aLine.GetLineStart() ); + rRect.Height( aLine.GetDropHeight() ); + rRect.Width( aLine.GetDropLeft() ); + + if ( IsRightToLeft() ) + SwitchLTRtoRTL( rRect ); + + if ( IsVertical() ) + SwitchHorizontalToVertical( rRect ); + return true; + } + + return false; +} + +bool SwTextFrame::CalcFollow(TextFrameIndex const nTextOfst) +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + SwSwapIfSwapped swap( this ); + + OSL_ENSURE( HasFollow(), "CalcFollow: missing Follow." ); + + SwTextFrame* pMyFollow = GetFollow(); + + SwParaPortion *pPara = GetPara(); + const bool bFollowField = pPara && pPara->IsFollowField(); + + if( !pMyFollow->GetOffset() || pMyFollow->GetOffset() != nTextOfst || + bFollowField || pMyFollow->IsFieldFollow() || + ( pMyFollow->IsVertical() && !pMyFollow->getFramePrintArea().Width() ) || + ( ! pMyFollow->IsVertical() && !pMyFollow->getFramePrintArea().Height() ) ) + { +#if OSL_DEBUG_LEVEL > 0 + const SwFrame *pOldUp = GetUpper(); +#endif + + SwRectFnSet aRectFnSet(this); + SwTwips nOldBottom = aRectFnSet.GetBottom(GetUpper()->getFrameArea()); + SwTwips nMyPos = aRectFnSet.GetTop(getFrameArea()); + + const SwPageFrame *pPage = nullptr; + bool bOldInvaContent = true; + if ( !IsInFly() && GetNext() ) + { + pPage = FindPageFrame(); + // Minimize (reset if possible) invalidations: see below + bOldInvaContent = pPage->IsInvalidContent(); + } + + pMyFollow->SetOffset_( nTextOfst ); + pMyFollow->SetFieldFollow( bFollowField ); + if( HasFootnote() || pMyFollow->HasFootnote() ) + { + ValidateFrame(); + ValidateBodyFrame(); + if( pPara ) + { + pPara->GetReformat() = SwCharRange(); + pPara->SetDelta(0); + } + } + + // The footnote area must not get larger + SwSaveFootnoteHeight aSave( FindFootnoteBossFrame( true ), LONG_MAX ); + + pMyFollow->CalcFootnoteFlag(); + if ( !pMyFollow->GetNext() && !pMyFollow->HasFootnote() ) + nOldBottom = aRectFnSet.IsVert() ? 0 : LONG_MAX; + + // tdf#122892 check flag: + // 1. WidowsAndOrphans::FindWidows() determines follow is a widow + // 2. SwTextFrame::PrepWidows() calls SetPrepWidows() on master; + // if it can spare lines, master truncates one line + // 3. SwTextFrame::CalcPreps() on master (below); + // unless IsPrepMustFit(), if master hasn't shrunk via 2., it will SetWidow() + // 4. loop must exit then, because the follow didn't grow so nothing will ever change + while (!IsWidow()) + { + if( !FormatLevel::LastLevel() ) + { + // If the follow is contained within a column section or column + // frame, we need to calculate that first. This is because the + // FormatWidthCols() does not work if it is called from MakeAll + // of the _locked_ follow. + SwSectionFrame* pSct = pMyFollow->FindSctFrame(); + if( pSct && !pSct->IsAnLower( this ) ) + { + if( pSct->GetFollow() ) + pSct->SimpleFormat(); + else if( ( pSct->IsVertical() && !pSct->getFrameArea().Width() ) || + ( ! pSct->IsVertical() && !pSct->getFrameArea().Height() ) ) + break; + } + // i#11760 - Intrinsic format of follow is controlled. + if ( FollowFormatAllowed() ) + { + // i#11760 - No nested format of follows, if + // text frame is contained in a column frame. + // Thus, forbid intrinsic format of follow. + { + bool bIsFollowInColumn = false; + SwFrame* pFollowUpper = pMyFollow->GetUpper(); + while ( pFollowUpper ) + { + if ( pFollowUpper->IsColumnFrame() ) + { + bIsFollowInColumn = true; + break; + } + if ( pFollowUpper->IsPageFrame() || + pFollowUpper->IsFlyFrame() ) + { + break; + } + pFollowUpper = pFollowUpper->GetUpper(); + } + if ( bIsFollowInColumn ) + { + pMyFollow->ForbidFollowFormat(); + } + } + + pMyFollow->Calc(pRenderContext); + // The Follow can tell from its getFrameArea().Height() that something went wrong + OSL_ENSURE( !pMyFollow->GetPrev(), "SwTextFrame::CalcFollow: cheesy follow" ); + if( pMyFollow->GetPrev() ) + { + pMyFollow->Prepare(); + pMyFollow->Calc(pRenderContext); + OSL_ENSURE( !pMyFollow->GetPrev(), "SwTextFrame::CalcFollow: very cheesy follow" ); + } + + // i#11760 - Reset control flag for follow format. + pMyFollow->AllowFollowFormat(); + } + + // Make sure that the Follow gets painted + pMyFollow->SetCompletePaint(); + } + + pPara = GetPara(); + // As long as the Follow requests lines due to Orphans, it is + // passed these and is formatted again if possible + if( pPara && pPara->IsPrepWidows() ) + CalcPreps(); + else + break; + } + + if( HasFootnote() || pMyFollow->HasFootnote() ) + { + ValidateBodyFrame(); + ValidateFrame(); + if( pPara ) + { + pPara->GetReformat() = SwCharRange(); + pPara->SetDelta(0); + } + } + + if ( pPage && !bOldInvaContent ) + pPage->ValidateContent(); + +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( pOldUp == GetUpper(), "SwTextFrame::CalcFollow: heavy follow" ); +#endif + + const tools::Long nRemaining = + - aRectFnSet.BottomDist( GetUpper()->getFrameArea(), nOldBottom ); + if ( nRemaining > 0 && + nRemaining != ( aRectFnSet.IsVert() ? + nMyPos - getFrameArea().Right() : + getFrameArea().Top() - nMyPos ) ) + { + return true; + } + } + + return false; +} + +void SwTextFrame::MakePos() +{ + Point aOldPos = getFrameArea().Pos(); + SwFrame::MakePos(); + + // Recalc split flys if our position changed. + if (aOldPos != getFrameArea().Pos()) + { + // Find the master frame. + const SwTextFrame* pMaster = this; + while (pMaster->IsFollow()) + { + pMaster = pMaster->FindMaster(); + } + // Find which flys are effectively anchored to this frame. + for (const auto& pFly : pMaster->GetSplitFlyDrawObjs()) + { + SwTextFrame* pFlyAnchor = pFly->FindAnchorCharFrame(); + if (pFlyAnchor != this) + { + continue; + } + // Possibly this fly was positioned relative to us, invalidate its position now that our + // position is changed. + SwPageFrame* pPageFrame = pFly->FindPageFrame(); + bool bFlyNeedsPositioning = false; + bool bFlyPageMismatch = false; + if (pPageFrame) + { + // Was the position just adjusted to be inside the page frame? + bFlyNeedsPositioning = pFly->getFrameArea().Pos() == pPageFrame->getFrameArea().Pos(); + // Is the fly on a page different than the anchor frame? + bFlyPageMismatch = pPageFrame != FindPageFrame(); + } + if (bFlyNeedsPositioning || bFlyPageMismatch) + { + // Not really positioned, unlock the position once to allow a recalc. + pFly->UnlockPosition(); + } + pFly->InvalidatePos(); + } + } + + // Inform LOK clients about change in position of redlines (if any) + if(!comphelper::LibreOfficeKit::isActive()) + return; + + SwTextNode const* pTextNode = GetTextNodeFirst(); + const SwRedlineTable& rTable = pTextNode->getIDocumentRedlineAccess().GetRedlineTable(); + for (SwRedlineTable::size_type nRedlnPos = 0; nRedlnPos < rTable.size(); ++nRedlnPos) + { + SwRangeRedline* pRedln = rTable[nRedlnPos]; + if (pTextNode->GetIndex() == pRedln->GetPoint()->GetNode().GetIndex()) + { + pRedln->MaybeNotifyRedlinePositionModification(getFrameArea().Top()); + if (GetMergedPara() + && pRedln->GetType() == RedlineType::Delete + && pRedln->GetPoint()->GetNode() != pRedln->GetMark()->GetNode()) + { + pTextNode = pRedln->End()->GetNode().GetTextNode(); + } + } + } +} + +void SwTextFrame::AdjustFrame( const SwTwips nChgHght, bool bHasToFit ) +{ + vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut(); + if( IsUndersized() ) + { + if( GetOffset() && !IsFollow() ) // A scrolled paragraph (undersized) + return; + SetUndersized( nChgHght == 0 || bHasToFit ); + } + + // AdjustFrame is called with a swapped frame during + // formatting but the frame is not swapped during FormatEmpty + SwSwapIfSwapped swap( this ); + SwRectFnSet aRectFnSet(this); + + // The Frame's size variable is incremented by Grow or decremented by Shrink. + // If the size cannot change, nothing should happen! + if( nChgHght >= 0) + { + SwTwips nChgHeight = nChgHght; + if( nChgHght && !bHasToFit ) + { + if( IsInFootnote() && !IsInSct() ) + { + SwTwips nReal = Grow( nChgHght, true ); + if( nReal < nChgHght ) + { + SwTwips nBot = aRectFnSet.YInc( aRectFnSet.GetBottom(getFrameArea()), + nChgHght - nReal ); + SwFrame* pCont = FindFootnoteFrame()->GetUpper(); + + if( aRectFnSet.BottomDist( pCont->getFrameArea(), nBot ) > 0 ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, nChgHght ); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + + if( aRectFnSet.IsVert() ) + { + aPrt.AddWidth(nChgHght ); + } + else + { + aPrt.AddHeight(nChgHght ); + } + + return; + } + } + } + + Grow( nChgHght ); + + if ( IsInFly() ) + { + // If one of the Upper is a Fly, it's very likely that this fly changes its + // position by the Grow. Therefore, my position has to be corrected also or + // the check further down is not meaningful. + // The predecessors need to be calculated, so that the position can be + // calculated correctly. + if ( GetPrev() ) + { + SwFrame *pPre = GetUpper()->Lower(); + do + { pPre->Calc(pRenderContext); + pPre = pPre->GetNext(); + } while ( pPre && pPre != this ); + } + const Point aOldPos( getFrameArea().Pos() ); + MakePos(); + if ( aOldPos != getFrameArea().Pos() ) + { + InvalidateObjs(false); + } + } + nChgHeight = 0; + } + // A Grow() is always accepted by the Layout, even if the + // FixSize of the surrounding layout frame should not allow it. + // We text for this case and correct the values. + // The Frame must NOT be shrunk further than its size permits + // even in the case of an emergency. + SwTwips nRstHeight; + if ( IsVertical() ) + { + OSL_ENSURE( ! IsSwapped(),"Swapped frame while calculating nRstHeight" ); + + if ( IsVertLR() ) + nRstHeight = GetUpper()->getFrameArea().Left() + + GetUpper()->getFramePrintArea().Left() + + GetUpper()->getFramePrintArea().Width() + - getFrameArea().Left(); + else + nRstHeight = getFrameArea().Left() + getFrameArea().Width() - + ( GetUpper()->getFrameArea().Left() + GetUpper()->getFramePrintArea().Left() ); + } + else + nRstHeight = GetUpper()->getFrameArea().Top() + + GetUpper()->getFramePrintArea().Top() + + GetUpper()->getFramePrintArea().Height() + - getFrameArea().Top(); + + // We can get a bit of space in table cells, because there could be some + // left through a vertical alignment to the top. + // Assure that first lower in upper is the current one or is valid. + if ( IsInTab() && + ( GetUpper()->Lower() == this || + GetUpper()->Lower()->isFrameAreaDefinitionValid() ) ) + { + tools::Long nAdd = aRectFnSet.YDiff( aRectFnSet.GetTop(GetUpper()->Lower()->getFrameArea()), + aRectFnSet.GetPrtTop(*GetUpper()) ); + OSL_ENSURE( nAdd >= 0, "Ey" ); + nRstHeight += nAdd; + } + + // nRstHeight < 0 means that the TextFrame is located completely outside of its Upper. + // This can happen, if it's located within a FlyAtContentFrame, which changed sides by a + // Grow(). In such a case, it's wrong to execute the following Grow(). + // In the case of a bug, we end up with an infinite loop. + SwTwips nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + SwTwips nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + + if( nRstHeight < nFrameHeight ) + { + // It can be that I have the right size, but the Upper is too small and can get me some room + if( ( nRstHeight >= 0 || ( IsInFootnote() && IsInSct() ) ) && !bHasToFit ) + nRstHeight += GetUpper()->Grow( nFrameHeight - nRstHeight ); + // In column sections we do not want to get too big or else more areas are created by + // GetNextSctLeaf. Instead, we shrink and remember bUndersized, so that FormatWidthCols + // can calculate the right column size. + if ( nRstHeight < nFrameHeight ) + { + if( bHasToFit || !IsMoveable() || + ( IsInSct() && !FindSctFrame()->MoveAllowed(this) ) ) + { + SetUndersized( true ); + Shrink( std::min( ( nFrameHeight - nRstHeight), nPrtHeight ) ); + } + else + SetUndersized( false ); + } + } + else if( nChgHeight ) + { + if( nRstHeight - nFrameHeight < nChgHeight ) + nChgHeight = nRstHeight - nFrameHeight; + if( nChgHeight ) + Grow( nChgHeight ); + } + } + else + Shrink( -nChgHght ); +} + +css::uno::Sequence< css::style::TabStop > SwTextFrame::GetTabStopInfo( SwTwips CurrentPos ) +{ + SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this ); + SwTextFormatter aLine( this, &aInf ); + SwTextCursor TextCursor( this, &aInf ); + const Point aCharPos( TextCursor.GetTopLeft() ); + + SwTwips nRight = aLine.Right(); + CurrentPos -= aCharPos.X(); + + // get current tab stop information stored in the Frame + const SvxTabStop *pTS = aLine.GetLineInfo().GetTabStop( CurrentPos, nRight ); + + if( !pTS ) + { + return {}; + } + + // copy tab stop information into a Sequence, which only contains one element. + css::style::TabStop ts; + ts.Position = pTS->GetTabPos(); + ts.DecimalChar = pTS->GetDecimal(); + ts.FillChar = pTS->GetFill(); + switch( pTS->GetAdjustment() ) + { + case SvxTabAdjust::Left : ts.Alignment = css::style::TabAlign_LEFT; break; + case SvxTabAdjust::Center : ts.Alignment = css::style::TabAlign_CENTER; break; + case SvxTabAdjust::Right : ts.Alignment = css::style::TabAlign_RIGHT; break; + case SvxTabAdjust::Decimal: ts.Alignment = css::style::TabAlign_DECIMAL; break; + case SvxTabAdjust::Default: ts.Alignment = css::style::TabAlign_DEFAULT; break; + default: break; // prevent warning + } + + return { ts }; +} + +// AdjustFollow expects the following situation: +// The SwTextIter points to the lower end of the Master, the Offset is set in the Follow. +// nOffset holds the Offset in the text string, from which the Master closes +// and the Follow starts. +// If it's 0, the FollowFrame is deleted. +void SwTextFrame::AdjustFollow_( SwTextFormatter &rLine, + const TextFrameIndex nOffset, const TextFrameIndex nEnd, + const sal_uInt8 nMode ) +{ + SwFrameSwapper aSwapper( this, false ); + + // We got the rest of the text mass: Delete all Follows + // DummyPortions() are a special case. + // Special cases are controlled by parameter <nMode>. + bool bDontJoin = nMode & 1; + if( HasFollow() && !bDontJoin && nOffset == nEnd ) + { + while( GetFollow() ) + { + if( GetFollow()->IsLocked() ) + { + // this can happen when follow calls pMaster->GetFormatted() + SAL_INFO("sw.core", "+SwTextFrame::JoinFrame: Follow is locked." ); + return; + } + if (GetFollow()->IsDeleteForbidden()) + return; + + if (HasNonLastSplitFlyDrawObj()) + { + // If a fly frame is anchored to us that has a follow, then don't join the anchor. + // First those fly frames have to be joined. + return; + } + + JoinFrame(); + } + + return; + } + + // Dancing on the volcano: We'll just format the last line quickly + // for the QuoVadis stuff. + // The Offset can move of course: + const TextFrameIndex nNewOfst = (IsInFootnote() && (!GetIndNext() || HasFollow())) + ? rLine.FormatQuoVadis(nOffset) : nOffset; + + bool bHasNonLastSplitFlyDrawObj = false; + if (GetFollow() && GetOffset() == GetFollow()->GetOffset()) + { + bHasNonLastSplitFlyDrawObj = HasNonLastSplitFlyDrawObj(); + } + + if( !bDontJoin ) + { + // We steal text mass from our Follows + // It can happen that we have to join some of them + while( GetFollow() && GetFollow()->GetFollow() && + nNewOfst >= GetFollow()->GetFollow()->GetOffset() ) + { + if (bHasNonLastSplitFlyDrawObj) + { + // A non-last split fly is anchored to us, don't move content from the last frame to + // this one and don't join. + return; + } + + JoinFrame(); + } + } + + if (IsEmptyMasterWithSplitFly()) + { + // A split fly is anchored to us, don't move content from the follow frame to this one. + return; + } + + // The Offset moved + if( GetFollow() ) + { + if (!bDontJoin && bHasNonLastSplitFlyDrawObj) + { + // A non-last split fly is anchored to us, our follow is the last one in the text frame + // chain. No move of text from that follow to this text frame. + return; + } + + if ( nMode ) + GetFollow()->ManipOfst(TextFrameIndex(0)); + + if ( CalcFollow( nNewOfst ) ) // CalcFollow only at the end, we do a SetOffset there + rLine.SetOnceMore( true ); + } +} + +SwContentFrame *SwTextFrame::JoinFrame() +{ + OSL_ENSURE( GetFollow(), "+SwTextFrame::JoinFrame: no follow" ); + SwTextFrame *pFoll = GetFollow(); + + SwTextFrame *pNxt = pFoll->GetFollow(); + + // All footnotes of the to-be-destroyed Follow are relocated to us + TextFrameIndex nStart = pFoll->GetOffset(); + if ( pFoll->HasFootnote() ) + { + SwFootnoteBossFrame *pFootnoteBoss = nullptr; + SwFootnoteBossFrame *pEndBoss = nullptr; + SwTextNode const* pNode(nullptr); + sw::MergedAttrIter iter(*pFoll); + for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode)) + { + if (RES_TXTATR_FTN == pHt->Which() + && nStart <= pFoll->MapModelToView(pNode, pHt->GetStart())) + { + if (pHt->GetFootnote().IsEndNote()) + { + if (!pEndBoss) + pEndBoss = pFoll->FindFootnoteBossFrame(); + SwFootnoteBossFrame::ChangeFootnoteRef( pFoll, static_cast<const SwTextFootnote*>(pHt), this ); + } + else + { + if (!pFootnoteBoss) + pFootnoteBoss = pFoll->FindFootnoteBossFrame( true ); + SwFootnoteBossFrame::ChangeFootnoteRef( pFoll, static_cast<const SwTextFootnote*>(pHt), this ); + } + SetFootnote( true ); + } + } + } + +#ifdef DBG_UTIL + else if ( pFoll->isFramePrintAreaValid() || + pFoll->isFrameAreaSizeValid() ) + { + pFoll->CalcFootnoteFlag(); + OSL_ENSURE( !pFoll->HasFootnote(), "Missing FootnoteFlag." ); + } +#endif + + pFoll->MoveFlyInCnt( this, nStart, TextFrameIndex(COMPLETE_STRING) ); + pFoll->SetFootnote( false ); + // i#27138 + // 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, which + // is <this>, will change. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + { + SwViewShell* pViewShell( pFoll->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + auto pNext = pFoll->FindNextCnt( true ); + pViewShell->InvalidateAccessibleParaFlowRelation( + pNext ? pNext->DynCastTextFrame() : nullptr, + this ); + } + } +#endif + + pFoll->Cut(); + SetFollow(pNxt); + SwFrame::DestroyFrame(pFoll); + return pNxt; +} + +void SwTextFrame::SplitFrame(TextFrameIndex const nTextPos) +{ + SwSwapIfSwapped swap( this ); + + // The Paste sends a Modify() to me + // I lock myself, so that my data does not disappear + TextFrameLockGuard aLock( this ); + SwTextFrame *const pNew = static_cast<SwTextFrame *>(GetTextNodeFirst()->MakeFrame(this)); + + pNew->SetFollow( GetFollow() ); + SetFollow( pNew ); + + pNew->Paste( GetUpper(), GetNext() ); + // i#27138 + // 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, which + // is <this>, will change. +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + { + SwViewShell* pViewShell( pNew->getRootFrame()->GetCurrShell() ); + if ( pViewShell && pViewShell->GetLayout() && + pViewShell->GetLayout()->IsAnyShellAccessible() ) + { + auto pNext = pNew->FindNextCnt( true ); + pViewShell->InvalidateAccessibleParaFlowRelation( + pNext ? pNext->DynCastTextFrame() : nullptr, + this ); + } + } +#endif + + // If footnotes end up in pNew bz our actions, we need + // to re-register them + if ( HasFootnote() ) + { + SwFootnoteBossFrame *pFootnoteBoss = nullptr; + SwFootnoteBossFrame *pEndBoss = nullptr; + SwTextNode const* pNode(nullptr); + sw::MergedAttrIter iter(*this); + for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode)) + { + if (RES_TXTATR_FTN == pHt->Which() + && nTextPos <= MapModelToView(pNode, pHt->GetStart())) + { + if (pHt->GetFootnote().IsEndNote()) + { + if (!pEndBoss) + pEndBoss = FindFootnoteBossFrame(); + SwFootnoteBossFrame::ChangeFootnoteRef( this, static_cast<const SwTextFootnote*>(pHt), pNew ); + } + else + { + if (!pFootnoteBoss) + pFootnoteBoss = FindFootnoteBossFrame( true ); + SwFootnoteBossFrame::ChangeFootnoteRef( this, static_cast<const SwTextFootnote*>(pHt), pNew ); + } + pNew->SetFootnote( true ); + } + } + } + +#ifdef DBG_UTIL + else + { + CalcFootnoteFlag( nTextPos - TextFrameIndex(1) ); + OSL_ENSURE( !HasFootnote(), "Missing FootnoteFlag." ); + } +#endif + + MoveFlyInCnt( pNew, nTextPos, TextFrameIndex(COMPLETE_STRING) ); + + // No SetOffset or CalcFollow, because an AdjustFollow follows immediately anyways + + pNew->ManipOfst( nTextPos ); +} + +void SwTextFrame::SetOffset_(TextFrameIndex const nNewOfst) +{ + // We do not need to invalidate our Follow. + // We are a Follow, get formatted right away and call + // SetOffset() from there + mnOffset = nNewOfst; + SwParaPortion *pPara = GetPara(); + if( pPara ) + { + SwCharRange &rReformat = pPara->GetReformat(); + rReformat.Start() = TextFrameIndex(0); + rReformat.Len() = TextFrameIndex(GetText().getLength()); + pPara->SetDelta(sal_Int32(rReformat.Len())); + } + InvalidateSize(); +} + +bool SwTextFrame::CalcPreps() +{ + OSL_ENSURE( ! IsVertical() || ! IsSwapped(), "SwTextFrame::CalcPreps with swapped frame" ); + SwRectFnSet aRectFnSet(this); + + SwParaPortion *pPara = GetPara(); + if ( !pPara ) + return false; + const bool bPrep = pPara->IsPrep(); + const bool bPrepWidows = pPara->IsPrepWidows(); + const bool bPrepAdjust = pPara->IsPrepAdjust(); + const bool bPrepMustFit = pPara->IsPrepMustFit(); + ResetPreps(); + + bool bRet = false; + if( bPrep && !pPara->GetReformat().Len() ) + { + // PrepareHint::Widows means that the orphans rule got activated in the Follow. + // In unfortunate cases we could also have a PrepAdjust! + if( bPrepWidows ) + { + if( !GetFollow() ) + { + OSL_ENSURE( GetFollow(), "+SwTextFrame::CalcPreps: no credits" ); + return false; + } + + // We need to prepare for two cases: + // We were able to hand over a few lines to the Follow + // -> we need to shrink + // or we need to go on the next page + // -> we let our Frame become too big + + SwTwips nChgHeight = GetParHeight(); + if( nChgHeight >= aRectFnSet.GetHeight(getFramePrintArea()) ) + { + if( bPrepMustFit ) + { + GetFollow()->SetJustWidow( true ); + GetFollow()->Prepare(); + } + else if ( aRectFnSet.IsVert() ) + { + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Width( aFrm.Width() + aFrm.Left() ); + aFrm.Left( 0 ); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( aPrt.Width() + getFrameArea().Left() ); + } + + SetWidow( true ); + } + else + { + // nTmp should be very large, but not so large as to cause overflow later (e.g., + // GetFrameOfModify in sw/source/core/layout/frmtool.cxx calculates nCurrentDist + // from, among others, the square of aDiff.getY(), which can be close to nTmp); + // the previously used value TWIPS_MAX/2 (i.e., (LONG_MAX - 1)/2) depended on + // the range of 'long', while the value (SAL_MAX_INT32 - 1)/2 (which matches the + // old value on platforms where 'long' is 'sal_Int32') is empirically shown to + // be large enough in practice even on platforms where 'long' is 'sal_Int64': + SwTwips const nTmp = sw::WIDOW_MAGIC - (getFrameArea().Top()+10000); + SwTwips nDiff = nTmp - getFrameArea().Height(); + + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aFrm.Height( nTmp ); + } + + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( aPrt.Height() + nDiff ); + } + + SetWidow( true ); + } + } + else + { + OSL_ENSURE( nChgHeight < aRectFnSet.GetHeight(getFramePrintArea()), + "+SwTextFrame::CalcPrep: want to shrink" ); + + nChgHeight = aRectFnSet.GetHeight(getFramePrintArea()) - nChgHeight; + + GetFollow()->SetJustWidow( true ); + GetFollow()->Prepare(); + Shrink( nChgHeight ); + SwRect &rRepaint = pPara->GetRepaint(); + + if ( aRectFnSet.IsVert() ) + { + SwRect aRepaint( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() ); + SwitchVerticalToHorizontal( aRepaint ); + rRepaint.Chg( aRepaint.Pos(), aRepaint.SSize() ); + } + else + rRepaint.Chg( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() ); + + if( 0 >= rRepaint.Width() ) + rRepaint.Width(1); + } + bRet = true; + } + else if ( bPrepAdjust ) + { + if ( HasFootnote() ) + { + if( !CalcPrepFootnoteAdjust() ) + { + if( bPrepMustFit ) + { + SwTextLineAccess aAccess( this ); + aAccess.GetPara()->SetPrepMustFit(true); + } + return false; + } + } + + { + SwSwapIfNotSwapped swap( this ); + + SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this ); + SwTextFormatter aLine( this, &aInf ); + + WidowsAndOrphans aFrameBreak( this ); + // Whatever the attributes say: we split the paragraph in + // MustFit case if necessary + if( bPrepMustFit ) + { + aFrameBreak.SetKeep( false ); + aFrameBreak.ClrOrphLines(); + } + // Before calling FormatAdjust, we need to make sure + // that the lines protruding at the bottom get indeed + // truncated + bool bBreak = aFrameBreak.IsBreakNowWidAndOrp( aLine ); + bRet = true; + while( !bBreak && aLine.Next() ) + { + bBreak = aFrameBreak.IsBreakNowWidAndOrp( aLine ); + } + if( bBreak ) + { + // We run into troubles: when TruncLines is called, the + // conditions in IsInside change immediately such that + // IsBreakNow can return different results. + // For this reason, we tell rFrameBreak that the + // end is reached at the location of rLine. + // Let's see if it works ... + aLine.TruncLines(); + aFrameBreak.SetRstHeight( aLine ); + FormatAdjust( aLine, aFrameBreak, TextFrameIndex(aInf.GetText().getLength()), aInf.IsStop() ); + } + else + { + if( !GetFollow() ) + { + FormatAdjust( aLine, aFrameBreak, + TextFrameIndex(aInf.GetText().getLength()), aInf.IsStop() ); + } + else if ( !aFrameBreak.IsKeepAlways() ) + { + // We delete a line before the Master, because the Follow + // could hand over a line + const SwCharRange aFollowRg(GetFollow()->GetOffset(), TextFrameIndex(1)); + pPara->GetReformat() += aFollowRg; + // We should continue! + bRet = false; + } + } + } + + // A final check, if FormatAdjust() didn't help we need to + // truncate + if( bPrepMustFit ) + { + const SwTwips nMust = aRectFnSet.GetPrtBottom(*GetUpper()); + const SwTwips nIs = aRectFnSet.GetBottom(getFrameArea()); + + if( aRectFnSet.IsVert() && nIs < nMust ) + { + Shrink( nMust - nIs ); + + if( getFramePrintArea().Width() < 0 ) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Width( 0 ); + } + + SetUndersized( true ); + } + else if ( ! aRectFnSet.IsVert() && nIs > nMust ) + { + Shrink( nIs - nMust ); + + if( getFramePrintArea().Height() < 0 ) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aPrt.Height( 0 ); + } + + SetUndersized( true ); + } + } + } + } + pPara->SetPrepMustFit( bPrepMustFit ); + return bRet; +} + +// Move the as-character objects - footnotes must be moved by RemoveFootnote! +void SwTextFrame::ChangeOffset( SwTextFrame* pFrame, TextFrameIndex nNew ) +{ + if( pFrame->GetOffset() < nNew ) + pFrame->MoveFlyInCnt( this, TextFrameIndex(0), nNew ); + else if( pFrame->GetOffset() > nNew ) + MoveFlyInCnt( pFrame, nNew, TextFrameIndex(COMPLETE_STRING) ); +} + +void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, + WidowsAndOrphans &rFrameBreak, + TextFrameIndex const nStrLen, + const bool bDummy ) +{ + SwSwapIfNotSwapped swap( this ); + + SwParaPortion *pPara = rLine.GetInfo().GetParaPortion(); + + TextFrameIndex nEnd = rLine.GetStart(); + + const bool bHasToFit = pPara->IsPrepMustFit(); + + // The StopFlag is set by footnotes which want to go onto the next page + // Call base class method <SwTextFrameBreak::IsBreakNow(..)> + // instead of method <WidowsAndOrphans::IsBreakNow(..)> to get a break, + // even if due to widow rule no enough lines exists. + sal_uInt8 nNew = ( !GetFollow() && + nEnd < nStrLen && + ( rLine.IsStop() || + ( bHasToFit + ? ( rLine.GetLineNr() > 1 && + !rFrameBreak.IsInside( rLine ) ) + : rFrameBreak.IsBreakNow( rLine ) ) ) ) + ? 1 : 0; + + SwTextFormatInfo& rInf = rLine.GetInfo(); + if (nNew == 0 && !nStrLen && !rInf.GetTextFly().IsOn() && IsEmptyWithSplitFly()) + { + // Empty paragraph, so IsBreakNow() is not called, but we should split the fly portion and + // the paragraph marker. + nNew = 1; + } + + // i#84870 + // no split of text frame, which only contains an as-character anchored object + bool bOnlyContainsAsCharAnchoredObj = + !IsFollow() && nStrLen == TextFrameIndex(1) && + GetDrawObjs() && GetDrawObjs()->size() == 1 && + (*GetDrawObjs())[0]->GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR; + + // Still try split text frame if we have columns. + if (FindColFrame()) + bOnlyContainsAsCharAnchoredObj = false; + + if ( nNew && bOnlyContainsAsCharAnchoredObj ) + { + nNew = 0; + } + + if ( nNew ) + { + SplitFrame( nEnd ); + } + + const SwFrame *pBodyFrame = FindBodyFrame(); + + const tools::Long nBodyHeight = pBodyFrame ? ( IsVertical() ? + pBodyFrame->getFrameArea().Width() : + pBodyFrame->getFrameArea().Height() ) : 0; + + // If the current values have been calculated, show that they + // are valid now + pPara->GetReformat() = SwCharRange(); + bool bDelta = pPara->GetDelta() != 0; + pPara->SetDelta(0); + + if( rLine.IsStop() ) + { + rLine.TruncLines( true ); + nNew = 1; + } + + // FindBreak truncates the last line + if( !rFrameBreak.FindBreak( this, rLine, bHasToFit ) ) + { + // If we're done formatting, we set nEnd to the end. + // AdjustFollow might execute JoinFrame() because of this. + // Else, nEnd is the end of the last line in the Master. + TextFrameIndex nOld = nEnd; + // Make sure content from the last floating table anchor is not shifted to previous anchors. + if (!HasNonLastSplitFlyDrawObj()) + { + nEnd = rLine.GetEnd(); + } + if( GetFollow() ) + { + if( nNew && nOld < nEnd ) + RemoveFootnote( nOld, nEnd - nOld ); + ChangeOffset( GetFollow(), nEnd ); + if( !bDelta ) + GetFollow()->ManipOfst( nEnd ); + } + } + else + { // If we pass over lines, we must not call Join in Follows, instead we even + // need to create a Follow. + // We also need to do this if the whole mass of text remains in the Master, + // because a hard line break could necessitate another line (without text mass)! + TextFrameIndex const nOld(nEnd); + nEnd = rLine.GetEnd(); + if( GetFollow() ) + { + // Another case for not joining the follow: + // Text frame has no content, but a numbering. Then, do *not* join. + // Example of this case: When an empty, but numbered paragraph + // at the end of page is completely displaced by a fly frame. + // Thus, the text frame introduced a follow by a + // <SwTextFrame::SplitFrame(..)> - see below. The follow then shows + // the numbering and must stay. + if ( GetFollow()->GetOffset() != nEnd || + GetFollow()->IsFieldFollow() || + (nStrLen == TextFrameIndex(0) && GetTextNodeForParaProps()->GetNumRule())) + { + nNew |= 3; + } + else if (FindTabFrame() && nEnd > TextFrameIndex(0) && + rLine.GetInfo().GetChar(nEnd - TextFrameIndex(1)) == CH_BREAK) + { + // We are in a table, the paragraph has a follow and the text + // ends with a hard line break. Don't join the follow just + // because the follow would have no content, we may still need it + // for the paragraph mark. + nNew |= 1; + } + // move footnotes if the follow is kept - if RemoveFootnote() is + // called in next format iteration, it will be with the *new* + // offset so no effect! + if (nNew && nOld < nEnd) + { + RemoveFootnote(nOld, nEnd - nOld); + } + ChangeOffset( GetFollow(), nEnd ); + + SwFlyAtContentFrame* pNonLastSplitFlyDrawObj = HasNonLastSplitFlyDrawObj(); + if (pNonLastSplitFlyDrawObj && !pNonLastSplitFlyDrawObj->IsWrapOnAllPages()) + { + // Make sure content from the last floating table anchor is not shifted to previous + // anchors, unless we're in the special "wrap on all pages" mode. + nEnd = TextFrameIndex(0); + } + + GetFollow()->ManipOfst( nEnd ); + } + else + { + const SwTextNode* pTextNode = GetTextNodeForParaProps(); + bool bHasVisibleNumRule = nStrLen == TextFrameIndex(0) && pTextNode->GetNumRule(); + + if (!pTextNode->HasVisibleNumberingOrBullet()) + { + bHasVisibleNumRule = false; + } + + // Only split frame, if the frame contains + // content or contains no content, but has a numbering. + // i#84870 - No split, if text frame only contains one + // as-character anchored object. + if ( !bOnlyContainsAsCharAnchoredObj && + (nStrLen > TextFrameIndex(0) || + bHasVisibleNumRule ) + ) + { + SplitFrame( nEnd ); + nNew |= 3; + } + } + // If the remaining height changed e.g by RemoveFootnote() we need to + // fill up in order to avoid oscillation. + if( bDummy && pBodyFrame && + nBodyHeight < ( IsVertical() ? + pBodyFrame->getFrameArea().Width() : + pBodyFrame->getFrameArea().Height() ) ) + rLine.MakeDummyLine(); + } + + // In AdjustFrame() we set ourselves via Grow/Shrink + // In AdjustFollow() we set our FollowFrame + + const SwTwips nDocPrtTop = getFrameArea().Top() + getFramePrintArea().Top(); + const SwTwips nOldHeight = getFramePrintArea().SSize().Height(); + SwTwips nChg = rLine.CalcBottomLine() - nDocPrtTop - nOldHeight; + + //#i84870# - no shrink of text frame, if it only contains one as-character anchored object. + if ( nChg < 0 && !bDelta && bOnlyContainsAsCharAnchoredObj ) + { + nChg = 0; + } + + // Vertical Formatting: + // The (rotated) repaint rectangle's x coordinate refers to the frame. + // If the frame grows (or shirks) the repaint rectangle cannot simply + // be rotated back after formatting, because we use the upper left point + // of the frame for rotation. This point changes when growing/shrinking. + + if ( IsVertical() && !IsVertLR() && nChg ) + { + SwRect &rRepaint = pPara->GetRepaint(); + rRepaint.Left( rRepaint.Left() - nChg ); + rRepaint.Width( rRepaint.Width() - nChg ); + } + + AdjustFrame( nChg, bHasToFit ); + + if( HasFollow() || IsInFootnote() ) + AdjustFollow_( rLine, nEnd, nStrLen, nNew ); + + pPara->SetPrepMustFit( false ); +} + +// bPrev is set whether Reformat.Start() was called because of Prev(). +// Else, we don't know whether we can limit the repaint or not. +bool SwTextFrame::FormatLine( SwTextFormatter &rLine, const bool bPrev ) +{ + OSL_ENSURE( ! IsVertical() || IsSwapped(), + "SwTextFrame::FormatLine( rLine, bPrev) with unswapped frame" ); + SwParaPortion *pPara = rLine.GetInfo().GetParaPortion(); + const SwLineLayout *pOldCur = rLine.GetCurr(); + const TextFrameIndex nOldLen = pOldCur->GetLen(); + const SwTwips nOldAscent = pOldCur->GetAscent(); + const SwTwips nOldHeight = pOldCur->Height(); + const SwTwips nOldWidth = pOldCur->Width() + pOldCur->GetHangingMargin(); + const bool bOldHyph = pOldCur->IsEndHyph(); + SwTwips nOldTop = 0; + SwTwips nOldBottom = 0; + if( rLine.GetCurr()->IsClipping() ) + rLine.CalcUnclipped( nOldTop, nOldBottom ); + + TextFrameIndex const nNewStart = rLine.FormatLine( rLine.GetStart() ); + + OSL_ENSURE( getFrameArea().Pos().Y() + getFramePrintArea().Pos().Y() == rLine.GetFirstPos(), + "SwTextFrame::FormatLine: frame leaves orbit." ); + OSL_ENSURE( rLine.GetCurr()->Height(), + "SwTextFrame::FormatLine: line height is zero" ); + + // The current line break object + const SwLineLayout *pNew = rLine.GetCurr(); + + bool bUnChg = nOldLen == pNew->GetLen() && + bOldHyph == pNew->IsEndHyph(); + if ( bUnChg && !bPrev ) + { + const tools::Long nWidthDiff = nOldWidth > pNew->Width() + ? nOldWidth - pNew->Width() + : pNew->Width() - nOldWidth; + + // we only declare a line as unchanged, if its main values have not + // changed and it is not the last line (!paragraph end symbol!) + bUnChg = nOldHeight == pNew->Height() && + nOldAscent == pNew->GetAscent() && + nWidthDiff <= SLOPPY_TWIPS && + pOldCur->GetNext(); + } + + // Calculate rRepaint + const SwTwips nBottom = rLine.Y() + rLine.GetLineHeight(); + SwRepaint &rRepaint = pPara->GetRepaint(); + if( bUnChg && rRepaint.Top() == rLine.Y() + && (bPrev || nNewStart <= pPara->GetReformat().Start()) + && (nNewStart < TextFrameIndex(GetText().getLength()))) + { + rRepaint.Top( nBottom ); + rRepaint.Height( 0 ); + } + else + { + if( nOldTop ) + { + if( nOldTop < rRepaint.Top() ) + rRepaint.Top( nOldTop ); + if( !rLine.IsUnclipped() || nOldBottom > rRepaint.Bottom() ) + { + rRepaint.Bottom( nOldBottom - 1 ); + rLine.SetUnclipped( true ); + } + } + if( rLine.GetCurr()->IsClipping() && rLine.IsFlyInCntBase() ) + { + SwTwips nTmpTop, nTmpBottom; + rLine.CalcUnclipped( nTmpTop, nTmpBottom ); + if( nTmpTop < rRepaint.Top() ) + rRepaint.Top( nTmpTop ); + if( !rLine.IsUnclipped() || nTmpBottom > rRepaint.Bottom() ) + { + rRepaint.Bottom( nTmpBottom - 1 ); + rLine.SetUnclipped( true ); + } + } + else + { + if( !rLine.IsUnclipped() || nBottom > rRepaint.Bottom() ) + { + rRepaint.Bottom( nBottom - 1 ); + rLine.SetUnclipped( false ); + } + } + SwTwips nRght = std::max( nOldWidth, pNew->Width() + + pNew->GetHangingMargin() ); + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + const SwViewOption *pOpt = pSh ? pSh->GetViewOptions() : nullptr; + if( pOpt && (pOpt->IsParagraph() || pOpt->IsLineBreak()) ) + nRght += ( std::max( nOldAscent, pNew->GetAscent() ) ); + else + nRght += ( std::max( nOldAscent, pNew->GetAscent() ) / 4); + nRght += rLine.GetLeftMargin(); + if( rRepaint.GetOffset() || rRepaint.GetRightOfst() < nRght ) + rRepaint.SetRightOfst( nRght ); + + // Finally we enlarge the repaint rectangle if we found an underscore + // within our line. 40 Twips should be enough + const bool bHasUnderscore = + ( rLine.GetInfo().GetUnderScorePos() < nNewStart ); + if ( bHasUnderscore || rLine.GetCurr()->HasUnderscore() ) + rRepaint.Bottom( rRepaint.Bottom() + 40 ); + + const_cast<SwLineLayout*>(rLine.GetCurr())->SetUnderscore( bHasUnderscore ); + } + + // Calculating the good ol' nDelta + const sal_Int32 nDiff = sal_Int32(pNew->GetLen()) - sal_Int32(nOldLen); + pPara->SetDelta(pPara->GetDelta() - nDiff); + + // Stop! + if( rLine.IsStop() ) + return false; + + // Absolutely another line + if( rLine.IsNewLine() ) + return true; + + // Until the String's end? + if (nNewStart >= TextFrameIndex(GetText().getLength())) + return false; + + if( rLine.GetInfo().IsShift() ) + return true; + + // Reached the Reformat's end? + const TextFrameIndex nEnd = pPara->GetReformat().Start() + + pPara->GetReformat().Len(); + + if( nNewStart <= nEnd ) + return true; + + return 0 != pPara->GetDelta(); +} + +void SwTextFrame::Format_( SwTextFormatter &rLine, SwTextFormatInfo &rInf, + const bool bAdjust ) +{ + OSL_ENSURE( ! IsVertical() || IsSwapped(),"SwTextFrame::Format_ with unswapped frame" ); + + SwParaPortion *pPara = rLine.GetInfo().GetParaPortion(); + rLine.SetUnclipped( false ); + + const OUString & rString = GetText(); + const TextFrameIndex nStrLen(rString.getLength()); + + SwCharRange &rReformat = pPara->GetReformat(); + SwRepaint &rRepaint = pPara->GetRepaint(); + std::unique_ptr<SwRepaint> pFreeze; + + // Due to performance reasons we set rReformat to COMPLETE_STRING in Init() + // In this case we adjust rReformat + if( rReformat.Len() > nStrLen ) + rReformat.Len() = nStrLen; + + if( rReformat.Start() + rReformat.Len() > nStrLen ) + rReformat.Len() = nStrLen - rReformat.Start(); + + SwTwips nOldBottom; + if( GetOffset() && !IsFollow() ) + { + rLine.Bottom(); + nOldBottom = rLine.Y(); + rLine.Top(); + } + else + nOldBottom = 0; + rLine.CharToLine( rReformat.Start() ); + + // When inserting or removing a Space, words can be moved out of the edited + // line and into the preceding line, hence the preceding line must be + // formatted as well. + // Optimization: If rReformat starts after the first word of the line, + // this line cannot possibly influence the previous one. + // ...Turns out that unfortunately it can: Text size changes + FlyFrames; + // the feedback can affect multiple lines (Frames!)! + + // i#46560 + // FME: Yes, consider this case: "(word )" has to go to the next line + // because ")" is a forbidden character at the beginning of a line although + // "(word" would still fit on the previous line. Adding text right in front + // of ")" would not trigger a reformatting of the previous line. Adding 1 + // to the result of FindBrk() does not solve the problem in all cases, + // nevertheless it should be sufficient. + bool bPrev = rLine.GetPrev() && + (FindBrk(rString, rLine.GetStart(), rReformat.Start() + TextFrameIndex(1)) + // i#46560 + + TextFrameIndex(1) + >= rReformat.Start() || + rLine.GetCurr()->IsRest() ); + if( bPrev ) + { + while( rLine.Prev() ) + if( rLine.GetCurr()->GetLen() && !rLine.GetCurr()->IsRest() ) + { + if( !rLine.GetStart() ) + rLine.Top(); // So that NumDone doesn't get confused + break; + } + TextFrameIndex nNew = rLine.GetStart() + rLine.GetLength(); + if( nNew ) + { + --nNew; + if (CH_BREAK == rString[sal_Int32(nNew)]) + { + ++nNew; + rLine.Next(); + bPrev = false; + } + } + rReformat.Len() += rReformat.Start() - nNew; + rReformat.Start() = nNew; + } + + rRepaint.SetOffset( 0 ); + rRepaint.SetRightOfst( 0 ); + rRepaint.Chg( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() ); + if( pPara->IsMargin() ) + rRepaint.Width( rRepaint.Width() + pPara->GetHangingMargin() ); + rRepaint.Top( rLine.Y() ); + if( 0 >= rRepaint.Width() ) + rRepaint.Width(1); + WidowsAndOrphans aFrameBreak( this, rInf.IsTest() ? 1 : 0 ); + + // rLine is now set to the first line which needs formatting. + // The bFirst flag makes sure that Next() is not called. + // The whole thing looks weird, but we need to make sure that + // rLine stops at the last non-fitting line when calling IsBreakNow. + bool bFirst = true; + bool bFormat = true; + + // The CharToLine() can also get us into the danger zone. + // In that case we need to walk back until rLine is set + // to the non-fitting line. Or else the mass of text is lost, + // because the Ofst was set wrongly in the Follow. + + bool bBreak = ( !pPara->IsPrepMustFit() || rLine.GetLineNr() > 1 ) + && aFrameBreak.IsBreakNowWidAndOrp( rLine ); + if( bBreak ) + { + bool bPrevDone = nullptr != rLine.Prev(); + while( bPrevDone && aFrameBreak.IsBreakNowWidAndOrp(rLine) ) + bPrevDone = nullptr != rLine.Prev(); + if( bPrevDone ) + { + aFrameBreak.SetKeep( false ); + rLine.Next(); + } + rLine.TruncLines(); + + // Play it safe + aFrameBreak.IsBreakNowWidAndOrp(rLine); + } + + /* Meaning if the following flags are set: + + Watch(End/Mid)Hyph: we need to format if we have a break at + the line end/Fly, as long as MaxHyph is reached + + Jump(End/Mid)Flag: the next line which has no break (line end/Fly), + needs to be formatted, because we could wrap now. This might have been + forbidden earlier by MaxHyph + + Watch(End/Mid)Hyph: if the last formatted line got a cutoff point, but + didn't have one before + + Jump(End/Mid)Hyph: if a cutoff point disappears + */ + bool bJumpEndHyph = false; + bool bWatchEndHyph = false; + bool bJumpMidHyph = false; + bool bWatchMidHyph = false; + + const SwAttrSet& rAttrSet = GetTextNodeForParaProps()->GetSwAttrSet(); + rInf.MaxHyph() = rAttrSet.GetHyphenZone().GetMaxHyphens(); + bool bMaxHyph = 0 != rInf.MaxHyph(); + if ( bMaxHyph ) + rLine.InitCntHyph(); + + if( IsFollow() && IsFieldFollow() && rLine.GetStart() == GetOffset() ) + { + SwTextFrame *pMaster = FindMaster(); + OSL_ENSURE( pMaster, "SwTextFrame::Format: homeless follow" ); + const SwLineLayout* pLine=nullptr; + if (pMaster) + { + if (!pMaster->HasPara()) + { // master could be locked because it's being formatted upstack + SAL_WARN("sw", "SwTextFrame::Format_: master not formatted!"); + } + else + { + SwTextSizeInfo aInf( pMaster ); + SwTextIter aMasterLine( pMaster, &aInf ); + aMasterLine.Bottom(); + pLine = aMasterLine.GetCurr(); + assert(aMasterLine.GetEnd() == GetOffset()); + } + } + SwLinePortion* pRest = pLine ? + rLine.MakeRestPortion(pLine, GetOffset()) : nullptr; + if( pRest ) + rInf.SetRest( pRest ); + else + SetFieldFollow( false ); + } + + /* Ad cancel criterion: + * In order to recognize, whether a line does not fit onto the page + * anymore, we need to format it. This overflow is removed again in + * e.g. AdjustFollow. + * Another complication: if we are the Master, we need to traverse + * the lines, because it could happen that one line can overflow + * from the Follow to the Master. + */ + do + { + if( bFirst ) + bFirst = false; + else + { + if ( bMaxHyph ) + { + if ( rLine.GetCurr()->IsEndHyph() ) + rLine.CntEndHyph()++; + else + rLine.CntEndHyph() = 0; + if ( rLine.GetCurr()->IsMidHyph() ) + rLine.CntMidHyph()++; + else + rLine.CntMidHyph() = 0; + } + if( !rLine.Next() ) + { + if( !bFormat ) + { + SwLinePortion* pRest = + rLine.MakeRestPortion( rLine.GetCurr(), rLine.GetEnd() ); + if( pRest ) + rInf.SetRest( pRest ); + } + rLine.Insert( new SwLineLayout() ); + rLine.Next(); + bFormat = true; + } + } + if ( !bFormat && bMaxHyph && + (bWatchEndHyph || bJumpEndHyph || bWatchMidHyph || bJumpMidHyph) ) + { + if ( rLine.GetCurr()->IsEndHyph() ) + { + if ( bWatchEndHyph ) + bFormat = ( rLine.CntEndHyph() == rInf.MaxHyph() ); + } + else + { + bFormat = bJumpEndHyph; + bWatchEndHyph = false; + bJumpEndHyph = false; + } + if ( rLine.GetCurr()->IsMidHyph() ) + { + if ( bWatchMidHyph && !bFormat ) + bFormat = ( rLine.CntEndHyph() == rInf.MaxHyph() ); + } + else + { + bFormat |= bJumpMidHyph; + bWatchMidHyph = false; + bJumpMidHyph = false; + } + } + if( bFormat ) + { + const bool bOldEndHyph = rLine.GetCurr()->IsEndHyph(); + const bool bOldMidHyph = rLine.GetCurr()->IsMidHyph(); + bFormat = FormatLine( rLine, bPrev ); + // There can only be one bPrev ... (???) + bPrev = false; + if ( bMaxHyph ) + { + if ( rLine.GetCurr()->IsEndHyph() != bOldEndHyph ) + { + bWatchEndHyph = !bOldEndHyph; + bJumpEndHyph = bOldEndHyph; + } + if ( rLine.GetCurr()->IsMidHyph() != bOldMidHyph ) + { + bWatchMidHyph = !bOldMidHyph; + bJumpMidHyph = bOldMidHyph; + } + } + } + + if( !rInf.IsNewLine() ) + { + if( !bFormat ) + bFormat = nullptr != rInf.GetRest(); + if( rInf.IsStop() || rInf.GetIdx() >= nStrLen ) + break; + if( !bFormat && ( !bMaxHyph || ( !bWatchEndHyph && + !bJumpEndHyph && !bWatchMidHyph && !bJumpMidHyph ) ) ) + { + if( GetFollow() ) + { + while( rLine.Next() ) + ; //Nothing + pFreeze.reset(new SwRepaint( rRepaint )); // to minimize painting + } + else + break; + } + } + bBreak = aFrameBreak.IsBreakNowWidAndOrp(rLine); + }while( !bBreak ); + + if( pFreeze ) + { + rRepaint = *pFreeze; + pFreeze.reset(); + } + + if( !rLine.IsStop() ) + { + // If we're finished formatting the text and we still + // have other line objects left, these are superfluous + // now because the text has gotten shorter. + bool bTruncLines = false; + if( rLine.GetStart() + rLine.GetLength() >= nStrLen && + rLine.GetCurr()->GetNext() ) + { + bTruncLines = true; + } + else if (GetMergedPara() && rLine.GetCurr()->GetNext()) + { + // We can also have superfluous lines with redlining in case the current line is shorter + // than the text length, but the total length of lines is still more than expected. + // Truncate in this case as well. + TextFrameIndex nLen(0); + for (const SwLineLayout* pLine = pPara; pLine; pLine = pLine->GetNext()) + { + nLen += pLine->GetLen(); + } + bTruncLines = nLen > nStrLen; + } + + if (bTruncLines) + { + rLine.TruncLines(); + rLine.SetTruncLines( true ); + } + } + + if( rInf.IsTest() ) + return; + + // FormatAdjust does not pay off at OnceMore + if( bAdjust || !rLine.GetDropFormat() || !rLine.CalcOnceMore() ) + { + FormatAdjust( rLine, aFrameBreak, nStrLen, rInf.IsStop() ); + } + if( rRepaint.HasArea() ) + SetRepaint(); + rLine.SetTruncLines( false ); + if( nOldBottom ) // We check whether paragraphs that need scrolling can + // be shrunk, so that they don't need scrolling anymore + { + rLine.Bottom(); + SwTwips nNewBottom = rLine.Y(); + if( nNewBottom < nOldBottom ) + SetOffset_(TextFrameIndex(0)); + } +} + +void SwTextFrame::FormatOnceMore( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) +{ + OSL_ENSURE( ! IsVertical() || IsSwapped(), + "A frame is not swapped in SwTextFrame::FormatOnceMore" ); + + SwParaPortion *pPara = rLine.GetInfo().GetParaPortion(); + if( !pPara ) + return; + + // If necessary the pPara + sal_uInt16 nOld = static_cast<const SwTextMargin&>(rLine).GetDropHeight(); + bool bShrink = false; + bool bGrow = false; + bool bGoOn = rLine.IsOnceMore(); + sal_uInt8 nGo = 0; + while( bGoOn ) + { + ++nGo; + rInf.Init(); + rLine.Top(); + if( !rLine.GetDropFormat() ) + rLine.SetOnceMore( false ); + SwCharRange aRange(TextFrameIndex(0), TextFrameIndex(rInf.GetText().getLength())); + pPara->GetReformat() = aRange; + Format_( rLine, rInf ); + + bGoOn = rLine.IsOnceMore(); + if( bGoOn ) + { + const sal_uInt16 nNew = static_cast<const SwTextMargin&>(rLine).GetDropHeight(); + if( nOld == nNew ) + bGoOn = false; + else + { + if( nOld > nNew ) + bShrink = true; + else + bGrow = true; + + if( bShrink == bGrow || 5 < nGo ) + bGoOn = false; + + nOld = nNew; + } + + // If something went wrong, we need to reformat again + if( !bGoOn ) + { + rInf.CtorInitTextFormatInfo( getRootFrame()->GetCurrShell()->GetOut(), this ); + rLine.CtorInitTextFormatter( this, &rInf ); + rLine.SetDropLines( 1 ); + rLine.CalcDropHeight( 1 ); + SwCharRange aTmpRange(TextFrameIndex(0), TextFrameIndex(rInf.GetText().getLength())); + pPara->GetReformat() = aTmpRange; + Format_( rLine, rInf, true ); + // We paint everything ... + SetCompletePaint(); + } + } + } +} + +void SwTextFrame::FormatImpl(vcl::RenderContext* pRenderContext, SwParaPortion *pPara, + std::vector<SwAnchoredObject *> & rIntersectingObjs) +{ + const bool bIsEmpty = GetText().isEmpty(); + + if ( bIsEmpty ) + { + // Empty lines do not get tortured for very long: + // pPara is cleared, which is the same as: + // *pPara = SwParaPortion; + const bool bMustFit = pPara->IsPrepMustFit(); + pPara->Truncate(); + pPara->FormatReset(); + + // delete pSpaceAdd and pKanaComp + pPara->FinishSpaceAdd(); + pPara->FinishKanaComp(); + pPara->ResetFlags(); + pPara->SetPrepMustFit( bMustFit ); + } + + OSL_ENSURE( ! IsSwapped(), "A frame is swapped before Format_" ); + + if ( IsVertical() ) + SwapWidthAndHeight(); + + SwTextFormatInfo aInf( pRenderContext, this ); + SwTextFormatter aLine( this, &aInf ); + + HideAndShowObjects(); + + Format_( aLine, aInf ); + + if( aLine.IsOnceMore() ) + FormatOnceMore( aLine, aInf ); + + if (aInf.GetTextFly().IsOn()) + { + SwRect const aRect(aInf.GetTextFly().GetFrameArea()); + for (SwAnchoredObject *const pObj : aInf.GetTextFly().GetAnchoredObjList()) + { + if (!aInf.GetTextFly().AnchoredObjToRect(pObj, aRect).IsEmpty()) + { + rIntersectingObjs.push_back(pObj); + } + } + } + + if ( IsVertical() ) + SwapWidthAndHeight(); + + OSL_ENSURE( ! IsSwapped(), "A frame is swapped after Format_" ); + + if( 1 >= aLine.GetDropLines() ) + return; + + if( SvxAdjust::Left != aLine.GetAdjust() && + SvxAdjust::Block != aLine.GetAdjust() ) + { + aLine.CalcDropAdjust(); + aLine.SetPaintDrop( true ); + } + + if( aLine.IsPaintDrop() ) + { + aLine.CalcDropRepaint(); + aLine.SetPaintDrop( false ); + } +} + +// We calculate the text frame's size and send a notification. +// Shrink() or Grow() to adjust the frame's size to the changed required space. +void SwTextFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs * ) +{ + SwRectFnSet aRectFnSet(this); + + CalcAdditionalFirstLineOffset(); + + // The range autopilot or the BASIC interface pass us TextFrames with + // a width <= 0 from time to time + if( aRectFnSet.GetWidth(getFramePrintArea()) <= 0 ) + { + // If MustFit is set, we shrink to the Upper's bottom edge if needed. + SwTextLineAccess aAccess( this ); + + if( aAccess.GetPara()->IsPrepMustFit() ) + { + const SwTwips nLimit = aRectFnSet.GetPrtBottom(*GetUpper()); + const SwTwips nDiff = - aRectFnSet.BottomDist( getFrameArea(), nLimit ); + if( nDiff > 0 ) + Shrink( nDiff ); + } + + tools::Long nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + const tools::Long nTop = aRectFnSet.GetTopMargin(*this); + + if( nTop > nFrameHeight ) + { + aRectFnSet.SetYMargins( *this, nFrameHeight, 0 ); + } + else if( aRectFnSet.GetHeight(getFramePrintArea()) < 0 ) + { + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetHeight( aPrt, 0 ); + } + + return; + } + + TextFrameIndex nStrLen(GetText().getLength()); + + if (HasNonLastSplitFlyDrawObj()) + { + // Non-last part of split fly anchor: consider this empty. + nStrLen = TextFrameIndex(0); + } + + if ( nStrLen || !FormatEmpty() ) + { + + SetEmpty( false ); + // In order to not get confused by nested Formats + FormatLevel aLevel; + if( 12 == FormatLevel::GetLevel() ) + return; + + // We could be possibly not allowed to alter the format information + if( IsLocked() ) + return; + + // Attention: Format() could be triggered by GetFormatted() + if( IsHiddenNow() ) + { + tools::Long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea()); + if( nPrtHeight ) + { + HideHidden(); + Shrink( nPrtHeight ); + } + else + { + // Assure that objects anchored + // at paragraph resp. at/as character inside paragraph + // are hidden. + HideAndShowObjects(); + } + ChgThisLines(); + return; + } + + // We do not want to be interrupted during formatting + TextFrameLockGuard aLock(this); + + // this is to ensure that the similar code in SwTextFrame::Format_ + // finds the master formatted in case it's needed + if (IsFollow() && IsFieldFollow()) + { + SwTextFrame *pMaster = FindMaster(); + assert(pMaster); + if (!pMaster->HasPara()) + { + pMaster->GetFormatted(); + } + if (!pMaster->HasPara()) + { // master could be locked because it's being formatted upstack + SAL_WARN("sw", "SwTextFrame::Format: failed to format master!"); + } + else + { + SwTextSizeInfo aInf( pMaster ); + SwTextIter aMasterLine( pMaster, &aInf ); + aMasterLine.Bottom(); + SetOffset(aMasterLine.GetEnd()); + } + } + + SwTextLineAccess aAccess( this ); + const bool bNew = !aAccess.IsAvailable(); + const bool bSetOffset = + (GetOffset() && GetOffset() > TextFrameIndex(GetText().getLength())); + + if( CalcPreps() ) + ; // nothing + // We return if already formatted, but if the TextFrame was just created + // and does not have any format information + else if( !bNew && !aAccess.GetPara()->GetReformat().Len() ) + { + if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue()) + { + aAccess.GetPara()->SetPrepAdjust(); + aAccess.GetPara()->SetPrep(); + CalcPreps(); + } + SetWidow( false ); + } + else if( bSetOffset && IsFollow() ) + { + SwTextFrame *pMaster = FindMaster(); + OSL_ENSURE( pMaster, "SwTextFrame::Format: homeless follow" ); + if( pMaster ) + pMaster->Prepare( PrepareHint::FollowFollows ); + SwTwips nMaxY = aRectFnSet.GetPrtBottom(*GetUpper()); + + if( aRectFnSet.OverStep( getFrameArea(), nMaxY ) ) + { + aRectFnSet.SetLimit( *this, nMaxY ); + } + else if( aRectFnSet.BottomDist( getFrameArea(), nMaxY ) < 0 ) + { + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.AddBottom( aFrm, -aRectFnSet.GetHeight(aFrm) ); + } + } + else + { + // bSetOffset here means that we have the "red arrow situation" + if ( bSetOffset ) + SetOffset_(TextFrameIndex(0)); + + const bool bOrphan = IsWidow(); + const SwFootnoteBossFrame* pFootnoteBoss = HasFootnote() ? FindFootnoteBossFrame() : nullptr; + SwTwips nFootnoteHeight = 0; + if( pFootnoteBoss ) + { + const SwFootnoteContFrame* pCont = pFootnoteBoss->FindFootnoteCont(); + nFootnoteHeight = pCont ? aRectFnSet.GetHeight(pCont->getFrameArea()) : 0; + } + do + { + ::std::vector<SwAnchoredObject *> intersectingObjs; + ::std::vector<SwFrame const*> nexts; + for (SwFrame const* pNext = GetNext(); pNext; pNext = pNext->GetNext()) + { + nexts.push_back(pNext); + } + FormatImpl(pRenderContext, aAccess.GetPara(), intersectingObjs); + if( pFootnoteBoss && nFootnoteHeight ) + { + const SwFootnoteContFrame* pCont = pFootnoteBoss->FindFootnoteCont(); + SwTwips nNewHeight = pCont ? aRectFnSet.GetHeight(pCont->getFrameArea()) : 0; + // If we lost some footnotes, we may have more space + // for our main text, so we have to format again ... + if( nNewHeight < nFootnoteHeight ) + { + nFootnoteHeight = nNewHeight; + continue; + } + } + if (!intersectingObjs.empty()) + { + // assumption is that FormatImpl() only moves frames + // in the next-chain to next page + SwPageFrame *const pPage(FindPageFrame()); + SwTextFrame * pLastMovedAnchor(nullptr); + auto lastIter(nexts.end()); + for (SwAnchoredObject *const pObj : intersectingObjs) + { + SwFrame *const pAnchor(pObj->AnchorFrame()); + SwPageFrame *const pAnchorPage(pAnchor->FindPageFrame()); + if (pAnchorPage != pPage) + { + auto const iter(::std::find(nexts.begin(), nexts.end(), pAnchor)); + if (iter != nexts.end()) + { + assert(pAnchor->IsTextFrame()); + // (can't check SwOszControl::IsInProgress()?) + // called in loop in FormatAnchorFrameAndItsPrevs() + if (static_cast<SwTextFrame const*>(pAnchor)->IsJoinLocked() + // called in loop in SwFrame::PrepareMake() + || pAnchor->IsDeleteForbidden()) + { + // when called via FormatAnchorFrameAndItsPrevs(): + // don't do anything, caller will handle it + pLastMovedAnchor = nullptr; + break; + } + assert(pPage->GetPhyPageNum() < pAnchorPage->GetPhyPageNum()); // how could it move backward? + + if (!pLastMovedAnchor || iter < lastIter) + { + pLastMovedAnchor = static_cast<SwTextFrame *>(pAnchor); + lastIter = iter; + } + } + } + } + SwPageFrame const*const pPrevPage(static_cast<SwPageFrame const*>(pPage->GetPrev())); + if (pLastMovedAnchor) + { + for (SwAnchoredObject *const pObj : intersectingObjs) + { + if (pObj->AnchorFrame() == pLastMovedAnchor) + { + SwPageFrame *const pAnchorPage(pLastMovedAnchor->FindPageFrame()); + SAL_INFO("sw.layout", "SwTextFrame::Format: 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); + pObj->SetForceNotifyNewBackground(true); + } + } + if (GetFollow() // this frame was split + && (!pPrevPage // prev page is still valid + || (!pPrevPage->IsInvalid() + && (!pPrevPage->GetSortedObjs() || !pPrevPage->IsInvalidFly())))) + { // this seems a bit risky... + SwLayouter::InsertMovedFwdFrame(GetTextNodeFirst()->GetDoc(), + *pLastMovedAnchor, FindPageFrame()->GetPhyPageNum() + 1); + } + continue; // try again without the fly + } + } + break; + } while ( pFootnoteBoss ); + if( bOrphan ) + { + ValidateFrame(); + SetWidow( false ); + } + } + if( IsEmptyMaster() ) + { + SwFrame* pPre = GetPrev(); + if( pPre && + // i#10826 It's the first, it cannot keep! + pPre->GetIndPrev() && + pPre->GetAttrSet()->GetKeep().GetValue() ) + { + pPre->InvalidatePos(); + } + + if (IsEmptyMasterWithSplitFly()) + { + // A fly is anchored to us, reduce size, so we definitely still fit the current + // page. + SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this); + aRectFnSet.SetHeight(aFrm, 0); + + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + aRectFnSet.SetTop(aPrt, 0); + aRectFnSet.SetHeight(aPrt, 0); + } + } + } + + ChgThisLines(); + + // the PrepMustFit should not survive a Format operation + SwParaPortion *pPara = GetPara(); + if ( pPara ) + pPara->SetPrepMustFit( false ); + + CalcBaseOfstForFly(); + CalcHeightOfLastLine(); // i#11860 - Adjust spacing implementation for + // object positioning - Compatibility to MS Word + // tdf#117982 -- Fix cell spacing hides content + // Check if the cell's content has greater size than the row height + if (IsInTab() && GetUpper() && ((GetUpper()->getFramePrintArea().Height() < getFramePrintArea().Height()) + || (getFramePrintArea().Height() <= 0))) + { + SAL_INFO("sw.core", "Warn: Cell content has greater size than cell height!"); + //get font size... + SwTwips aTmpHeight = getFrameArea().Height(); + //...and push it into the text frame + SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this); + //if only bottom margin what we have: + if (GetTopMargin() == 0) + //set the frame to its original location + aPrt.SetTopAndHeight(0, aTmpHeight); + } +} + +// bForceQuickFormat is set if GetFormatted() has been called during the +// painting process. Actually I cannot imagine a situation which requires +// a full formatting of the paragraph during painting, on the other hand +// a full formatting can cause the invalidation of other layout frames, +// e.g., if there are footnotes in this paragraph, and invalid layout +// frames will not calculated during the painting. So I actually want to +// avoid a formatting during painting, but since I'm a coward, I'll only +// force the quick formatting in the situation of issue i29062. +bool SwTextFrame::FormatQuick( bool bForceQuickFormat ) +{ + OSL_ENSURE( ! IsVertical() || ! IsSwapped(), + "SwTextFrame::FormatQuick with swapped frame" ); + + if( IsEmpty() && FormatEmpty() ) + return true; + + // We're very picky: + if( HasPara() || IsWidow() || IsLocked() + || !isFrameAreaSizeValid() || + ( ( IsVertical() ? getFramePrintArea().Width() : getFramePrintArea().Height() ) && IsHiddenNow() ) ) + return false; + + SwTextLineAccess aAccess( this ); + SwParaPortion *pPara = aAccess.GetPara(); + if( !pPara ) + return false; + + SwFrameSwapper aSwapper( this, true ); + + TextFrameLockGuard aLock(this); + SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this, false, true ); + if( 0 != aInf.MaxHyph() ) // Respect MaxHyphen! + return false; + + SwTextFormatter aLine( this, &aInf ); + + // DropCaps are too complicated ... + if( aLine.GetDropFormat() ) + return false; + + TextFrameIndex nStart = GetOffset(); + const TextFrameIndex nEnd = GetFollow() + ? GetFollow()->GetOffset() + : TextFrameIndex(aInf.GetText().getLength()); + + int nLoopProtection = 0; + do + { + TextFrameIndex nNewStart = aLine.FormatLine(nStart); + if (nNewStart == nStart) + ++nLoopProtection; + else + nLoopProtection = 0; + nStart = nNewStart; + const bool bWillEndlessInsert = nLoopProtection > 250; + SAL_WARN_IF(bWillEndlessInsert, "sw", "loop detection triggered"); + if ((!bWillEndlessInsert) // Check for special case: line is invisible, + // like in too thin table cell: tdf#66141 + && (aInf.IsNewLine() || (!aInf.IsStop() && nStart < nEnd))) + aLine.Insert( new SwLineLayout() ); + } while( aLine.Next() ); + + // Last exit: the heights need to match + Point aTopLeft( getFrameArea().Pos() ); + aTopLeft += getFramePrintArea().Pos(); + const SwTwips nNewHeight = aLine.Y() + aLine.GetLineHeight(); + const SwTwips nOldHeight = aTopLeft.Y() + getFramePrintArea().Height(); + + if( !bForceQuickFormat && nNewHeight != nOldHeight && !IsUndersized() ) + { + // Attention: This situation can occur due to FormatLevel==12. Don't panic! + TextFrameIndex const nStrt = GetOffset(); + InvalidateRange_( SwCharRange( nStrt, nEnd - nStrt) ); + return false; + } + + if (m_pFollow && nStart != static_cast<SwTextFrame*>(m_pFollow)->GetOffset()) + return false; // can be caused by e.g. Orphans + + // We made it! + + // Set repaint + pPara->GetRepaint().Pos( aTopLeft ); + pPara->GetRepaint().SSize( getFramePrintArea().SSize() ); + + // Delete reformat + pPara->GetReformat() = SwCharRange(); + pPara->SetDelta(0); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |