summaryrefslogtreecommitdiffstats
path: root/sw/source/core/text/txtfrm.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sw/source/core/text/txtfrm.cxx')
-rw-r--r--sw/source/core/text/txtfrm.cxx4219
1 files changed, 4219 insertions, 0 deletions
diff --git a/sw/source/core/text/txtfrm.cxx b/sw/source/core/text/txtfrm.cxx
new file mode 100644
index 0000000000..18eb78db83
--- /dev/null
+++ b/sw/source/core/text/txtfrm.cxx
@@ -0,0 +1,4219 @@
+/* -*- 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 <hintids.hxx>
+#include <hints.hxx>
+#include <svl/ctloptions.hxx>
+#include <editeng/lspcitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/pgrditem.hxx>
+#include <unotools/configmgr.hxx>
+#include <swmodule.hxx>
+#include <SwSmartTagMgr.hxx>
+#include <doc.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <IDocumentDeviceAccess.hxx>
+#include <IDocumentFieldsAccess.hxx>
+#include <rootfrm.hxx>
+#include <pagefrm.hxx>
+#include <viewsh.hxx>
+#include <pam.hxx>
+#include <ndtxt.hxx>
+#include <paratr.hxx>
+#include <viewopt.hxx>
+#include <flyfrm.hxx>
+#include <tabfrm.hxx>
+#include <frmatr.hxx>
+#include <frmtool.hxx>
+#include <tgrditem.hxx>
+#include <dbg_lay.hxx>
+#include <fmtfld.hxx>
+#include <fmtftn.hxx>
+#include <txtfld.hxx>
+#include <txtftn.hxx>
+#include <ftninfo.hxx>
+#include <fmtline.hxx>
+#include <txtfrm.hxx>
+#include <notxtfrm.hxx>
+#include <sectfrm.hxx>
+#include "itrform2.hxx"
+#include "widorp.hxx"
+#include "txtcache.hxx"
+#include <fntcache.hxx>
+#include <SwGrammarMarkUp.hxx>
+#include <lineinfo.hxx>
+#include <SwPortionHandler.hxx>
+#include <dcontact.hxx>
+#include <sortedobjs.hxx>
+#include <txtflcnt.hxx>
+#include <fmtflcnt.hxx>
+#include <fmtcntnt.hxx>
+#include <numrule.hxx>
+#include <GrammarContact.hxx>
+#include <calbck.hxx>
+#include <ftnidx.hxx>
+#include <ftnfrm.hxx>
+
+#include <wrtsh.hxx>
+#include <view.hxx>
+#include <edtwin.hxx>
+#include <FrameControlsManager.hxx>
+
+namespace sw {
+
+ MergedAttrIterBase::MergedAttrIterBase(SwTextFrame const& rFrame)
+ : m_pMerged(rFrame.GetMergedPara())
+ , m_pNode(m_pMerged ? nullptr : rFrame.GetTextNodeFirst())
+ , m_CurrentExtent(0)
+ , m_CurrentHint(0)
+ {
+ }
+
+ SwTextAttr const* MergedAttrIter::NextAttr(SwTextNode const** ppNode)
+ {
+ if (m_pMerged)
+ {
+ while (m_CurrentExtent < m_pMerged->extents.size())
+ {
+ sw::Extent const& rExtent(m_pMerged->extents[m_CurrentExtent]);
+ if (SwpHints const*const pHints = rExtent.pNode->GetpSwpHints())
+ {
+ while (m_CurrentHint < pHints->Count())
+ {
+ SwTextAttr *const pHint(pHints->Get(m_CurrentHint));
+ if (rExtent.nEnd < pHint->GetStart()
+ // <= if it has no end or isn't empty
+ || (rExtent.nEnd == pHint->GetStart()
+ && (!pHint->GetEnd()
+ || *pHint->GetEnd() != pHint->GetStart())))
+ {
+ break;
+ }
+ ++m_CurrentHint;
+ if (rExtent.nStart <= pHint->GetStart())
+ {
+ if (ppNode)
+ {
+ *ppNode = rExtent.pNode;
+ }
+ return pHint;
+ }
+ }
+ }
+ ++m_CurrentExtent;
+ if (m_CurrentExtent < m_pMerged->extents.size() &&
+ rExtent.pNode != m_pMerged->extents[m_CurrentExtent].pNode)
+ {
+ m_CurrentHint = 0; // reset
+ }
+ }
+ return nullptr;
+ }
+ else
+ {
+ SwpHints const*const pHints(m_pNode->GetpSwpHints());
+ if (pHints)
+ {
+ if (m_CurrentHint < pHints->Count())
+ {
+ SwTextAttr const*const pHint(pHints->Get(m_CurrentHint));
+ ++m_CurrentHint;
+ if (ppNode)
+ {
+ *ppNode = m_pNode;
+ }
+ return pHint;
+ }
+ }
+ return nullptr;
+ }
+ }
+
+ MergedAttrIterByEnd::MergedAttrIterByEnd(SwTextFrame const& rFrame)
+ : m_pNode(rFrame.GetMergedPara() ? nullptr : rFrame.GetTextNodeFirst())
+ , m_CurrentHint(0)
+ {
+ if (!m_pNode)
+ {
+ MergedAttrIterReverse iter(rFrame);
+ SwTextNode const* pNode(nullptr);
+ while (SwTextAttr const* pHint = iter.PrevAttr(&pNode))
+ {
+ m_Hints.emplace_back(pNode, pHint);
+ }
+ }
+ }
+
+ SwTextAttr const* MergedAttrIterByEnd::NextAttr(SwTextNode const*& rpNode)
+ {
+ if (m_pNode)
+ {
+ SwpHints const*const pHints(m_pNode->GetpSwpHints());
+ if (pHints)
+ {
+ if (m_CurrentHint < pHints->Count())
+ {
+ SwTextAttr const*const pHint(
+ pHints->GetSortedByEnd(m_CurrentHint));
+ ++m_CurrentHint;
+ rpNode = m_pNode;
+ return pHint;
+ }
+ }
+ return nullptr;
+ }
+ else
+ {
+ if (m_CurrentHint < m_Hints.size())
+ {
+ auto const ret = m_Hints[m_Hints.size() - m_CurrentHint - 1];
+ ++m_CurrentHint;
+ rpNode = ret.first;
+ return ret.second;
+ }
+ return nullptr;
+ }
+ }
+
+ void MergedAttrIterByEnd::PrevAttr()
+ {
+ assert(0 < m_CurrentHint); // should only rewind as far as 0
+ --m_CurrentHint;
+ }
+
+ MergedAttrIterReverse::MergedAttrIterReverse(SwTextFrame const& rFrame)
+ : MergedAttrIterBase(rFrame)
+ {
+ if (m_pMerged)
+ {
+ m_CurrentExtent = m_pMerged->extents.size();
+ SwpHints const*const pHints(0 < m_CurrentExtent
+ ? m_pMerged->extents[m_CurrentExtent-1].pNode->GetpSwpHints()
+ : nullptr);
+ if (pHints)
+ {
+ pHints->SortIfNeedBe();
+ m_CurrentHint = pHints->Count();
+ }
+ }
+ else
+ {
+ if (SwpHints const*const pHints = m_pNode->GetpSwpHints())
+ {
+ pHints->SortIfNeedBe();
+ m_CurrentHint = pHints->Count();
+ }
+ }
+ }
+
+ SwTextAttr const* MergedAttrIterReverse::PrevAttr(SwTextNode const** ppNode)
+ {
+ if (m_pMerged)
+ {
+ while (0 < m_CurrentExtent)
+ {
+ sw::Extent const& rExtent(m_pMerged->extents[m_CurrentExtent-1]);
+ if (SwpHints const*const pHints = rExtent.pNode->GetpSwpHints())
+ {
+ while (0 < m_CurrentHint)
+ {
+ SwTextAttr *const pHint(
+ pHints->GetSortedByEnd(m_CurrentHint - 1));
+ if (pHint->GetAnyEnd() < rExtent.nStart
+ // <= if it has end and isn't empty
+ || (pHint->GetEnd()
+ && *pHint->GetEnd() != pHint->GetStart()
+ && *pHint->GetEnd() == rExtent.nStart))
+ {
+ break;
+ }
+ --m_CurrentHint;
+ if (pHint->GetAnyEnd() <= rExtent.nEnd)
+ {
+ if (ppNode)
+ {
+ *ppNode = rExtent.pNode;
+ }
+ return pHint;
+ }
+ }
+ }
+ --m_CurrentExtent;
+ if (0 < m_CurrentExtent &&
+ rExtent.pNode != m_pMerged->extents[m_CurrentExtent-1].pNode)
+ {
+ SwpHints const*const pHints(
+ m_pMerged->extents[m_CurrentExtent-1].pNode->GetpSwpHints());
+ m_CurrentHint = pHints ? pHints->Count() : 0; // reset
+ if (pHints)
+ pHints->SortIfNeedBe();
+ }
+ }
+ return nullptr;
+ }
+ else
+ {
+ SwpHints const*const pHints(m_pNode->GetpSwpHints());
+ if (pHints && 0 < m_CurrentHint)
+ {
+ SwTextAttr const*const pHint(pHints->GetSortedByEnd(m_CurrentHint - 1));
+ --m_CurrentHint;
+ if (ppNode)
+ {
+ *ppNode = m_pNode;
+ }
+ return pHint;
+ }
+ return nullptr;
+ }
+ }
+
+ bool FrameContainsNode(SwContentFrame const& rFrame, SwNodeOffset const nNodeIndex)
+ {
+ if (rFrame.IsTextFrame())
+ {
+ SwTextFrame const& rTextFrame(static_cast<SwTextFrame const&>(rFrame));
+ if (sw::MergedPara const*const pMerged = rTextFrame.GetMergedPara())
+ {
+ SwNodeOffset const nFirst(pMerged->pFirstNode->GetIndex());
+ SwNodeOffset const nLast(pMerged->pLastNode->GetIndex());
+ return (nFirst <= nNodeIndex && nNodeIndex <= nLast);
+ }
+ else
+ {
+ return rTextFrame.GetTextNodeFirst()->GetIndex() == nNodeIndex;
+ }
+ }
+ else
+ {
+ assert(rFrame.IsNoTextFrame());
+ return static_cast<SwNoTextFrame const&>(rFrame).GetNode()->GetIndex() == nNodeIndex;
+ }
+ }
+
+ bool IsParaPropsNode(SwRootFrame const& rLayout, SwTextNode const& rNode)
+ {
+ if (rLayout.HasMergedParas())
+ {
+ if (SwTextFrame const*const pFrame = static_cast<SwTextFrame*>(rNode.getLayoutFrame(&rLayout)))
+ {
+ sw::MergedPara const*const pMerged(pFrame->GetMergedPara());
+ if (pMerged && pMerged->pParaPropsNode != &rNode)
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ SwTextNode *
+ GetParaPropsNode(SwRootFrame const& rLayout, SwNode const& rPos)
+ {
+ const SwTextNode *const pTextNode(rPos.GetTextNode());
+ if (pTextNode && !sw::IsParaPropsNode(rLayout, *pTextNode))
+ {
+ return static_cast<SwTextFrame*>(pTextNode->getLayoutFrame(&rLayout))->GetMergedPara()->pParaPropsNode;
+ }
+ else
+ {
+ return const_cast<SwTextNode*>(pTextNode);
+ }
+ }
+
+ SwPosition
+ GetParaPropsPos(SwRootFrame const& rLayout, SwPosition const& rPos)
+ {
+ SwPosition pos(rPos);
+ SwTextNode const*const pNode(pos.GetNode().GetTextNode());
+ if (pNode)
+ pos.Assign( *sw::GetParaPropsNode(rLayout, *pNode) );
+ return pos;
+ }
+
+ std::pair<SwTextNode *, SwTextNode *>
+ GetFirstAndLastNode(SwRootFrame const& rLayout, SwNode const& rPos)
+ {
+ SwTextNode *const pTextNode(const_cast<SwTextNode*>(rPos.GetTextNode()));
+ if (pTextNode && rLayout.HasMergedParas())
+ {
+ if (SwTextFrame const*const pFrame = static_cast<SwTextFrame*>(pTextNode->getLayoutFrame(&rLayout)))
+ {
+ if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara())
+ {
+ return std::make_pair(pMerged->pFirstNode, const_cast<SwTextNode*>(pMerged->pLastNode));
+ }
+ }
+ }
+ return std::make_pair(pTextNode, pTextNode);
+ }
+
+ SwTextNode const& GetAttrMerged(SfxItemSet & rFormatSet,
+ SwTextNode const& rNode, SwRootFrame const*const pLayout)
+ {
+ rNode.SwContentNode::GetAttr(rFormatSet);
+ if (pLayout && pLayout->HasMergedParas())
+ {
+ auto pFrame = static_cast<SwTextFrame*>(rNode.getLayoutFrame(pLayout));
+ if (sw::MergedPara const*const pMerged = pFrame ? pFrame->GetMergedPara() : nullptr)
+ {
+ if (pMerged->pFirstNode != &rNode)
+ {
+ rFormatSet.ClearItem(RES_PAGEDESC);
+ rFormatSet.ClearItem(RES_BREAK);
+ static_assert(RES_PAGEDESC + 1 == sal_uInt16(RES_BREAK),
+ "first-node items must be adjacent");
+ SfxItemSetFixed<RES_PAGEDESC, RES_BREAK> firstSet(*rFormatSet.GetPool());
+ pMerged->pFirstNode->SwContentNode::GetAttr(firstSet);
+ rFormatSet.Put(firstSet);
+
+ }
+ if (pMerged->pParaPropsNode != &rNode)
+ {
+ for (sal_uInt16 i = RES_PARATR_BEGIN; i != RES_FRMATR_END; ++i)
+ {
+ if (i != RES_PAGEDESC && i != RES_BREAK)
+ {
+ rFormatSet.ClearItem(i);
+ }
+ }
+ for (sal_uInt16 i = XATTR_FILL_FIRST; i <= XATTR_FILL_LAST; ++i)
+ {
+ rFormatSet.ClearItem(i);
+ }
+ SfxItemSetFixed<RES_PARATR_BEGIN, RES_PAGEDESC,
+ RES_BREAK+1, RES_FRMATR_END,
+ XATTR_FILL_FIRST, XATTR_FILL_LAST+1>
+ propsSet(*rFormatSet.GetPool());
+ pMerged->pParaPropsNode->SwContentNode::GetAttr(propsSet);
+ rFormatSet.Put(propsSet);
+ return *pMerged->pParaPropsNode;
+ }
+ // keep all the CHRATR/UNKNOWNATR anyway...
+ }
+ }
+ return rNode;
+ }
+
+} // namespace sw
+
+/// Switches width and height of the text frame
+void SwTextFrame::SwapWidthAndHeight()
+{
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+
+ if ( ! mbIsSwapped )
+ {
+ const tools::Long nPrtOfstX = aPrt.Pos().X();
+ aPrt.Pos().setX( aPrt.Pos().Y() );
+
+ if( IsVertLR() )
+ {
+ aPrt.Pos().setY( nPrtOfstX );
+ }
+ else
+ {
+ aPrt.Pos().setY( getFrameArea().Width() - ( nPrtOfstX + aPrt.Width() ) );
+ }
+ }
+ else
+ {
+ const tools::Long nPrtOfstY = aPrt.Pos().Y();
+ aPrt.Pos().setY( aPrt.Pos().X() );
+
+ if( IsVertLR() )
+ {
+ aPrt.Pos().setX( nPrtOfstY );
+ }
+ else
+ {
+ aPrt.Pos().setX( getFrameArea().Height() - ( nPrtOfstY + aPrt.Height() ) );
+ }
+ }
+
+ const tools::Long nPrtWidth = aPrt.Width();
+ aPrt.Width( aPrt.Height() );
+ aPrt.Height( nPrtWidth );
+ }
+
+ {
+ const tools::Long nFrameWidth = getFrameArea().Width();
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aFrm.Width( aFrm.Height() );
+ aFrm.Height( nFrameWidth );
+ }
+
+ mbIsSwapped = ! mbIsSwapped;
+}
+
+/**
+ * Calculates the coordinates of a rectangle when switching from
+ * horizontal to vertical layout.
+ */
+void SwTextFrame::SwitchHorizontalToVertical( SwRect& rRect ) const
+{
+ // calc offset inside frame
+ tools::Long nOfstX, nOfstY;
+ if ( IsVertLR() )
+ {
+ if (IsVertLRBT())
+ {
+ // X and Y offsets here mean the position of the point that will be the top left corner
+ // after the switch.
+ nOfstX = rRect.Left() + rRect.Width() - getFrameArea().Left();
+ nOfstY = rRect.Top() - getFrameArea().Top();
+ }
+ else
+ {
+ nOfstX = rRect.Left() - getFrameArea().Left();
+ nOfstY = rRect.Top() - getFrameArea().Top();
+ }
+ }
+ else
+ {
+ nOfstX = rRect.Left() - getFrameArea().Left();
+ nOfstY = rRect.Top() + rRect.Height() - getFrameArea().Top();
+ }
+
+ const tools::Long nWidth = rRect.Width();
+ const tools::Long nHeight = rRect.Height();
+
+ if ( IsVertLR() )
+ {
+ rRect.Left(getFrameArea().Left() + nOfstY);
+ }
+ else
+ {
+ if ( mbIsSwapped )
+ rRect.Left( getFrameArea().Left() + getFrameArea().Height() - nOfstY );
+ else
+ // frame is rotated
+ rRect.Left( getFrameArea().Left() + getFrameArea().Width() - nOfstY );
+ }
+
+ if (IsVertLRBT())
+ {
+ if (mbIsSwapped)
+ rRect.Top(getFrameArea().Top() + getFrameArea().Width() - nOfstX);
+ else
+ rRect.Top(getFrameArea().Top() + getFrameArea().Height() - nOfstX);
+ }
+ else
+ rRect.Top(getFrameArea().Top() + nOfstX);
+ rRect.Width( nHeight );
+ rRect.Height( nWidth );
+}
+
+/**
+ * Calculates the coordinates of a point when switching from
+ * horizontal to vertical layout.
+ */
+void SwTextFrame::SwitchHorizontalToVertical( Point& rPoint ) const
+{
+ if (IsVertLRBT())
+ {
+ // The horizontal origo is the top left corner, the LRBT origo is the
+ // bottom left corner. Finally x and y has to be swapped.
+ SAL_WARN_IF(!mbIsSwapped, "sw.core",
+ "SwTextFrame::SwitchHorizontalToVertical, IsVertLRBT, not swapped");
+ Point aPoint(rPoint);
+ rPoint.setX(getFrameArea().Left() + (aPoint.Y() - getFrameArea().Top()));
+ // This would be bottom - x delta, but bottom is top + height, finally
+ // width (and not height), as it's swapped.
+ rPoint.setY(getFrameArea().Top() + getFrameArea().Width()
+ - (aPoint.X() - getFrameArea().Left()));
+ return;
+ }
+
+ // calc offset inside frame
+ const tools::Long nOfstX = rPoint.X() - getFrameArea().Left();
+ const tools::Long nOfstY = rPoint.Y() - getFrameArea().Top();
+ if ( IsVertLR() )
+ rPoint.setX( getFrameArea().Left() + nOfstY );
+ else
+ {
+ if ( mbIsSwapped )
+ rPoint.setX( getFrameArea().Left() + getFrameArea().Height() - nOfstY );
+ else
+ // calc rotated coords
+ rPoint.setX( getFrameArea().Left() + getFrameArea().Width() - nOfstY );
+ }
+
+ rPoint.setY( getFrameArea().Top() + nOfstX );
+}
+
+/**
+ * Calculates the a limit value when switching from
+ * horizontal to vertical layout.
+ */
+tools::Long SwTextFrame::SwitchHorizontalToVertical( tools::Long nLimit ) const
+{
+ Point aTmp( 0, nLimit );
+ SwitchHorizontalToVertical( aTmp );
+ return aTmp.X();
+}
+
+/**
+ * Calculates the coordinates of a rectangle when switching from
+ * vertical to horizontal layout.
+ */
+void SwTextFrame::SwitchVerticalToHorizontal( SwRect& rRect ) const
+{
+ tools::Long nOfstX;
+
+ // calc offset inside frame
+ if ( IsVertLR() )
+ nOfstX = rRect.Left() - getFrameArea().Left();
+ else
+ {
+ if ( mbIsSwapped )
+ nOfstX = getFrameArea().Left() + getFrameArea().Height() - ( rRect.Left() + rRect.Width() );
+ else
+ nOfstX = getFrameArea().Left() + getFrameArea().Width() - ( rRect.Left() + rRect.Width() );
+ }
+
+ tools::Long nOfstY;
+ if (IsVertLRBT())
+ {
+ // Note that mbIsSwapped only affects the frame area, not rRect, so rRect.Height() is used
+ // here unconditionally.
+ if (mbIsSwapped)
+ nOfstY = getFrameArea().Top() + getFrameArea().Width() - (rRect.Top() + rRect.Height());
+ else
+ nOfstY = getFrameArea().Top() + getFrameArea().Height() - (rRect.Top() + rRect.Height());
+ }
+ else
+ nOfstY = rRect.Top() - getFrameArea().Top();
+ const tools::Long nWidth = rRect.Height();
+ const tools::Long nHeight = rRect.Width();
+
+ // calc rotated coords
+ rRect.Left( getFrameArea().Left() + nOfstY );
+ rRect.Top( getFrameArea().Top() + nOfstX );
+ rRect.Width( nWidth );
+ rRect.Height( nHeight );
+}
+
+/**
+ * Calculates the coordinates of a point when switching from
+ * vertical to horizontal layout.
+ */
+void SwTextFrame::SwitchVerticalToHorizontal( Point& rPoint ) const
+{
+ tools::Long nOfstX;
+
+ // calc offset inside frame
+ if ( IsVertLR() )
+ // X offset is Y - left.
+ nOfstX = rPoint.X() - getFrameArea().Left();
+ else
+ {
+ // X offset is right - X.
+ if ( mbIsSwapped )
+ nOfstX = getFrameArea().Left() + getFrameArea().Height() - rPoint.X();
+ else
+ nOfstX = getFrameArea().Left() + getFrameArea().Width() - rPoint.X();
+ }
+
+ tools::Long nOfstY;
+ if (IsVertLRBT())
+ {
+ // Y offset is bottom - Y.
+ if (mbIsSwapped)
+ nOfstY = getFrameArea().Top() + getFrameArea().Width() - rPoint.Y();
+ else
+ nOfstY = getFrameArea().Top() + getFrameArea().Height() - rPoint.Y();
+ }
+ else
+ // Y offset is Y - top.
+ nOfstY = rPoint.Y() - getFrameArea().Top();
+
+ // calc rotated coords
+ rPoint.setX( getFrameArea().Left() + nOfstY );
+ rPoint.setY( getFrameArea().Top() + nOfstX );
+}
+
+/**
+ * Calculates the a limit value when switching from
+ * vertical to horizontal layout.
+ */
+tools::Long SwTextFrame::SwitchVerticalToHorizontal( tools::Long nLimit ) const
+{
+ Point aTmp( nLimit, 0 );
+ SwitchVerticalToHorizontal( aTmp );
+ return aTmp.Y();
+}
+
+SwFrameSwapper::SwFrameSwapper( const SwTextFrame* pTextFrame, bool bSwapIfNotSwapped )
+ : pFrame( pTextFrame ), bUndo( false )
+{
+ if (pFrame->IsVertical() && bSwapIfNotSwapped != pFrame->IsSwapped())
+ {
+ bUndo = true;
+ const_cast<SwTextFrame*>(pFrame)->SwapWidthAndHeight();
+ }
+}
+
+SwFrameSwapper::~SwFrameSwapper()
+{
+ if ( bUndo )
+ const_cast<SwTextFrame*>(pFrame)->SwapWidthAndHeight();
+}
+
+void SwTextFrame::SwitchLTRtoRTL( SwRect& rRect ) const
+{
+ SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this));
+
+ tools::Long nWidth = rRect.Width();
+ rRect.Left( 2 * ( getFrameArea().Left() + getFramePrintArea().Left() ) +
+ getFramePrintArea().Width() - rRect.Right() - 1 );
+
+ rRect.Width( nWidth );
+}
+
+void SwTextFrame::SwitchLTRtoRTL( Point& rPoint ) const
+{
+ SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this));
+
+ rPoint.setX( 2 * ( getFrameArea().Left() + getFramePrintArea().Left() ) + getFramePrintArea().Width() - rPoint.X() - 1 );
+}
+
+SwLayoutModeModifier::SwLayoutModeModifier( const OutputDevice& rOutp ) :
+ m_rOut( rOutp ), m_nOldLayoutMode( rOutp.GetLayoutMode() )
+{
+}
+
+SwLayoutModeModifier::~SwLayoutModeModifier()
+{
+ const_cast<OutputDevice&>(m_rOut).SetLayoutMode( m_nOldLayoutMode );
+}
+
+void SwLayoutModeModifier::Modify( bool bChgToRTL )
+{
+ const_cast<OutputDevice&>(m_rOut).SetLayoutMode( bChgToRTL ?
+ vcl::text::ComplexTextLayoutFlags::BiDiStrong | vcl::text::ComplexTextLayoutFlags::BiDiRtl :
+ vcl::text::ComplexTextLayoutFlags::BiDiStrong );
+}
+
+void SwLayoutModeModifier::SetAuto()
+{
+ const vcl::text::ComplexTextLayoutFlags nNewLayoutMode = m_nOldLayoutMode & ~vcl::text::ComplexTextLayoutFlags::BiDiStrong;
+ const_cast<OutputDevice&>(m_rOut).SetLayoutMode( nNewLayoutMode );
+}
+
+SwDigitModeModifier::SwDigitModeModifier( const OutputDevice& rOutp, LanguageType eCurLang ) :
+ rOut( rOutp ), nOldLanguageType( rOutp.GetDigitLanguage() )
+{
+ LanguageType eLang = eCurLang;
+ if (utl::ConfigManager::IsFuzzing())
+ eLang = LANGUAGE_ENGLISH_US;
+ else
+ {
+ const SvtCTLOptions::TextNumerals nTextNumerals = SvtCTLOptions::GetCTLTextNumerals();
+
+ if ( SvtCTLOptions::NUMERALS_HINDI == nTextNumerals )
+ eLang = LANGUAGE_ARABIC_SAUDI_ARABIA;
+ else if ( SvtCTLOptions::NUMERALS_ARABIC == nTextNumerals )
+ eLang = LANGUAGE_ENGLISH;
+ else if ( SvtCTLOptions::NUMERALS_SYSTEM == nTextNumerals )
+ eLang = ::GetAppLanguage();
+ }
+
+ const_cast<OutputDevice&>(rOut).SetDigitLanguage( eLang );
+}
+
+SwDigitModeModifier::~SwDigitModeModifier()
+{
+ const_cast<OutputDevice&>(rOut).SetDigitLanguage( nOldLanguageType );
+}
+
+void SwTextFrame::Init()
+{
+ OSL_ENSURE( !IsLocked(), "+SwTextFrame::Init: this is locked." );
+ if( !IsLocked() )
+ {
+ ClearPara();
+ SetHasRotatedPortions(false);
+ // set flags directly to save a ResetPreps call,
+ // and thereby an unnecessary GetPara call
+ // don't set bOrphan, bLocked or bWait to false!
+ // bOrphan = bFlag7 = bFlag8 = false;
+ }
+}
+
+SwTextFrame::SwTextFrame(SwTextNode * const pNode, SwFrame* pSib,
+ sw::FrameMode const eMode)
+ : SwContentFrame( pNode, pSib )
+ , mnAllLines( 0 )
+ , mnThisLines( 0 )
+ , mnFlyAnchorOfst( 0 )
+ , mnFlyAnchorOfstNoWrap( 0 )
+ , mnFlyAnchorVertOfstNoWrap( 0 )
+ , mnFootnoteLine( 0 )
+ , mnHeightOfLastLine( 0 )
+ , mnAdditionalFirstLineOffset( 0 )
+ , mnOffset( 0 )
+ , mnCacheIndex( USHRT_MAX )
+ , mbLocked( false )
+ , mbWidow( false )
+ , mbJustWidow( false )
+ , mbEmpty( false )
+ , mbInFootnoteConnect( false )
+ , mbFootnote( false )
+ , mbRepaint( false )
+ , mbHasRotatedPortions( false )
+ , mbFieldFollow( false )
+ , mbHasAnimation( false )
+ , mbIsSwapped( false )
+ , mbFollowFormatAllowed( true )
+{
+ mnFrameType = SwFrameType::Txt;
+ // note: this may call SwClientNotify if it's in a list so do it last
+ // note: this may change this->pRegisteredIn to m_pMergedPara->listeners
+ m_pMergedPara = CheckParaRedlineMerge(*this, *pNode, eMode);
+}
+
+void SwTextFrame::dumpAsXmlAttributes(xmlTextWriterPtr writer) const
+{
+ SwContentFrame::dumpAsXmlAttributes(writer);
+
+ const SwTextNode *pTextNode = GetTextNodeFirst();
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "txtNodeIndex" ), "%" SAL_PRIdINT32, sal_Int32(pTextNode->GetIndex()) );
+
+ OString aMode = "Horizontal"_ostr;
+ if (IsVertLRBT())
+ {
+ aMode = "VertBTLR"_ostr;
+ }
+ else if (IsVertLR())
+ {
+ aMode = "VertLR"_ostr;
+ }
+ else if (IsVertical())
+ {
+ aMode = "Vertical"_ostr;
+ }
+ (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("WritingMode"), BAD_CAST(aMode.getStr()));
+}
+
+void SwTextFrame::dumpAsXml(xmlTextWriterPtr writer) const
+{
+ (void)xmlTextWriterStartElement(writer, reinterpret_cast<const xmlChar*>("txt"));
+ dumpAsXmlAttributes( writer );
+ if ( HasFollow() )
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "follow" ), "%" SAL_PRIuUINT32, GetFollow()->GetFrameId() );
+
+ if (m_pPrecede != nullptr)
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "precede" ), "%" SAL_PRIuUINT32, static_cast<SwTextFrame*>(m_pPrecede)->GetFrameId() );
+
+ (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("offset"), BAD_CAST(OString::number(static_cast<sal_Int32>(mnOffset)).getStr()));
+
+ sw::MergedPara const*const pMerged(GetMergedPara());
+ if (pMerged)
+ {
+ (void)xmlTextWriterStartElement( writer, BAD_CAST( "merged" ) );
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "paraPropsNodeIndex" ), "%" SAL_PRIdINT32, sal_Int32(pMerged->pParaPropsNode->GetIndex()) );
+ for (auto const& e : pMerged->extents)
+ {
+ (void)xmlTextWriterStartElement( writer, BAD_CAST( "extent" ) );
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "txtNodeIndex" ), "%" SAL_PRIdINT32, sal_Int32(e.pNode->GetIndex()) );
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "start" ), "%" SAL_PRIdINT32, e.nStart );
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "end" ), "%" SAL_PRIdINT32, e.nEnd );
+ (void)xmlTextWriterEndElement( writer );
+ }
+ (void)xmlTextWriterEndElement( writer );
+ }
+
+ (void)xmlTextWriterStartElement(writer, BAD_CAST("infos"));
+ dumpInfosAsXml(writer);
+ (void)xmlTextWriterEndElement(writer);
+
+ // Dump Anchored objects if any
+ const SwSortedObjs* pAnchored = GetDrawObjs();
+ if ( pAnchored && pAnchored->size() > 0 )
+ {
+ (void)xmlTextWriterStartElement( writer, BAD_CAST( "anchored" ) );
+
+ for (SwAnchoredObject* pObject : *pAnchored)
+ {
+ pObject->dumpAsXml( writer );
+ }
+
+ (void)xmlTextWriterEndElement( writer );
+ }
+
+ // Dump the children
+ OUString aText = GetText( );
+ for ( int i = 0; i < 32; i++ )
+ {
+ aText = aText.replace( i, '*' );
+ }
+ auto nTextOffset = static_cast<sal_Int32>(GetOffset());
+ sal_Int32 nTextLength = aText.getLength() - nTextOffset;
+ if (const SwTextFrame* pTextFrameFollow = GetFollow())
+ {
+ nTextLength = static_cast<sal_Int32>(pTextFrameFollow->GetOffset() - GetOffset());
+ }
+ if (nTextLength > 0)
+ {
+ OString aText8
+ = OUStringToOString(aText.subView(nTextOffset, nTextLength), RTL_TEXTENCODING_UTF8);
+ (void)xmlTextWriterWriteString( writer,
+ reinterpret_cast<const xmlChar *>(aText8.getStr( )) );
+ }
+ if (const SwParaPortion* pPara = GetPara())
+ {
+ (void)xmlTextWriterStartElement(writer, BAD_CAST("SwParaPortion"));
+ TextFrameIndex nOffset(0);
+ const OUString& rText = GetText();
+ (void)xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("ptr"), "%p", pPara);
+ const SwLineLayout* pLine = pPara;
+ if (IsFollow())
+ {
+ nOffset += GetOffset();
+ }
+ while (pLine)
+ {
+ (void)xmlTextWriterStartElement(writer, BAD_CAST("SwLineLayout"));
+ pLine->dumpAsXmlAttributes(writer, rText, nOffset);
+ const SwLinePortion* pPor = pLine->GetFirstPortion();
+ while (pPor)
+ {
+ pPor->dumpAsXml(writer, rText, nOffset);
+ pPor = pPor->GetNextPortion();
+ }
+ (void)xmlTextWriterEndElement(writer);
+ pLine = pLine->GetNext();
+ }
+ (void)xmlTextWriterEndElement(writer);
+ }
+
+ (void)xmlTextWriterEndElement(writer);
+}
+
+namespace sw {
+
+SwTextFrame * MakeTextFrame(SwTextNode & rNode, SwFrame *const pSibling,
+ sw::FrameMode const eMode)
+{
+ return new SwTextFrame(&rNode, pSibling, eMode);
+}
+
+void RemoveFootnotesForNode(
+ SwRootFrame const& rLayout, SwTextNode const& rTextNode,
+ std::vector<std::pair<sal_Int32, sal_Int32>> const*const pExtents)
+{
+ if (pExtents && pExtents->empty())
+ {
+ return; // nothing to do
+ }
+ const SwFootnoteIdxs &rFootnoteIdxs = rTextNode.GetDoc().GetFootnoteIdxs();
+ size_t nPos = 0;
+ SwNodeOffset const nIndex = rTextNode.GetIndex();
+ rFootnoteIdxs.SeekEntry( rTextNode, &nPos );
+ if (nPos < rFootnoteIdxs.size())
+ {
+ while (nPos && rTextNode == (rFootnoteIdxs[ nPos ]->GetTextNode()))
+ --nPos;
+ if (nPos || rTextNode != (rFootnoteIdxs[ nPos ]->GetTextNode()))
+ ++nPos;
+ }
+ size_t iter(0);
+ for ( ; nPos < rFootnoteIdxs.size(); ++nPos)
+ {
+ SwTextFootnote* pTextFootnote = rFootnoteIdxs[ nPos ];
+ if (pTextFootnote->GetTextNode().GetIndex() > nIndex)
+ break;
+ if (pExtents)
+ {
+ while ((*pExtents)[iter].second <= pTextFootnote->GetStart())
+ {
+ ++iter;
+ if (iter == pExtents->size())
+ {
+ return;
+ }
+ }
+ if (pTextFootnote->GetStart() < (*pExtents)[iter].first)
+ {
+ continue;
+ }
+ }
+ pTextFootnote->DelFrames(&rLayout);
+ }
+}
+
+} // namespace sw
+
+void SwTextFrame::DestroyImpl()
+{
+ // Remove associated SwParaPortion from s_pTextCache
+ ClearPara();
+
+ assert(!GetDoc().IsInDtor()); // this shouldn't be happening with ViewShell owning layout
+ if (!GetDoc().IsInDtor() && HasFootnote())
+ {
+ if (m_pMergedPara)
+ {
+ SwTextNode const* pNode(nullptr);
+ for (auto const& e : m_pMergedPara->extents)
+ {
+ if (e.pNode != pNode)
+ {
+ pNode = e.pNode;
+ // sw_redlinehide: not sure if it's necessary to check
+ // if the nodes are still alive here, which would require
+ // accessing WriterMultiListener::m_vDepends
+ sw::RemoveFootnotesForNode(*getRootFrame(), *pNode, nullptr);
+ }
+ }
+ }
+ else
+ {
+ SwTextNode *const pNode(static_cast<SwTextNode*>(GetDep()));
+ if (pNode)
+ {
+ sw::RemoveFootnotesForNode(*getRootFrame(), *pNode, nullptr);
+ }
+ }
+ }
+
+ if (!GetDoc().IsInDtor())
+ {
+ if (SwView* pView = GetActiveView())
+ pView->GetEditWin().GetFrameControlsManager().RemoveControls(this);
+ }
+
+ SwContentFrame::DestroyImpl();
+}
+
+SwTextFrame::~SwTextFrame()
+{
+ RemoveFromCache();
+}
+
+namespace sw {
+
+// 1. if real insert => correct nStart/nEnd for full nLen
+// 2. if rl un-delete => do not correct nStart/nEnd but just include un-deleted
+static TextFrameIndex UpdateMergedParaForInsert(MergedPara & rMerged,
+ bool const isRealInsert,
+ SwTextNode const& rNode, sal_Int32 const nIndex, sal_Int32 const nLen)
+{
+ assert(!isRealInsert || nLen); // can 0 happen? yes, for redline in empty node
+ assert(nIndex <= rNode.Len());
+ assert(nIndex + nLen <= rNode.Len());
+ assert(rMerged.pFirstNode->GetIndex() <= rNode.GetIndex() && rNode.GetIndex() <= rMerged.pLastNode->GetIndex());
+ if (!nLen)
+ {
+ return TextFrameIndex(0);
+ }
+ OUStringBuffer text(rMerged.mergedText);
+ sal_Int32 nTFIndex(0); // index used for insertion at the end
+ sal_Int32 nInserted(0);
+ bool bInserted(false);
+ bool bFoundNode(false);
+ auto itInsert(rMerged.extents.end());
+ for (auto it = rMerged.extents.begin(); it != rMerged.extents.end(); ++it)
+ {
+ if (it->pNode == &rNode)
+ {
+ if (isRealInsert)
+ {
+ bFoundNode = true;
+ if (it->nStart <= nIndex && nIndex <= it->nEnd)
+ { // note: this can happen only once
+ text.insert(nTFIndex + (nIndex - it->nStart),
+ rNode.GetText().subView(nIndex, nLen));
+ it->nEnd += nLen;
+ nInserted = nLen;
+ assert(!bInserted);
+ bInserted = true;
+ }
+ else if (nIndex < it->nStart)
+ {
+ if (itInsert == rMerged.extents.end())
+ {
+ itInsert = it;
+ }
+ it->nStart += nLen;
+ it->nEnd += nLen;
+ }
+ }
+ else
+ {
+ assert(it == rMerged.extents.begin() || (it-1)->pNode != &rNode || (it-1)->nEnd < nIndex);
+ if (nIndex + nLen < it->nStart)
+ {
+ itInsert = it;
+ break;
+ }
+ if (nIndex < it->nStart)
+ {
+ text.insert(nTFIndex,
+ rNode.GetText().subView(nIndex, it->nStart - nIndex));
+ nInserted += it->nStart - nIndex;
+ it->nStart = nIndex;
+ bInserted = true;
+ }
+ assert(it->nStart <= nIndex);
+ if (nIndex <= it->nEnd)
+ {
+ nTFIndex += it->nEnd - it->nStart;
+ while (it->nEnd < nIndex + nLen)
+ {
+ auto *const pNext(
+ (it+1) != rMerged.extents.end() && (it+1)->pNode == it->pNode
+ ? &*(it+1)
+ : nullptr);
+ if (pNext && pNext->nStart <= nIndex + nLen)
+ {
+ text.insert(nTFIndex,
+ rNode.GetText().subView(it->nEnd, pNext->nStart - it->nEnd));
+ nTFIndex += pNext->nStart - it->nEnd;
+ nInserted += pNext->nStart - it->nEnd;
+ pNext->nStart = it->nStart;
+ it = rMerged.extents.erase(it);
+ }
+ else
+ {
+ text.insert(nTFIndex,
+ rNode.GetText().subView(it->nEnd, nIndex + nLen - it->nEnd));
+ nTFIndex += nIndex + nLen - it->nEnd;
+ nInserted += nIndex + nLen - it->nEnd;
+ it->nEnd = nIndex + nLen;
+ }
+ }
+ bInserted = true;
+ break;
+ }
+ }
+ }
+ else if (rNode.GetIndex() < it->pNode->GetIndex() || bFoundNode)
+ {
+ if (itInsert == rMerged.extents.end())
+ {
+ itInsert = it;
+ }
+ break;
+ }
+ if (itInsert == rMerged.extents.end())
+ {
+ nTFIndex += it->nEnd - it->nStart;
+ }
+ }
+// assert((bFoundNode || rMerged.extents.empty()) && "text node not found - why is it sending hints to us");
+ if (!bInserted)
+ { // must be in a gap
+ rMerged.extents.emplace(itInsert, const_cast<SwTextNode*>(&rNode), nIndex, nIndex + nLen);
+ text.insert(nTFIndex, rNode.GetText().subView(nIndex, nLen));
+ nInserted = nLen;
+ if (rMerged.extents.size() == 1 // also if it was empty!
+ || rMerged.pParaPropsNode->GetIndex() < rNode.GetIndex())
+ { // text inserted after current para-props node
+ rMerged.pParaPropsNode->RemoveFromListRLHidden();
+ rMerged.pParaPropsNode = &const_cast<SwTextNode&>(rNode);
+ rMerged.pParaPropsNode->AddToListRLHidden();
+ }
+ // called from SwRangeRedline::InvalidateRange()
+ if (rNode.GetRedlineMergeFlag() == SwNode::Merge::Hidden)
+ {
+ const_cast<SwTextNode&>(rNode).SetRedlineMergeFlag(SwNode::Merge::NonFirst);
+ }
+ }
+ rMerged.mergedText = text.makeStringAndClear();
+ return TextFrameIndex(nInserted);
+}
+
+// 1. if real delete => correct nStart/nEnd for full nLen
+// 2. if rl delete => do not correct nStart/nEnd but just exclude deleted
+TextFrameIndex UpdateMergedParaForDelete(MergedPara & rMerged,
+ bool const isRealDelete,
+ SwTextNode const& rNode, sal_Int32 nIndex, sal_Int32 const nLen)
+{
+ assert(nIndex <= rNode.Len());
+ assert(rMerged.pFirstNode->GetIndex() <= rNode.GetIndex() && rNode.GetIndex() <= rMerged.pLastNode->GetIndex());
+ OUStringBuffer text(rMerged.mergedText);
+ sal_Int32 nTFIndex(0);
+ sal_Int32 nToDelete(nLen);
+ sal_Int32 nDeleted(0);
+ size_t nFoundNode(0);
+ size_t nErased(0);
+ auto it = rMerged.extents.begin();
+ for (; it != rMerged.extents.end(); )
+ {
+ bool bErase(false);
+ if (it->pNode == &rNode)
+ {
+ ++nFoundNode;
+ if (nIndex + nToDelete < it->nStart)
+ {
+ nToDelete = 0;
+ if (!isRealDelete)
+ {
+ break;
+ }
+ it->nStart -= nLen;
+ it->nEnd -= nLen;
+ }
+ else
+ {
+ if (nIndex < it->nStart)
+ {
+ // do not adjust nIndex into the text frame index space!
+ nToDelete -= it->nStart - nIndex;
+ nIndex = it->nStart;
+ // note: continue with the if check below, no else!
+ }
+ if (it->nStart <= nIndex && nIndex < it->nEnd)
+ {
+ sal_Int32 const nDeleteHere(nIndex + nToDelete <= it->nEnd
+ ? nToDelete
+ : it->nEnd - nIndex);
+ text.remove(nTFIndex + (nIndex - it->nStart), nDeleteHere);
+ bErase = nDeleteHere == it->nEnd - it->nStart;
+ if (bErase)
+ {
+ ++nErased;
+ assert(it->nStart == nIndex);
+ it = rMerged.extents.erase(it);
+ }
+ else if (isRealDelete)
+ { // adjust for deleted text
+ it->nStart -= (nLen - nToDelete);
+ it->nEnd -= (nLen - nToDelete + nDeleteHere);
+ if (it != rMerged.extents.begin()
+ && (it-1)->pNode == &rNode
+ && (it-1)->nEnd == it->nStart)
+ { // merge adjacent extents
+ nTFIndex += it->nEnd - it->nStart;
+ (it-1)->nEnd = it->nEnd;
+ it = rMerged.extents.erase(it);
+ bErase = true; // skip increment
+ }
+ }
+ else
+ { // exclude text marked as deleted
+ if (nIndex + nDeleteHere == it->nEnd)
+ {
+ it->nEnd -= nDeleteHere;
+ }
+ else
+ {
+ if (nIndex == it->nStart)
+ {
+ it->nStart += nDeleteHere;
+ }
+ else
+ {
+ sal_Int32 const nOldEnd(it->nEnd);
+ it->nEnd = nIndex;
+ it = rMerged.extents.emplace(it+1,
+ it->pNode, nIndex + nDeleteHere, nOldEnd);
+ }
+ assert(nDeleteHere == nToDelete);
+ }
+ }
+ nDeleted += nDeleteHere;
+ nToDelete -= nDeleteHere;
+ nIndex += nDeleteHere;
+ if (!isRealDelete && nToDelete == 0)
+ {
+ break;
+ }
+ }
+ }
+ }
+ else if (nFoundNode != 0)
+ {
+ break;
+ }
+ if (!bErase)
+ {
+ nTFIndex += it->nEnd - it->nStart;
+ ++it;
+ }
+ }
+// assert(nFoundNode != 0 && "text node not found - why is it sending hints to us");
+ assert(nIndex <= rNode.Len() + nLen);
+ // if there's a remaining deletion, it must be in gap at the end of the node
+// can't do: might be last one in node was erased assert(nLen == 0 || rMerged.empty() || (it-1)->nEnd <= nIndex);
+ // note: if first node gets deleted then that must call DelFrames as
+ // pFirstNode is never updated
+ if (nErased && nErased == nFoundNode)
+ { // all visible text from node was erased
+#if 1
+ if (rMerged.pParaPropsNode == &rNode)
+ {
+ rMerged.pParaPropsNode->RemoveFromListRLHidden();
+ rMerged.pParaPropsNode = rMerged.extents.empty()
+ ? const_cast<SwTextNode*>(rMerged.pLastNode)
+ : rMerged.extents.front().pNode;
+ rMerged.pParaPropsNode->AddToListRLHidden();
+ }
+#endif
+// NOPE must listen on all non-hidden nodes; particularly on pLastNode rMerged.listener.EndListening(&const_cast<SwTextNode&>(rNode));
+ }
+ rMerged.mergedText = text.makeStringAndClear();
+ return TextFrameIndex(nDeleted);
+}
+
+std::pair<SwTextNode*, sal_Int32>
+MapViewToModel(MergedPara const& rMerged, TextFrameIndex const i_nIndex)
+{
+ sal_Int32 nIndex(i_nIndex);
+ sw::Extent const* pExtent(nullptr);
+ for (const auto& rExt : rMerged.extents)
+ {
+ pExtent = &rExt;
+ if (nIndex < (pExtent->nEnd - pExtent->nStart))
+ {
+ return std::make_pair(pExtent->pNode, pExtent->nStart + nIndex);
+ }
+ nIndex = nIndex - (pExtent->nEnd - pExtent->nStart);
+ }
+ assert(nIndex == 0 && "view index out of bounds");
+ return pExtent
+ ? std::make_pair(pExtent->pNode, pExtent->nEnd) //1-past-the-end index
+ : std::make_pair(const_cast<SwTextNode*>(rMerged.pLastNode), rMerged.pLastNode->Len());
+}
+
+TextFrameIndex MapModelToView(MergedPara const& rMerged, SwTextNode const*const pNode, sal_Int32 const nIndex)
+{
+ assert(rMerged.pFirstNode->GetIndex() <= pNode->GetIndex()
+ && pNode->GetIndex() <= rMerged.pLastNode->GetIndex());
+ sal_Int32 nRet(0);
+ bool bFoundNode(false);
+ for (auto const& e : rMerged.extents)
+ {
+ if (pNode->GetIndex() < e.pNode->GetIndex())
+ {
+ return TextFrameIndex(nRet);
+ }
+ if (e.pNode == pNode)
+ {
+ if (e.nStart <= nIndex && nIndex <= e.nEnd)
+ {
+ return TextFrameIndex(nRet + (nIndex - e.nStart));
+ }
+ else if (nIndex < e.nStart)
+ {
+ // in gap before this extent => map to 0 here TODO???
+ return TextFrameIndex(nRet);
+ }
+ bFoundNode = true;
+ }
+ else if (bFoundNode)
+ {
+ break;
+ }
+ nRet += e.nEnd - e.nStart;
+ }
+ if (bFoundNode)
+ {
+ // must be in a gap at the end of the node
+ assert(nIndex <= pNode->Len());
+ return TextFrameIndex(nRet);
+ }
+ else if (rMerged.extents.empty())
+ {
+ assert(nIndex <= pNode->Len());
+ return TextFrameIndex(0);
+ }
+ return TextFrameIndex(rMerged.mergedText.getLength());
+}
+
+} // namespace sw
+
+std::pair<SwTextNode*, sal_Int32>
+SwTextFrame::MapViewToModel(TextFrameIndex const nIndex) const
+{
+//nope assert(GetPara());
+ sw::MergedPara const*const pMerged(GetMergedPara());
+ if (pMerged)
+ {
+ return sw::MapViewToModel(*pMerged, nIndex);
+ }
+ else
+ {
+ return std::make_pair(static_cast<SwTextNode*>(const_cast<sw::BroadcastingModify*>(
+ SwFrame::GetDep())), sal_Int32(nIndex));
+ }
+}
+
+SwPosition SwTextFrame::MapViewToModelPos(TextFrameIndex const nIndex) const
+{
+ std::pair<SwTextNode*, sal_Int32> const ret(MapViewToModel(nIndex));
+ return SwPosition(*ret.first, ret.second);
+}
+
+TextFrameIndex SwTextFrame::MapModelToView(SwTextNode const*const pNode, sal_Int32 const nIndex) const
+{
+//nope assert(GetPara());
+ sw::MergedPara const*const pMerged(GetMergedPara());
+ if (pMerged)
+ {
+ return sw::MapModelToView(*pMerged, pNode, nIndex);
+ }
+ else
+ {
+ assert(static_cast<const SwTextNode*>(SwFrame::GetDep()) == pNode);
+ return TextFrameIndex(nIndex);
+ }
+}
+
+TextFrameIndex SwTextFrame::MapModelToViewPos(SwPosition const& rPos) const
+{
+ SwTextNode const*const pNode(rPos.GetNode().GetTextNode());
+ sal_Int32 const nIndex(rPos.GetContentIndex());
+ return MapModelToView(pNode, nIndex);
+}
+
+void SwTextFrame::SetMergedPara(std::unique_ptr<sw::MergedPara> p)
+{
+ SwTextNode *const pFirst(m_pMergedPara ? m_pMergedPara->pFirstNode : nullptr);
+ m_pMergedPara = std::move(p);
+ if (pFirst)
+ {
+ if (m_pMergedPara)
+ {
+ assert(pFirst == m_pMergedPara->pFirstNode);
+ }
+ else
+ {
+ pFirst->Add(this); // must register at node again
+ }
+ }
+ // postcondition: frame must be listening somewhere
+ assert(m_pMergedPara || GetDep());
+}
+
+const OUString& SwTextFrame::GetText() const
+{
+//nope assert(GetPara());
+ sw::MergedPara const*const pMerged(GetMergedPara());
+ if (pMerged)
+ return pMerged->mergedText;
+ else
+ return static_cast<SwTextNode const*>(SwFrame::GetDep())->GetText();
+}
+
+SwTextNode const* SwTextFrame::GetTextNodeForParaProps() const
+{
+ // FIXME can GetPara be 0 ? yes... this is needed in SwContentNotify::SwContentNotify() which is called before any formatting is started
+//nope assert(GetPara());
+ sw::MergedPara const*const pMerged(GetMergedPara());
+ if (pMerged)
+ {
+// assert(pMerged->pFirstNode == pMerged->pParaPropsNode); // surprising news!
+ return pMerged->pParaPropsNode;
+ }
+ else
+ return static_cast<SwTextNode const*>(SwFrame::GetDep());
+}
+
+SwTextNode const* SwTextFrame::GetTextNodeForFirstText() const
+{
+ sw::MergedPara const*const pMerged(GetMergedPara());
+ if (pMerged)
+ return pMerged->extents.empty()
+ ? pMerged->pFirstNode
+ : pMerged->extents.front().pNode;
+ else
+ return static_cast<SwTextNode const*>(SwFrame::GetDep());
+}
+
+SwTextNode const* SwTextFrame::GetTextNodeFirst() const
+{
+//nope assert(GetPara());
+ sw::MergedPara const*const pMerged(GetMergedPara());
+ if (pMerged)
+ return pMerged->pFirstNode;
+ else
+ return static_cast<SwTextNode const*>(SwFrame::GetDep());
+}
+
+SwDoc const& SwTextFrame::GetDoc() const
+{
+ return GetTextNodeFirst()->GetDoc();
+}
+
+LanguageType SwTextFrame::GetLangOfChar(TextFrameIndex const nIndex,
+ sal_uInt16 const nScript, bool const bNoChar) const
+{
+ // a single character can be mapped uniquely!
+ std::pair<SwTextNode const*, sal_Int32> const pos(MapViewToModel(nIndex));
+ return pos.first->GetLang(pos.second, bNoChar ? 0 : 1, nScript);
+}
+
+void SwTextFrame::ResetPreps()
+{
+ if ( GetCacheIdx() != USHRT_MAX )
+ {
+ if (SwParaPortion *pPara = GetPara())
+ pPara->ResetPreps();
+ }
+}
+
+bool SwTextFrame::IsHiddenNow() const
+{
+ SwFrameSwapper aSwapper( this, true );
+
+ if( !getFrameArea().Width() && isFrameAreaDefinitionValid() && GetUpper()->isFrameAreaDefinitionValid() ) // invalid when stack overflows (StackHack)!
+ {
+// OSL_FAIL( "SwTextFrame::IsHiddenNow: thin frame" );
+ return true;
+ }
+
+ bool bHiddenCharsHidePara(false);
+ bool bHiddenParaField(false);
+ if (m_pMergedPara)
+ {
+ TextFrameIndex nHiddenStart(COMPLETE_STRING);
+ TextFrameIndex nHiddenEnd(0);
+ if (auto const pScriptInfo = GetScriptInfo())
+ {
+ pScriptInfo->GetBoundsOfHiddenRange(TextFrameIndex(0),
+ nHiddenStart, nHiddenEnd);
+ }
+ else // ParaPortion is created in Format, but this is called earlier
+ {
+ SwScriptInfo aInfo;
+ aInfo.InitScriptInfo(*m_pMergedPara->pFirstNode, m_pMergedPara.get(), IsRightToLeft());
+ aInfo.GetBoundsOfHiddenRange(TextFrameIndex(0),
+ nHiddenStart, nHiddenEnd);
+ }
+ if (TextFrameIndex(0) == nHiddenStart &&
+ TextFrameIndex(GetText().getLength()) <= nHiddenEnd)
+ {
+ bHiddenCharsHidePara = true;
+ }
+ sw::MergedAttrIter iter(*this);
+ SwTextNode const* pNode(nullptr);
+ int nNewResultWeight = 0;
+ for (SwTextAttr const* pHint = iter.NextAttr(&pNode); pHint; pHint = iter.NextAttr(&pNode))
+ {
+ if (pHint->Which() == RES_TXTATR_FIELD)
+ {
+ // see also SwpHints::CalcHiddenParaField()
+ const SwFormatField& rField = pHint->GetFormatField();
+ int nCurWeight = pNode->GetDoc().FieldCanHideParaWeight(rField.GetField()->GetTyp()->Which());
+ if (nCurWeight > nNewResultWeight)
+ {
+ nNewResultWeight = nCurWeight;
+ bHiddenParaField = pNode->GetDoc().FieldHidesPara(*rField.GetField());
+ }
+ else if (nCurWeight == nNewResultWeight && bHiddenParaField)
+ {
+ // Currently, for both supported hiding types (HiddenPara, Database), "Don't hide"
+ // takes precedence - i.e., if there's a "Don't hide" field of that weight, we only
+ // care about fields of higher weight.
+ bHiddenParaField = pNode->GetDoc().FieldHidesPara(*rField.GetField());
+ }
+ }
+ }
+ }
+ else
+ {
+ bHiddenCharsHidePara = static_cast<SwTextNode const*>(SwFrame::GetDep())->HasHiddenCharAttribute( true );
+ bHiddenParaField = static_cast<SwTextNode const*>(SwFrame::GetDep())->IsHiddenByParaField();
+ }
+ const SwViewShell* pVsh = getRootFrame()->GetCurrShell();
+
+ if ( pVsh && ( bHiddenCharsHidePara || bHiddenParaField ) )
+ {
+
+ if (
+ ( bHiddenParaField &&
+ ( !pVsh->GetViewOptions()->IsShowHiddenPara() &&
+ !pVsh->GetViewOptions()->IsFieldName() ) ) ||
+ ( bHiddenCharsHidePara &&
+ !pVsh->GetViewOptions()->IsShowHiddenChar() ) )
+ {
+ // in order to put the cursor in the body text, one paragraph must
+ // be visible - check this for the 1st body paragraph
+ if (IsInDocBody() && FindPrevCnt() == nullptr)
+ {
+ bool isAllHidden(true);
+ for (SwContentFrame const* pNext = FindNextCnt(true);
+ pNext != nullptr; pNext = pNext->FindNextCnt(true))
+ {
+ if (!pNext->IsTextFrame()
+ || !static_cast<SwTextFrame const*>(pNext)->IsHiddenNow())
+ {
+ isAllHidden = false;
+ break;
+ }
+ }
+ if (isAllHidden)
+ {
+ SAL_INFO("sw.core", "unhiding one body paragraph");
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/// Removes Textfrm's attachments, when it's hidden
+void SwTextFrame::HideHidden()
+{
+ OSL_ENSURE( !GetFollow() && IsHiddenNow(),
+ "HideHidden on visible frame of hidden frame has follow" );
+
+ HideFootnotes(GetOffset(), TextFrameIndex(COMPLETE_STRING));
+ HideAndShowObjects();
+
+ // format information is obsolete
+ ClearPara();
+}
+
+void SwTextFrame::HideFootnotes(TextFrameIndex const nStart, TextFrameIndex const nEnd)
+{
+ SwPageFrame *pPage = nullptr;
+ sw::MergedAttrIter iter(*this);
+ SwTextNode const* pNode(nullptr);
+ for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode))
+ {
+ if (pHt->Which() == RES_TXTATR_FTN)
+ {
+ TextFrameIndex const nIdx(MapModelToView(pNode, pHt->GetStart()));
+ if (nEnd < nIdx)
+ break;
+ if (nStart <= nIdx)
+ {
+ if (!pPage)
+ pPage = FindPageFrame();
+ pPage->RemoveFootnote( this, static_cast<const SwTextFootnote*>(pHt) );
+ }
+ }
+ }
+}
+
+/**
+ * as-character anchored graphics, which are used for a graphic bullet list.
+ * As long as these graphic bullet list aren't imported, do not hide a
+ * at-character anchored object, if
+ * (a) the document is an imported WW8 document -
+ * checked by checking certain compatibility options -
+ * (b) the paragraph is the last content in the document and
+ * (c) the anchor character is an as-character anchored graphic.
+ */
+bool sw_HideObj( const SwTextFrame& _rFrame,
+ const RndStdIds _eAnchorType,
+ SwFormatAnchor const& rFormatAnchor,
+ SwAnchoredObject* _pAnchoredObj )
+{
+ bool bRet( true );
+
+ if (_eAnchorType == RndStdIds::FLY_AT_CHAR)
+ {
+ const IDocumentSettingAccess *const pIDSA = &_rFrame.GetDoc().getIDocumentSettingAccess();
+ if ( !pIDSA->get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) &&
+ !pIDSA->get(DocumentSettingId::OLD_LINE_SPACING) &&
+ !pIDSA->get(DocumentSettingId::USE_FORMER_OBJECT_POS) &&
+ pIDSA->get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) &&
+ _rFrame.IsInDocBody() && !_rFrame.FindNextCnt() )
+ {
+ SwTextNode const& rNode(*rFormatAnchor.GetAnchorNode()->GetTextNode());
+ assert(FrameContainsNode(_rFrame, rNode.GetIndex()));
+ sal_Int32 const nObjAnchorPos(rFormatAnchor.GetAnchorContentOffset());
+ const sal_Unicode cAnchorChar = nObjAnchorPos < rNode.Len()
+ ? rNode.GetText()[nObjAnchorPos]
+ : 0;
+ if (cAnchorChar == CH_TXTATR_BREAKWORD)
+ {
+ const SwTextAttr* const pHint(
+ rNode.GetTextAttrForCharAt(nObjAnchorPos, RES_TXTATR_FLYCNT));
+ if ( pHint )
+ {
+ const SwFrameFormat* pFrameFormat =
+ static_cast<const SwTextFlyCnt*>(pHint)->GetFlyCnt().GetFrameFormat();
+ if ( pFrameFormat->Which() == RES_FLYFRMFMT )
+ {
+ SwNodeIndex nContentIndex = *(pFrameFormat->GetContent().GetContentIdx());
+ ++nContentIndex;
+ if ( nContentIndex.GetNode().IsNoTextNode() )
+ {
+ bRet = false;
+ // set needed data structure values for object positioning
+ SwRectFnSet aRectFnSet(&_rFrame);
+ SwRect aLastCharRect( _rFrame.getFrameArea() );
+ aRectFnSet.SetWidth( aLastCharRect, 1 );
+ _pAnchoredObj->maLastCharRect = aLastCharRect;
+ _pAnchoredObj->mnLastTopOfLine = aRectFnSet.GetTop(aLastCharRect);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+/**
+ * Hide/show objects
+ *
+ * Method hides respectively shows objects, which are anchored at paragraph,
+ * at/as a character of the paragraph, corresponding to the paragraph and
+ * paragraph portion visibility.
+ *
+ * - is called from HideHidden() - should hide objects in hidden paragraphs and
+ * - from Format_() - should hide/show objects in partly visible paragraphs
+ */
+void SwTextFrame::HideAndShowObjects()
+{
+ if ( GetDrawObjs() )
+ {
+ if ( IsHiddenNow() )
+ {
+ // complete paragraph is hidden. Thus, hide all objects
+ for (SwAnchoredObject* i : *GetDrawObjs())
+ {
+ SdrObject* pObj = i->DrawObj();
+ SwContact* pContact = static_cast<SwContact*>(pObj->GetUserCall());
+ // under certain conditions
+ const RndStdIds eAnchorType( pContact->GetAnchorId() );
+ if ((eAnchorType != RndStdIds::FLY_AT_CHAR) ||
+ sw_HideObj(*this, eAnchorType, pContact->GetAnchorFormat(),
+ i ))
+ {
+ pContact->MoveObjToInvisibleLayer( pObj );
+ }
+ }
+ }
+ else
+ {
+ // paragraph is visible, but can contain hidden text portion.
+ // first we check if objects are allowed to be hidden:
+ const SwViewShell* pVsh = getRootFrame()->GetCurrShell();
+ const bool bShouldBeHidden = !pVsh || !pVsh->GetWin() ||
+ !pVsh->GetViewOptions()->IsShowHiddenChar();
+
+ // Thus, show all objects, which are anchored at paragraph and
+ // hide/show objects, which are anchored at/as character, according
+ // to the visibility of the anchor character.
+ for (SwAnchoredObject* i : *GetDrawObjs())
+ {
+ SdrObject* pObj = i->DrawObj();
+ SwContact* pContact = static_cast<SwContact*>(pObj->GetUserCall());
+ // Determine anchor type only once
+ const RndStdIds eAnchorType( pContact->GetAnchorId() );
+
+ if (eAnchorType == RndStdIds::FLY_AT_PARA)
+ {
+ pContact->MoveObjToVisibleLayer( pObj );
+ }
+ else if ((eAnchorType == RndStdIds::FLY_AT_CHAR) ||
+ (eAnchorType == RndStdIds::FLY_AS_CHAR))
+ {
+ sal_Int32 nHiddenStart;
+ sal_Int32 nHiddenEnd;
+ const SwFormatAnchor& rAnchorFormat = pContact->GetAnchorFormat();
+ SwScriptInfo::GetBoundsOfHiddenRange(
+ *rAnchorFormat.GetAnchorNode()->GetTextNode(),
+ rAnchorFormat.GetAnchorContentOffset(), nHiddenStart, nHiddenEnd);
+ // Under certain conditions
+ if ( nHiddenStart != COMPLETE_STRING && bShouldBeHidden &&
+ sw_HideObj(*this, eAnchorType, rAnchorFormat, i))
+ {
+ pContact->MoveObjToInvisibleLayer( pObj );
+ }
+ else
+ pContact->MoveObjToVisibleLayer( pObj );
+ }
+ else
+ {
+ OSL_FAIL( "<SwTextFrame::HideAndShowObjects()> - object not anchored at/inside paragraph!?" );
+ }
+ }
+ }
+ }
+
+ if (IsFollow())
+ {
+ SwTextFrame *pMaster = FindMaster();
+ OSL_ENSURE(pMaster, "SwTextFrame without master");
+ if (pMaster)
+ pMaster->HideAndShowObjects();
+ }
+}
+
+/**
+ * Returns the first possible break point in the current line.
+ * This method is used in SwTextFrame::Format() to decide whether the previous
+ * line has to be formatted as well.
+ * nFound is <= nEndLine.
+ */
+TextFrameIndex SwTextFrame::FindBrk(std::u16string_view aText,
+ const TextFrameIndex nStart,
+ const TextFrameIndex nEnd)
+{
+ sal_Int32 nFound = sal_Int32(nStart);
+ const sal_Int32 nEndLine = std::min(sal_Int32(nEnd), sal_Int32(aText.size()) - 1);
+
+ // Skip all leading blanks.
+ while( nFound <= nEndLine && ' ' == aText[nFound] )
+ {
+ nFound++;
+ }
+
+ // A tricky situation with the TextAttr-Dummy-character (in this case "$"):
+ // "Dr.$Meyer" at the beginning of the second line. Typing a blank after that
+ // doesn't result in the word moving into first line, even though that would work.
+ // For this reason we don't skip the dummy char.
+ while( nFound <= nEndLine && ' ' != aText[nFound] )
+ {
+ nFound++;
+ }
+
+ return TextFrameIndex(nFound);
+}
+
+bool SwTextFrame::IsIdxInside(TextFrameIndex const nPos, TextFrameIndex const nLen) const
+{
+ if (nPos == TextFrameIndex(COMPLETE_STRING)) // the "not found" range
+ return false;
+// Silence over-eager warning emitted at least by GCC trunk towards 6:
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstrict-overflow"
+#endif
+ if (nLen != TextFrameIndex(COMPLETE_STRING) && GetOffset() > nPos + nLen) // the range preceded us
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic pop
+#endif
+ return false;
+
+ if( !GetFollow() ) // the range doesn't precede us,
+ return true; // nobody follows us.
+
+ TextFrameIndex const nMax = GetFollow()->GetOffset();
+
+ // either the range overlap or our text has been deleted
+ // sw_redlinehide: GetText() should be okay here because it has already
+ // been updated in the INS/DEL hint case
+ if (nMax > nPos || nMax > TextFrameIndex(GetText().getLength()))
+ return true;
+
+ // changes made in the first line of a follow can modify the master
+ const SwParaPortion* pPara = GetFollow()->GetPara();
+ return pPara && ( nPos <= nMax + pPara->GetLen() );
+}
+
+inline void SwTextFrame::InvalidateRange(const SwCharRange &aRange, const tools::Long nD)
+{
+ if ( IsIdxInside( aRange.Start(), aRange.Len() ) )
+ InvalidateRange_( aRange, nD );
+}
+
+void SwTextFrame::InvalidateRange_( const SwCharRange &aRange, const tools::Long nD)
+{
+ if ( !HasPara() )
+ { InvalidateSize();
+ return;
+ }
+
+ SetWidow( false );
+ SwParaPortion *pPara = GetPara();
+
+ bool bInv = false;
+ if( 0 != nD )
+ {
+ // In nDelta the differences between old and new
+ // linelengths are being added, that's why it's negative
+ // if chars have been added and positive, if chars have
+ // deleted
+ pPara->SetDelta(pPara->GetDelta() + nD);
+ bInv = true;
+ }
+ SwCharRange &rReformat = pPara->GetReformat();
+ if(aRange != rReformat) {
+ if (TextFrameIndex(COMPLETE_STRING) == rReformat.Len())
+ rReformat = aRange;
+ else
+ rReformat += aRange;
+ bInv = true;
+ }
+ if(bInv)
+ {
+ InvalidateSize();
+ }
+}
+
+void SwTextFrame::CalcLineSpace()
+{
+ OSL_ENSURE( ! IsVertical() || ! IsSwapped(),
+ "SwTextFrame::CalcLineSpace with swapped frame!" );
+
+ if( IsLocked() || !HasPara() )
+ return;
+
+ if( GetDrawObjs() ||
+ GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent().IsAutoFirst())
+ {
+ Init();
+ return;
+ }
+
+ SwParaPortion *const pPara(GetPara());
+ assert(pPara);
+ if (pPara->IsFixLineHeight())
+ {
+ Init();
+ return;
+ }
+
+ Size aNewSize( getFramePrintArea().SSize() );
+
+ SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this );
+ SwTextFormatter aLine( this, &aInf );
+ if( aLine.GetDropLines() )
+ {
+ Init();
+ return;
+ }
+
+ aLine.Top();
+ aLine.RecalcRealHeight();
+
+ aNewSize.setHeight( (aLine.Y() - getFrameArea().Top()) + aLine.GetLineHeight() );
+
+ SwTwips nDelta = aNewSize.Height() - getFramePrintArea().Height();
+ // Underflow with free-flying frames
+ if( aInf.GetTextFly().IsOn() )
+ {
+ SwRect aTmpFrame( getFrameArea() );
+ if( nDelta < 0 )
+ aTmpFrame.Height( getFramePrintArea().Height() );
+ else
+ aTmpFrame.Height( aNewSize.Height() );
+ if( aInf.GetTextFly().Relax( aTmpFrame ) )
+ {
+ Init();
+ return;
+ }
+ }
+
+ if( !nDelta )
+ return;
+
+ SwTextFrameBreak aBreak( this );
+ if( GetFollow() || aBreak.IsBreakNow( aLine ) )
+ {
+ // if there is a Follow() or if we need to break here, reformat
+ Init();
+ }
+ else
+ {
+ // everything is business as usual...
+ pPara->SetPrepAdjust();
+ pPara->SetPrep();
+ }
+}
+
+static void lcl_SetWrong( SwTextFrame& rFrame, SwTextNode const& rNode,
+ sal_Int32 const nPos, sal_Int32 const nCnt, bool const bMove)
+{
+ if ( !rFrame.IsFollow() )
+ {
+ SwTextNode& rTextNode = const_cast<SwTextNode&>(rNode);
+ sw::GrammarContact* pGrammarContact = sw::getGrammarContactFor(rTextNode);
+ SwGrammarMarkUp* pWrongGrammar = pGrammarContact ?
+ pGrammarContact->getGrammarCheck( rTextNode, false ) :
+ rTextNode.GetGrammarCheck();
+ bool bGrammarProxy = pWrongGrammar != rTextNode.GetGrammarCheck();
+ if( bMove )
+ {
+ if( rTextNode.GetWrong() )
+ rTextNode.GetWrong()->Move( nPos, nCnt );
+ if( pWrongGrammar )
+ pWrongGrammar->MoveGrammar( nPos, nCnt );
+ if( bGrammarProxy && rTextNode.GetGrammarCheck() )
+ rTextNode.GetGrammarCheck()->MoveGrammar( nPos, nCnt );
+ if( rTextNode.GetSmartTags() )
+ rTextNode.GetSmartTags()->Move( nPos, nCnt );
+ }
+ else
+ {
+ if( rTextNode.GetWrong() )
+ rTextNode.GetWrong()->Invalidate( nPos, nCnt );
+ if( pWrongGrammar )
+ pWrongGrammar->Invalidate( nPos, nCnt );
+ if( rTextNode.GetSmartTags() )
+ rTextNode.GetSmartTags()->Invalidate( nPos, nCnt );
+ }
+ const sal_Int32 nEnd = nPos + (nCnt > 0 ? nCnt : 1 );
+ if ( !rTextNode.GetWrong() && !rTextNode.IsWrongDirty() )
+ {
+ rTextNode.SetWrong( std::make_unique<SwWrongList>( WRONGLIST_SPELL ) );
+ rTextNode.GetWrong()->SetInvalid( nPos, nEnd );
+ }
+ if ( !rTextNode.GetSmartTags() && !rTextNode.IsSmartTagDirty() )
+ {
+ rTextNode.SetSmartTags( std::make_unique<SwWrongList>( WRONGLIST_SMARTTAG ) );
+ rTextNode.GetSmartTags()->SetInvalid( nPos, nEnd );
+ }
+ rTextNode.SetWrongDirty(sw::WrongState::TODO);
+ rTextNode.SetGrammarCheckDirty( true );
+ rTextNode.SetWordCountDirty( true );
+ rTextNode.SetAutoCompleteWordDirty( true );
+ rTextNode.SetSmartTagDirty( true );
+ }
+
+ SwRootFrame *pRootFrame = rFrame.getRootFrame();
+ if (pRootFrame)
+ {
+ pRootFrame->SetNeedGrammarCheck( true );
+ }
+
+ SwPageFrame *pPage = rFrame.FindPageFrame();
+ if( pPage )
+ {
+ pPage->InvalidateSpelling();
+ pPage->InvalidateAutoCompleteWords();
+ pPage->InvalidateWordCount();
+ pPage->InvalidateSmartTags();
+ }
+}
+
+static void lcl_SetScriptInval(SwTextFrame& rFrame, TextFrameIndex const nPos)
+{
+ if( rFrame.GetPara() )
+ rFrame.GetPara()->GetScriptInfo().SetInvalidityA( nPos );
+}
+
+// note: SwClientNotify will be called once for every frame => just fix own Ofst
+static void lcl_ModifyOfst(SwTextFrame & rFrame,
+ TextFrameIndex const nPos, TextFrameIndex const nLen,
+ TextFrameIndex (* op)(TextFrameIndex const&, TextFrameIndex const&))
+{
+ assert(nLen != TextFrameIndex(COMPLETE_STRING));
+ if (rFrame.IsFollow() && nPos < rFrame.GetOffset())
+ {
+ rFrame.ManipOfst( std::max(nPos, op(rFrame.GetOffset(), nLen)) );
+ assert(sal_Int32(rFrame.GetOffset()) <= rFrame.GetText().getLength());
+ }
+}
+
+namespace {
+
+void UpdateMergedParaForMove(sw::MergedPara & rMerged,
+ SwTextFrame & rTextFrame,
+ bool & o_rbRecalcFootnoteFlag,
+ SwTextNode const& rDestNode,
+ SwTextNode const& rNode,
+ sal_Int32 const nDestStart,
+ sal_Int32 const nSourceStart,
+ sal_Int32 const nLen)
+{
+ std::vector<std::pair<sal_Int32, sal_Int32>> deleted;
+ sal_Int32 const nSourceEnd(nSourceStart + nLen);
+ sal_Int32 nLastEnd(0);
+ for (const auto& rExt : rMerged.extents)
+ {
+ if (rExt.pNode == &rNode)
+ {
+ sal_Int32 const nStart(std::max(nLastEnd, nSourceStart));
+ sal_Int32 const nEnd(std::min(rExt.nStart, nSourceEnd));
+ if (nStart < nEnd)
+ {
+ deleted.emplace_back(nStart, nEnd);
+ }
+ nLastEnd = rExt.nEnd;
+ if (nSourceEnd <= rExt.nEnd)
+ {
+ break;
+ }
+ }
+ else if (rNode.GetIndex() < rExt.pNode->GetIndex())
+ {
+ break;
+ }
+ }
+ if (nLastEnd != rNode.Len()) // without nLen, string yet to be removed
+ {
+ if (nLastEnd < nSourceEnd)
+ {
+ deleted.emplace_back(std::max(nLastEnd, nSourceStart), nSourceEnd);
+ }
+ }
+ if (deleted.empty())
+ return;
+
+ o_rbRecalcFootnoteFlag = true;
+ for (auto const& it : deleted)
+ {
+ sal_Int32 const nStart(it.first - nSourceStart + nDestStart);
+ TextFrameIndex const nDeleted = UpdateMergedParaForDelete(rMerged, false,
+ rDestNode, nStart, it.second - it.first);
+//FIXME asserts valid for join - but if called from split, the new node isn't there yet and it will be added later... assert(nDeleted);
+// assert(nDeleted == it.second - it.first);
+ if(nDeleted)
+ {
+ // InvalidateRange/lcl_SetScriptInval was called sufficiently for InsertText
+ lcl_SetWrong(rTextFrame, rDestNode, nStart, it.first - it.second, false);
+ TextFrameIndex const nIndex(sw::MapModelToView(rMerged, &rDestNode, nStart));
+ lcl_ModifyOfst(rTextFrame, nIndex, nDeleted, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>);
+ }
+ }
+}
+
+} // namespace
+
+/**
+ * Related: fdo#56031 filter out attribute changes that don't matter for
+ * humans/a11y to stop flooding the destination mortal with useless noise
+ */
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+static bool isA11yRelevantAttribute(sal_uInt16 nWhich)
+{
+ return nWhich != RES_CHRATR_RSID;
+}
+
+static bool hasA11yRelevantAttribute( const std::vector<sal_uInt16>& rWhichFmtAttr )
+{
+ for( sal_uInt16 nWhich : rWhichFmtAttr )
+ if ( isA11yRelevantAttribute( nWhich ) )
+ return true;
+
+ return false;
+}
+#endif // ENABLE_WASM_STRIP_ACCESSIBILITY
+
+// Note: for now this overrides SwClient::SwClientNotify; the intermediary
+// classes still override SwClient::Modify, which should continue to work
+// as their implementation of SwClientNotify is SwClient's which calls Modify.
+// Therefore we also don't need to call SwClient::SwClientNotify(rModify, rHint)
+// because that's all it does, and this implementation calls
+// SwContentFrame::SwClientNotify() when appropriate.
+void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint)
+{
+ SfxPoolItem const* pOld(nullptr);
+ SfxPoolItem const* pNew(nullptr);
+ sw::MoveText const* pMoveText(nullptr);
+ sw::InsertText const* pInsertText(nullptr);
+ sw::DeleteText const* pDeleteText(nullptr);
+ sw::DeleteChar const* pDeleteChar(nullptr);
+ sw::RedlineDelText const* pRedlineDelText(nullptr);
+ sw::RedlineUnDelText const* pRedlineUnDelText(nullptr);
+
+ sal_uInt16 nWhich = 0;
+ if (rHint.GetId() == SfxHintId::SwLegacyModify)
+ {
+ auto pHint = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ pOld = pHint->m_pOld;
+ pNew = pHint->m_pNew;
+ nWhich = pHint->GetWhich();
+ }
+ else if (rHint.GetId() == SfxHintId::SwInsertText)
+ {
+ pInsertText = static_cast<const sw::InsertText*>(&rHint);
+ }
+ else if (rHint.GetId() == SfxHintId::SwDeleteText)
+ {
+ pDeleteText = static_cast<const sw::DeleteText*>(&rHint);
+ }
+ else if (rHint.GetId() == SfxHintId::SwDeleteChar)
+ {
+ pDeleteChar = static_cast<const sw::DeleteChar*>(&rHint);
+ }
+ else if (rHint.GetId() == SfxHintId::SwDocPosUpdateAtIndex)
+ {
+ auto pDocPosAt = static_cast<const sw::DocPosUpdateAtIndex*>(&rHint);
+ Broadcast(SfxHint()); // notify SwAccessibleParagraph
+ if(IsLocked())
+ return;
+ if(pDocPosAt->m_nDocPos > getFrameArea().Top())
+ return;
+ TextFrameIndex const nIndex(MapModelToView(
+ &pDocPosAt->m_rNode,
+ pDocPosAt->m_nIndex));
+ InvalidateRange(SwCharRange(nIndex, TextFrameIndex(1)));
+ return;
+ }
+ else if (rHint.GetId() == SfxHintId::SwVirtPageNumHint)
+ {
+ auto& rVirtPageNumHint = const_cast<sw::VirtPageNumHint&>(static_cast<const sw::VirtPageNumHint&>(rHint));
+ if(!IsInDocBody() || IsFollow() || rVirtPageNumHint.IsFound())
+ return;
+ if(const SwPageFrame* pPage = FindPageFrame())
+ pPage->UpdateVirtPageNumInfo(rVirtPageNumHint, this);
+ return;
+ }
+ else if (auto const pHt = dynamic_cast<sw::MoveText const*>(&rHint))
+ {
+ pMoveText = pHt;
+ }
+ else if (auto const pHynt = dynamic_cast<sw::RedlineDelText const*>(&rHint))
+ {
+ pRedlineDelText = pHynt;
+ }
+ else if (auto const pHnt = dynamic_cast<sw::RedlineUnDelText const*>(&rHint))
+ {
+ pRedlineUnDelText = pHnt;
+ }
+ else
+ {
+ assert(!"unexpected hint");
+ }
+
+ if (m_pMergedPara)
+ {
+ assert(m_pMergedPara->listener.IsListeningTo(&rModify));
+ }
+
+ SwTextNode const& rNode(static_cast<SwTextNode const&>(rModify));
+
+ // modifications concerning frame attributes are processed by the base class
+ if( IsInRange( aFrameFormatSetRange, nWhich ) || RES_FMT_CHG == nWhich )
+ {
+ if (m_pMergedPara)
+ { // ignore item set changes that don't apply
+ SwTextNode const*const pAttrNode(
+ (nWhich == RES_PAGEDESC || nWhich == RES_BREAK)
+ ? m_pMergedPara->pFirstNode
+ : m_pMergedPara->pParaPropsNode);
+ if (pAttrNode != &rModify)
+ {
+ return;
+ }
+ }
+ SwContentFrame::SwClientNotify(rModify, sw::LegacyModifyHint(pOld, pNew));
+ if( nWhich == RES_FMT_CHG && getRootFrame()->GetCurrShell() )
+ {
+ // collection has changed
+ Prepare();
+ InvalidatePrt_();
+ lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false );
+ SetDerivedR2L( false );
+ CheckDirChange();
+ // Force complete paint due to existing indents.
+ SetCompletePaint();
+ InvalidateLineNum();
+ }
+ return;
+ }
+
+ if (m_pMergedPara && m_pMergedPara->pParaPropsNode != &rModify)
+ {
+ if (isPARATR(nWhich) || isPARATR_LIST(nWhich)) // FRMATR handled above
+ {
+ return; // ignore it
+ }
+ }
+
+ Broadcast(SfxHint()); // notify SwAccessibleParagraph
+
+ // while locked ignore all modifications
+ if( IsLocked() )
+ return;
+
+ // save stack
+ // warning: one has to ensure that all variables are set
+ TextFrameIndex nPos;
+ TextFrameIndex nLen;
+ bool bSetFieldsDirty = false;
+ bool bRecalcFootnoteFlag = false;
+
+ if (pRedlineDelText)
+ {
+ if (m_pMergedPara)
+ {
+ sal_Int32 const nNPos = pRedlineDelText->nStart;
+ sal_Int32 const nNLen = pRedlineDelText->nLen;
+ nPos = MapModelToView(&rNode, nNPos);
+ // update merged before doing anything else
+ nLen = UpdateMergedParaForDelete(*m_pMergedPara, false, rNode, nNPos, nNLen);
+ const sal_Int32 m = -nNLen;
+ if (nLen && IsIdxInside(nPos, nLen))
+ {
+ InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), m );
+ }
+ lcl_SetWrong( *this, rNode, nNPos, m, false );
+ if (nLen)
+ {
+ lcl_SetScriptInval( *this, nPos );
+ bSetFieldsDirty = bRecalcFootnoteFlag = true;
+ lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>);
+ }
+ }
+ }
+ else if (pRedlineUnDelText)
+ {
+ if (m_pMergedPara)
+ {
+ sal_Int32 const nNPos = pRedlineUnDelText->nStart;
+ sal_Int32 const nNLen = pRedlineUnDelText->nLen;
+ nPos = MapModelToView(&rNode, nNPos);
+ nLen = UpdateMergedParaForInsert(*m_pMergedPara, false, rNode, nNPos, nNLen);
+ if (IsIdxInside(nPos, nLen))
+ {
+ if (!nLen)
+ {
+ // Refresh NumPortions even when line is empty!
+ if (nPos)
+ InvalidateSize();
+ else
+ Prepare();
+ }
+ else
+ InvalidateRange_( SwCharRange( nPos, nLen ), nNLen );
+ }
+ lcl_SetWrong( *this, rNode, nNPos, nNLen, false );
+ lcl_SetScriptInval( *this, nPos );
+ bSetFieldsDirty = true;
+ lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator+<sal_Int32, Tag_TextFrameIndex>);
+ }
+ }
+ else if (pMoveText)
+ {
+ if (m_pMergedPara
+ && m_pMergedPara->pFirstNode->GetIndex() <= pMoveText->pDestNode->GetIndex()
+ && pMoveText->pDestNode->GetIndex() <= m_pMergedPara->pLastNode->GetIndex())
+ { // if it's not 2 nodes in merged frame, assume the target node doesn't have frames at all
+ assert(abs(rNode.GetIndex() - pMoveText->pDestNode->GetIndex()) == SwNodeOffset(1));
+ UpdateMergedParaForMove(*m_pMergedPara,
+ *this,
+ bRecalcFootnoteFlag,
+ *pMoveText->pDestNode, rNode,
+ pMoveText->nDestStart,
+ pMoveText->nSourceStart,
+ pMoveText->nLen);
+ }
+ else
+ {
+ // there is a situation where this is okay: from JoinNext, which will then call CheckResetRedlineMergeFlag, which will then create merged from scratch for this frame
+ // assert(!m_pMergedPara || !getRootFrame()->IsHideRedlines() || !pMoveText->pDestNode->getLayoutFrame(getRootFrame()));
+ }
+ }
+ else if (pInsertText)
+ {
+ nPos = MapModelToView(&rNode, pInsertText->nPos);
+ // unlike redlines, inserting into fieldmark must be explicitly handled
+ bool isHidden(false);
+ switch (getRootFrame()->GetFieldmarkMode())
+ {
+ case sw::FieldmarkMode::ShowCommand:
+ isHidden = pInsertText->isInsideFieldmarkResult;
+ break;
+ case sw::FieldmarkMode::ShowResult:
+ isHidden = pInsertText->isInsideFieldmarkCommand;
+ break;
+ case sw::FieldmarkMode::ShowBoth: // just to avoid the warning
+ break;
+ }
+ if (!isHidden)
+ {
+ nLen = TextFrameIndex(pInsertText->nLen);
+ if (m_pMergedPara)
+ {
+ UpdateMergedParaForInsert(*m_pMergedPara, true, rNode, pInsertText->nPos, pInsertText->nLen);
+ }
+ if( IsIdxInside( nPos, nLen ) )
+ {
+ if( !nLen )
+ {
+ // Refresh NumPortions even when line is empty!
+ if( nPos )
+ InvalidateSize();
+ else
+ Prepare();
+ }
+ else
+ InvalidateRange_( SwCharRange( nPos, nLen ), pInsertText->nLen );
+ }
+ lcl_SetScriptInval( *this, nPos );
+ bSetFieldsDirty = true;
+ lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator+<sal_Int32, Tag_TextFrameIndex>);
+ }
+ lcl_SetWrong( *this, rNode, pInsertText->nPos, pInsertText->nLen, true );
+ }
+ else if (pDeleteText)
+ {
+ nPos = MapModelToView(&rNode, pDeleteText->nStart);
+ if (m_pMergedPara)
+ { // update merged before doing anything else
+ nLen = UpdateMergedParaForDelete(*m_pMergedPara, true, rNode, pDeleteText->nStart, pDeleteText->nLen);
+ }
+ else
+ {
+ nLen = TextFrameIndex(pDeleteText->nLen);
+ }
+ const sal_Int32 m = -pDeleteText->nLen;
+ if ((!m_pMergedPara || nLen) && IsIdxInside(nPos, nLen))
+ {
+ if( !nLen )
+ InvalidateSize();
+ else
+ InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), m );
+ }
+ lcl_SetWrong( *this, rNode, pDeleteText->nStart, m, true );
+ if (nLen)
+ {
+ lcl_SetScriptInval( *this, nPos );
+ bSetFieldsDirty = bRecalcFootnoteFlag = true;
+ lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>);
+ }
+ }
+ else if (pDeleteChar)
+ {
+ nPos = MapModelToView(&rNode, pDeleteChar->m_nPos);
+ if (m_pMergedPara)
+ {
+ nLen = UpdateMergedParaForDelete(*m_pMergedPara, true, rNode, pDeleteChar->m_nPos, 1);
+ }
+ else
+ {
+ nLen = TextFrameIndex(1);
+ }
+ lcl_SetWrong( *this, rNode, pDeleteChar->m_nPos, -1, true );
+ if (nLen)
+ {
+ InvalidateRange( SwCharRange(nPos, nLen), -1 );
+ lcl_SetScriptInval( *this, nPos );
+ bSetFieldsDirty = bRecalcFootnoteFlag = true;
+ lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>);
+ }
+ }
+ else switch (nWhich)
+ {
+ case RES_LINENUMBER:
+ {
+ assert(false); // should have been forwarded to SwContentFrame
+ InvalidateLineNum();
+ }
+ break;
+ case RES_UPDATE_ATTR:
+ {
+ const SwUpdateAttr* pNewUpdate = static_cast<const SwUpdateAttr*>(pNew);
+
+ sal_Int32 const nNPos = pNewUpdate->getStart();
+ sal_Int32 const nNLen = pNewUpdate->getEnd() - nNPos;
+ nPos = MapModelToView(&rNode, nNPos);
+ nLen = MapModelToView(&rNode, nNPos + nNLen) - nPos;
+ if( IsIdxInside( nPos, nLen ) )
+ {
+ // We need to reformat anyways, even if the invalidated
+ // range is empty.
+ // E.g.: empty line, set 14 pt!
+
+ // FootnoteNumbers need to be formatted
+ if( !nLen )
+ nLen = TextFrameIndex(1);
+
+ InvalidateRange_( SwCharRange( nPos, nLen) );
+ const sal_uInt16 nTmp = pNewUpdate->getWhichAttr();
+
+ if( ! nTmp || RES_TXTATR_CHARFMT == nTmp || RES_TXTATR_INETFMT == nTmp || RES_TXTATR_AUTOFMT == nTmp ||
+ RES_FMT_CHG == nTmp || RES_ATTRSET_CHG == nTmp )
+ {
+ lcl_SetWrong( *this, rNode, nNPos, nNPos + nNLen, false );
+ lcl_SetScriptInval( *this, nPos );
+ }
+ }
+
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+ if( isA11yRelevantAttribute( pNewUpdate->getWhichAttr() ) &&
+ hasA11yRelevantAttribute( pNewUpdate->getFmtAttrs() ) )
+ {
+ SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr;
+ if ( pViewSh )
+ {
+ pViewSh->InvalidateAccessibleParaAttrs( *this );
+ }
+ }
+#endif
+ }
+ break;
+ case RES_OBJECTDYING:
+ break;
+
+ case RES_PARATR_LINESPACING:
+ {
+ CalcLineSpace();
+ InvalidateSize();
+ InvalidatePrt_();
+ if( IsInSct() && !GetPrev() )
+ {
+ SwSectionFrame *pSect = FindSctFrame();
+ if( pSect->ContainsAny() == this )
+ pSect->InvalidatePrt();
+ }
+
+ // i#11859
+ // (1) Also invalidate next frame on next page/column.
+ // (2) Skip empty sections and hidden paragraphs
+ // Thus, use method <InvalidateNextPrtArea()>
+ InvalidateNextPrtArea();
+
+ SetCompletePaint();
+ }
+ break;
+
+ case RES_TXTATR_FIELD:
+ case RES_TXTATR_ANNOTATION:
+ {
+ sal_Int32 const nNPos = static_cast<const SwFormatField*>(pNew)->GetTextField()->GetStart();
+ nPos = MapModelToView(&rNode, nNPos);
+ if (IsIdxInside(nPos, TextFrameIndex(1)))
+ {
+ if (SfxPoolItem::areSame( pNew, pOld ))
+ {
+ // only repaint
+ // opt: invalidate window?
+ InvalidatePage();
+ SetCompletePaint();
+ }
+ else
+ InvalidateRange_(SwCharRange(nPos, TextFrameIndex(1)));
+ }
+ bSetFieldsDirty = true;
+ // ST2
+ if ( SwSmartTagMgr::Get().IsSmartTagsEnabled() )
+ lcl_SetWrong( *this, rNode, nNPos, nNPos + 1, false );
+ }
+ break;
+
+ case RES_TXTATR_FTN :
+ {
+ if (!IsInFootnote())
+ { // the hint may be sent from the anchor node, or from a
+ // node in the footnote; the anchor index is only valid in the
+ // anchor node!
+ assert(rNode == static_cast<const SwFormatFootnote*>(pNew)->GetTextFootnote()->GetTextNode());
+ nPos = MapModelToView(&rNode,
+ static_cast<const SwFormatFootnote*>(pNew)->GetTextFootnote()->GetStart());
+ }
+#ifdef _MSC_VER
+ else nPos = TextFrameIndex(42); // shut up MSVC 2017 spurious warning C4701
+#endif
+ if (IsInFootnote() || IsIdxInside(nPos, TextFrameIndex(1)))
+ Prepare( PrepareHint::FootnoteInvalidation, static_cast<const SwFormatFootnote*>(pNew)->GetTextFootnote() );
+ break;
+ }
+
+ case RES_ATTRSET_CHG:
+ {
+ InvalidateLineNum();
+
+ const SwAttrSet& rNewSet = *static_cast<const SwAttrSetChg*>(pNew)->GetChgSet();
+ int nClear = 0;
+ sal_uInt16 nCount = rNewSet.Count();
+
+ if( const SwFormatFootnote* pItem = rNewSet.GetItemIfSet( RES_TXTATR_FTN, false ) )
+ {
+ nPos = MapModelToView(&rNode, pItem->GetTextFootnote()->GetStart());
+ if (IsIdxInside(nPos, TextFrameIndex(1)))
+ Prepare( PrepareHint::FootnoteInvalidation, pNew );
+ nClear = 0x01;
+ --nCount;
+ }
+
+ if( const SwFormatField* pItem = rNewSet.GetItemIfSet( RES_TXTATR_FIELD, false ) )
+ {
+ nPos = MapModelToView(&rNode, pItem->GetTextField()->GetStart());
+ if (IsIdxInside(nPos, TextFrameIndex(1)))
+ {
+ const SfxPoolItem* pOldItem = pOld ?
+ &(static_cast<const SwAttrSetChg*>(pOld)->GetChgSet()->Get(RES_TXTATR_FIELD)) : nullptr;
+ if (SfxPoolItem::areSame( pItem, pOldItem ))
+ {
+ InvalidatePage();
+ SetCompletePaint();
+ }
+ else
+ InvalidateRange_(SwCharRange(nPos, TextFrameIndex(1)));
+ }
+ nClear |= 0x02;
+ --nCount;
+ }
+ bool bLineSpace = SfxItemState::SET == rNewSet.GetItemState(
+ RES_PARATR_LINESPACING, false ),
+ bRegister = SfxItemState::SET == rNewSet.GetItemState(
+ RES_PARATR_REGISTER, false );
+ if ( bLineSpace || bRegister )
+ {
+ if (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)
+ {
+ Prepare( bRegister ? PrepareHint::Register : PrepareHint::AdjustSizeWithoutFormatting );
+ CalcLineSpace();
+ InvalidateSize();
+ InvalidatePrt_();
+
+ // i#11859
+ // (1) Also invalidate next frame on next page/column.
+ // (2) Skip empty sections and hidden paragraphs
+ // Thus, use method <InvalidateNextPrtArea()>
+ InvalidateNextPrtArea();
+
+ SetCompletePaint();
+ }
+ nClear |= 0x04;
+ if ( bLineSpace )
+ {
+ --nCount;
+ if ((!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)
+ && IsInSct() && !GetPrev())
+ {
+ SwSectionFrame *pSect = FindSctFrame();
+ if( pSect->ContainsAny() == this )
+ pSect->InvalidatePrt();
+ }
+ }
+ if ( bRegister )
+ --nCount;
+ }
+ if ( SfxItemState::SET == rNewSet.GetItemState( RES_PARATR_SPLIT,
+ false ))
+ {
+ if (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)
+ {
+ if (GetPrev())
+ CheckKeep();
+ Prepare();
+ InvalidateSize();
+ }
+ nClear |= 0x08;
+ --nCount;
+ }
+
+ if( SfxItemState::SET == rNewSet.GetItemState( RES_BACKGROUND, false)
+ && (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)
+ && !IsFollow() && GetDrawObjs() )
+ {
+ SwSortedObjs *pObjs = GetDrawObjs();
+ for ( size_t i = 0; GetDrawObjs() && i < pObjs->size(); ++i )
+ {
+ SwAnchoredObject* pAnchoredObj = (*pObjs)[i];
+ if ( auto pFly = pAnchoredObj->DynCastFlyFrame() )
+ {
+ if( !pFly->IsFlyInContentFrame() )
+ {
+ const SvxBrushItem &rBack =
+ pFly->GetAttrSet()->GetBackground();
+ // #GetTransChg#
+ // following condition determines, if the fly frame
+ // "inherites" the background color of text frame.
+ // This is the case, if fly frame background
+ // color is "no fill"/"auto fill" and if the fly frame
+ // has no background graphic.
+ // Thus, check complete fly frame background
+ // color and *not* only its transparency value
+ if ( (rBack.GetColor() == COL_TRANSPARENT) &&
+ rBack.GetGraphicPos() == GPOS_NONE )
+ {
+ pFly->SetCompletePaint();
+ pFly->InvalidatePage();
+ }
+ }
+ }
+ }
+ }
+
+ if ( SfxItemState::SET ==
+ rNewSet.GetItemState( RES_TXTATR_CHARFMT, false ) )
+ {
+ lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false );
+ lcl_SetScriptInval( *this, TextFrameIndex(0) );
+ }
+ else if ( SfxItemState::SET ==
+ rNewSet.GetItemState( RES_CHRATR_LANGUAGE, false ) ||
+ SfxItemState::SET ==
+ rNewSet.GetItemState( RES_CHRATR_CJK_LANGUAGE, false ) ||
+ SfxItemState::SET ==
+ rNewSet.GetItemState( RES_CHRATR_CTL_LANGUAGE, false ) )
+ lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false );
+ else if ( SfxItemState::SET ==
+ rNewSet.GetItemState( RES_CHRATR_FONT, false ) ||
+ SfxItemState::SET ==
+ rNewSet.GetItemState( RES_CHRATR_CJK_FONT, false ) ||
+ SfxItemState::SET ==
+ rNewSet.GetItemState( RES_CHRATR_CTL_FONT, false ) )
+ lcl_SetScriptInval( *this, TextFrameIndex(0) );
+ else if ( SfxItemState::SET ==
+ rNewSet.GetItemState( RES_FRAMEDIR, false )
+ && (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify))
+ {
+ SetDerivedR2L( false );
+ CheckDirChange();
+ // Force complete paint due to existing indents.
+ SetCompletePaint();
+ }
+
+ if( nCount )
+ {
+ if( getRootFrame()->GetCurrShell() )
+ {
+ Prepare();
+ InvalidatePrt_();
+ }
+
+ if (nClear || (m_pMergedPara &&
+ (m_pMergedPara->pParaPropsNode != &rModify ||
+ m_pMergedPara->pFirstNode != &rModify)))
+ {
+ assert(pOld);
+ SwAttrSetChg aOldSet( *static_cast<const SwAttrSetChg*>(pOld) );
+ SwAttrSetChg aNewSet( *static_cast<const SwAttrSetChg*>(pNew) );
+
+ if (m_pMergedPara && m_pMergedPara->pParaPropsNode != &rModify)
+ {
+ for (sal_uInt16 i = RES_PARATR_BEGIN; i != RES_FRMATR_END; ++i)
+ {
+ if (i != RES_BREAK && i != RES_PAGEDESC)
+ {
+ aOldSet.ClearItem(i);
+ aNewSet.ClearItem(i);
+ }
+ }
+ for (sal_uInt16 i = XATTR_FILL_FIRST; i <= XATTR_FILL_LAST; ++i)
+ {
+ aOldSet.ClearItem(i);
+ aNewSet.ClearItem(i);
+ }
+ }
+ if (m_pMergedPara && m_pMergedPara->pFirstNode != &rModify)
+ {
+ aOldSet.ClearItem(RES_BREAK);
+ aNewSet.ClearItem(RES_BREAK);
+ aOldSet.ClearItem(RES_PAGEDESC);
+ aNewSet.ClearItem(RES_PAGEDESC);
+ }
+
+ if( 0x01 & nClear )
+ {
+ aOldSet.ClearItem( RES_TXTATR_FTN );
+ aNewSet.ClearItem( RES_TXTATR_FTN );
+ }
+ if( 0x02 & nClear )
+ {
+ aOldSet.ClearItem( RES_TXTATR_FIELD );
+ aNewSet.ClearItem( RES_TXTATR_FIELD );
+ }
+ if ( 0x04 & nClear )
+ {
+ if ( bLineSpace )
+ {
+ aOldSet.ClearItem( RES_PARATR_LINESPACING );
+ aNewSet.ClearItem( RES_PARATR_LINESPACING );
+ }
+ if ( bRegister )
+ {
+ aOldSet.ClearItem( RES_PARATR_REGISTER );
+ aNewSet.ClearItem( RES_PARATR_REGISTER );
+ }
+ }
+ if ( 0x08 & nClear )
+ {
+ aOldSet.ClearItem( RES_PARATR_SPLIT );
+ aNewSet.ClearItem( RES_PARATR_SPLIT );
+ }
+ if (aOldSet.Count() || aNewSet.Count())
+ {
+ SwContentFrame::SwClientNotify(rModify, sw::LegacyModifyHint(&aOldSet, &aNewSet));
+ }
+ }
+ else
+ SwContentFrame::SwClientNotify(rModify, sw::LegacyModifyHint(pOld, pNew));
+ }
+
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+ if (isA11yRelevantAttribute(nWhich))
+ {
+ SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr;
+ if ( pViewSh )
+ {
+ pViewSh->InvalidateAccessibleParaAttrs( *this );
+ }
+ }
+#endif
+ }
+ break;
+ case RES_PARATR_SPLIT:
+ if ( GetPrev() )
+ CheckKeep();
+ Prepare();
+ bSetFieldsDirty = true;
+ break;
+ case RES_FRAMEDIR :
+ assert(false); // should have been forwarded to SwContentFrame
+ SetDerivedR2L( false );
+ CheckDirChange();
+ break;
+ default:
+ {
+ Prepare();
+ InvalidatePrt_();
+ if ( !nWhich )
+ {
+ // is called by e. g. HiddenPara with 0
+ SwFrame *pNxt = FindNext();
+ if ( nullptr != pNxt )
+ pNxt->InvalidatePrt();
+ }
+ }
+ } // switch
+
+ if( bSetFieldsDirty )
+ GetDoc().getIDocumentFieldsAccess().SetFieldsDirty( true, &rNode, SwNodeOffset(1) );
+
+ if ( bRecalcFootnoteFlag )
+ CalcFootnoteFlag();
+}
+
+void SwTextFrame::PrepWidows( const sal_uInt16 nNeed, bool bNotify )
+{
+ OSL_ENSURE(GetFollow() && nNeed, "+SwTextFrame::Prepare: lost all friends");
+
+ SwParaPortion *pPara = GetPara();
+ if ( !pPara )
+ return;
+ pPara->SetPrepWidows();
+
+ sal_uInt16 nHave = nNeed;
+
+ // We yield a few lines and shrink in CalcPreps()
+ SwSwapIfNotSwapped swap( this );
+
+ SwTextSizeInfo aInf( this );
+ SwTextMargin aLine( this, &aInf );
+ aLine.Bottom();
+ TextFrameIndex nTmpLen = aLine.GetCurr()->GetLen();
+ while( nHave && aLine.PrevLine() )
+ {
+ if( nTmpLen )
+ --nHave;
+ nTmpLen = aLine.GetCurr()->GetLen();
+ }
+
+ // If it's certain that we can yield lines, the Master needs
+ // to check the widow rule
+ if( !nHave )
+ {
+ bool bSplit = true;
+ if( !IsFollow() ) // only a master decides about orphans
+ {
+ const WidowsAndOrphans aWidOrp( this );
+ bSplit = ( aLine.GetLineNr() >= aWidOrp.GetOrphansLines() &&
+ aLine.GetLineNr() >= aLine.GetDropLines() );
+ }
+
+ if( bSplit )
+ {
+ GetFollow()->SetOffset( aLine.GetEnd() );
+ aLine.TruncLines( true );
+ if( pPara->IsFollowField() )
+ GetFollow()->SetFieldFollow( true );
+ }
+ }
+ if ( bNotify )
+ {
+ InvalidateSize_();
+ InvalidatePage();
+ }
+}
+
+static bool lcl_ErgoVadis(SwTextFrame* pFrame, TextFrameIndex & rPos, const PrepareHint ePrep)
+{
+ const SwFootnoteInfo &rFootnoteInfo = pFrame->GetDoc().GetFootnoteInfo();
+ if( ePrep == PrepareHint::ErgoSum )
+ {
+ if( rFootnoteInfo.m_aErgoSum.isEmpty() )
+ return false;
+ rPos = pFrame->GetOffset();
+ }
+ else
+ {
+ if( rFootnoteInfo.m_aQuoVadis.isEmpty() )
+ return false;
+ if( pFrame->HasFollow() )
+ rPos = pFrame->GetFollow()->GetOffset();
+ else
+ rPos = TextFrameIndex(pFrame->GetText().getLength());
+ if( rPos )
+ --rPos; // our last character
+ }
+ return true;
+}
+
+// Silence over-eager warning emitted at least by GCC 5.3.1
+#if defined __GNUC__ && !defined __clang__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wstrict-overflow"
+#endif
+bool SwTextFrame::Prepare( const PrepareHint ePrep, const void* pVoid,
+ bool bNotify )
+{
+ bool bParaPossiblyInvalid = false;
+
+ SwFrameSwapper aSwapper( this, false );
+
+ if ( IsEmpty() )
+ {
+ switch ( ePrep )
+ {
+ case PrepareHint::BossChanged:
+ SetInvalidVert( true ); // Test
+ [[fallthrough]];
+ case PrepareHint::WidowsOrphans:
+ case PrepareHint::Widows:
+ case PrepareHint::FootnoteInvalidationGone : return bParaPossiblyInvalid;
+
+ case PrepareHint::FramePositionChanged :
+ {
+ // We also need an InvalidateSize for Areas (with and without columns),
+ // so that we format and bUndersized is set (if needed)
+ if( IsInFly() || IsInSct() )
+ {
+ SwTwips nTmpBottom = GetUpper()->getFrameArea().Top() +
+ GetUpper()->getFramePrintArea().Bottom();
+ if( nTmpBottom < getFrameArea().Bottom() )
+ break;
+ }
+ // Are there any free-flying frames on this page?
+ SwTextFly aTextFly( this );
+ if( aTextFly.IsOn() )
+ {
+ // Does any free-flying frame overlap?
+ if ( aTextFly.Relax() || IsUndersized() )
+ break;
+ }
+ if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue())
+ break;
+
+ SwTextGridItem const*const pGrid(GetGridItem(FindPageFrame()));
+ if (pGrid && GetTextNodeForParaProps()->GetSwAttrSet().GetParaGrid().GetValue())
+ break;
+
+ // i#28701 - consider anchored objects
+ if ( GetDrawObjs() )
+ break;
+
+ return bParaPossiblyInvalid;
+ }
+ default:
+ break;
+ }
+ }
+
+ // Split fly anchors are technically empty (have no SwParaPortion), but otherwise behave like
+ // other split text frames, which are non-empty.
+ bool bSplitFlyAnchor = GetOffset() == TextFrameIndex(0) && HasFollow()
+ && GetFollow()->GetOffset() == TextFrameIndex(0);
+
+ if( !HasPara() && !bSplitFlyAnchor && PrepareHint::MustFit != ePrep )
+ {
+ SetInvalidVert( true ); // Test
+ OSL_ENSURE( !IsLocked(), "SwTextFrame::Prepare: three of a perfect pair" );
+ if ( bNotify )
+ InvalidateSize();
+ else
+ InvalidateSize_();
+ return bParaPossiblyInvalid;
+ }
+
+ // Get object from cache while locking
+ SwTextLineAccess aAccess( this );
+ SwParaPortion *pPara = aAccess.GetPara();
+
+ switch( ePrep )
+ {
+ case PrepareHint::FootnoteMove :
+ {
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aFrm.Height(0);
+ }
+
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+ aPrt.Height(0);
+ }
+
+ InvalidatePrt_();
+ InvalidateSize_();
+ [[fallthrough]];
+ case PrepareHint::AdjustSizeWithoutFormatting :
+ pPara->SetPrepAdjust();
+ if( IsFootnoteNumFrame() != pPara->IsFootnoteNum() ||
+ IsUndersized() )
+ {
+ InvalidateRange(SwCharRange(TextFrameIndex(0), TextFrameIndex(1)), 1);
+ if( GetOffset() && !IsFollow() )
+ SetOffset_(TextFrameIndex(0));
+ }
+ break;
+ case PrepareHint::MustFit :
+ pPara->SetPrepMustFit(true);
+ [[fallthrough]];
+ case PrepareHint::WidowsOrphans :
+ pPara->SetPrepAdjust();
+ break;
+ case PrepareHint::Widows :
+ // MustFit is stronger than anything else
+ if( pPara->IsPrepMustFit() )
+ return bParaPossiblyInvalid;
+ // see comment in WidowsAndOrphans::FindOrphans and CalcPreps()
+ PrepWidows( *static_cast<const sal_uInt16 *>(pVoid), bNotify );
+ break;
+
+ case PrepareHint::FootnoteInvalidation :
+ {
+ SwTextFootnote const *pFootnote = static_cast<SwTextFootnote const *>(pVoid);
+ if( IsInFootnote() )
+ {
+ // Am I the first TextFrame of a footnote?
+ if( !GetPrev() )
+ // So we're a TextFrame of the footnote, which has
+ // to display the footnote number or the ErgoSum text
+ InvalidateRange(SwCharRange(TextFrameIndex(0), TextFrameIndex(1)), 1);
+
+ if( !GetNext() )
+ {
+ // We're the last Footnote; we need to update the
+ // QuoVadis texts now
+ const SwFootnoteInfo &rFootnoteInfo = GetDoc().GetFootnoteInfo();
+ if( !pPara->UpdateQuoVadis( rFootnoteInfo.m_aQuoVadis ) )
+ {
+ TextFrameIndex nPos = pPara->GetParLen();
+ if( nPos )
+ --nPos;
+ InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), 1);
+ }
+ }
+ }
+ else
+ {
+ // We are the TextFrame _with_ the footnote
+ TextFrameIndex const nPos = MapModelToView(
+ &pFootnote->GetTextNode(), pFootnote->GetStart());
+ InvalidateRange(SwCharRange(nPos, TextFrameIndex(1)), 1);
+ }
+ break;
+ }
+ case PrepareHint::BossChanged :
+ {
+ // Test
+ {
+ SetInvalidVert( false );
+ bool bOld = IsVertical();
+ SetInvalidVert( true );
+ if( bOld != IsVertical() )
+ InvalidateRange(SwCharRange(GetOffset(), TextFrameIndex(COMPLETE_STRING)));
+ }
+
+ if( HasFollow() )
+ {
+ TextFrameIndex nNxtOfst = GetFollow()->GetOffset();
+ if( nNxtOfst )
+ --nNxtOfst;
+ InvalidateRange(SwCharRange( nNxtOfst, TextFrameIndex(1)), 1);
+ }
+ if( IsInFootnote() )
+ {
+ TextFrameIndex nPos;
+ if( lcl_ErgoVadis( this, nPos, PrepareHint::QuoVadis ) )
+ InvalidateRange( SwCharRange( nPos, TextFrameIndex(1)) );
+ if( lcl_ErgoVadis( this, nPos, PrepareHint::ErgoSum ) )
+ InvalidateRange( SwCharRange( nPos, TextFrameIndex(1)) );
+ }
+ // If we have a page number field, we must invalidate those spots
+ SwTextNode const* pNode(nullptr);
+ sw::MergedAttrIter iter(*this);
+ TextFrameIndex const nEnd = GetFollow()
+ ? GetFollow()->GetOffset() : TextFrameIndex(COMPLETE_STRING);
+ for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode))
+ {
+ TextFrameIndex const nStart(MapModelToView(pNode, pHt->GetStart()));
+ if (nStart >= GetOffset())
+ {
+ if (nStart >= nEnd)
+ break;
+
+ // If we're flowing back and own a Footnote, the Footnote also flows
+ // with us. So that it doesn't obstruct us, we send ourselves
+ // an ADJUST_FRM.
+ // pVoid != 0 means MoveBwd()
+ const sal_uInt16 nWhich = pHt->Which();
+ if (RES_TXTATR_FIELD == nWhich ||
+ (HasFootnote() && pVoid && RES_TXTATR_FTN == nWhich))
+ InvalidateRange(SwCharRange(nStart, TextFrameIndex(1)), 1);
+ }
+ }
+ // A new boss, a new chance for growing
+ if( IsUndersized() )
+ {
+ InvalidateSize_();
+ InvalidateRange(SwCharRange(GetOffset(), TextFrameIndex(1)), 1);
+ }
+ break;
+ }
+
+ case PrepareHint::FramePositionChanged :
+ {
+ if ( isFramePrintAreaValid() )
+ {
+ SwTextGridItem const*const pGrid(GetGridItem(FindPageFrame()));
+ if (pGrid && GetTextNodeForParaProps()->GetSwAttrSet().GetParaGrid().GetValue())
+ InvalidatePrt();
+ }
+
+ // If we don't overlap with anybody:
+ // did any free-flying frame overlapped _before_ the position change?
+ bool bFormat = pPara->HasFly();
+ if( !bFormat )
+ {
+ if( IsInFly() )
+ {
+ SwTwips nTmpBottom = GetUpper()->getFrameArea().Top() +
+ GetUpper()->getFramePrintArea().Bottom();
+ if( nTmpBottom < getFrameArea().Bottom() )
+ bFormat = true;
+ }
+ if( !bFormat )
+ {
+ if ( GetDrawObjs() )
+ {
+ const size_t nCnt = GetDrawObjs()->size();
+ for ( size_t i = 0; i < nCnt; ++i )
+ {
+ SwAnchoredObject* pAnchoredObj = (*GetDrawObjs())[i];
+ // i#28701 - consider all
+ // to-character anchored objects
+ if ( pAnchoredObj->GetFrameFormat().GetAnchor().GetAnchorId()
+ == RndStdIds::FLY_AT_CHAR )
+ {
+ bFormat = true;
+ break;
+ }
+ }
+ }
+ if( !bFormat )
+ {
+ // Are there any free-flying frames on this page?
+ SwTextFly aTextFly( this );
+ if( aTextFly.IsOn() )
+ {
+ // Does any free-flying frame overlap?
+ const bool bRelaxed = aTextFly.Relax();
+ bFormat = bRelaxed || IsUndersized();
+ if (bRelaxed)
+ {
+ // It's possible that pPara was deleted above; retrieve it again
+ pPara = aAccess.GetPara();
+ }
+ }
+ }
+ }
+ }
+
+ if( bFormat )
+ {
+ if( !IsLocked() )
+ {
+ if( pPara->GetRepaint().HasArea() )
+ SetCompletePaint();
+ Init();
+ pPara = nullptr;
+ InvalidateSize_();
+ }
+ }
+ else
+ {
+ if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue())
+ bParaPossiblyInvalid = Prepare( PrepareHint::Register, nullptr, bNotify );
+ // The Frames need to be readjusted, which caused by changes
+ // in position
+ else if( HasFootnote() )
+ {
+ bParaPossiblyInvalid = Prepare( PrepareHint::AdjustSizeWithoutFormatting, nullptr, bNotify );
+ InvalidateSize_();
+ }
+ else
+ return bParaPossiblyInvalid; // So that there's no SetPrep()
+
+ if (bParaPossiblyInvalid)
+ {
+ // It's possible that pPara was deleted above; retrieve it again
+ pPara = aAccess.GetPara();
+ }
+
+ }
+ break;
+ }
+ case PrepareHint::Register:
+ if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue())
+ {
+ pPara->SetPrepAdjust();
+ CalcLineSpace();
+
+ // It's possible that pPara was deleted above; retrieve it again
+ bParaPossiblyInvalid = true;
+ pPara = aAccess.GetPara();
+
+ InvalidateSize();
+ InvalidatePrt_();
+ SwFrame* pNxt = GetIndNext();
+ if ( nullptr != pNxt )
+ {
+ pNxt->InvalidatePrt_();
+ if ( pNxt->IsLayoutFrame() )
+ pNxt->InvalidatePage();
+ }
+ SetCompletePaint();
+ }
+ break;
+ case PrepareHint::FootnoteInvalidationGone :
+ {
+ // If a Follow is calling us, because a footnote is being deleted, our last
+ // line has to be formatted, so that the first line of the Follow can flow up.
+ // Which had flowed to the next page to be together with the footnote (this is
+ // especially true for areas with columns)
+ OSL_ENSURE( GetFollow(), "PrepareHint::FootnoteInvalidationGone may only be called by Follow" );
+ TextFrameIndex nPos = GetFollow()->GetOffset();
+ if( IsFollow() && GetOffset() == nPos ) // If we don't have a mass of text, we call our
+ FindMaster()->Prepare( PrepareHint::FootnoteInvalidationGone ); // Master's Prepare
+ if( nPos )
+ --nPos; // The char preceding our Follow
+ InvalidateRange(SwCharRange(nPos, TextFrameIndex(1)));
+ return bParaPossiblyInvalid;
+ }
+ case PrepareHint::ErgoSum:
+ case PrepareHint::QuoVadis:
+ {
+ TextFrameIndex nPos;
+ if( lcl_ErgoVadis( this, nPos, ePrep ) )
+ InvalidateRange(SwCharRange(nPos, TextFrameIndex(1)));
+ }
+ break;
+ case PrepareHint::FlyFrameAttributesChanged:
+ {
+ if( pVoid )
+ {
+ TextFrameIndex const nWhere = CalcFlyPos( static_cast<SwFrameFormat const *>(pVoid) );
+ OSL_ENSURE( TextFrameIndex(COMPLETE_STRING) != nWhere, "Prepare: Why me?" );
+ InvalidateRange(SwCharRange(nWhere, TextFrameIndex(1)));
+ return bParaPossiblyInvalid;
+ }
+ [[fallthrough]]; // else: continue with default case block
+ }
+ case PrepareHint::Clear:
+ default:
+ {
+ if( IsLocked() )
+ {
+ if( PrepareHint::FlyFrameArrive == ePrep || PrepareHint::FlyFrameLeave == ePrep )
+ {
+ TextFrameIndex const nLen = (GetFollow()
+ ? GetFollow()->GetOffset()
+ : TextFrameIndex(COMPLETE_STRING))
+ - GetOffset();
+ InvalidateRange( SwCharRange( GetOffset(), nLen ) );
+ }
+ }
+ else
+ {
+ if( pPara->GetRepaint().HasArea() )
+ SetCompletePaint();
+ Init();
+ pPara = nullptr;
+ if( GetOffset() && !IsFollow() )
+ SetOffset_( TextFrameIndex(0) );
+ if ( bNotify )
+ InvalidateSize();
+ else
+ InvalidateSize_();
+ }
+ return bParaPossiblyInvalid; // no SetPrep() happened
+ }
+ }
+ if( pPara )
+ {
+ pPara->SetPrep();
+ }
+
+ return bParaPossiblyInvalid;
+}
+#if defined __GNUC__ && !defined __clang__
+# pragma GCC diagnostic pop
+#endif
+
+/**
+ * Small Helper class:
+ * Prepares a test format.
+ * The frame is changed in size and position, its SwParaPortion is moved aside
+ * and a new one is created.
+ * To achieve this, run formatting with bTestFormat flag set.
+ * In the destructor the TextFrame is reset to its original state.
+ */
+class SwTestFormat
+{
+ SwTextFrame *pFrame;
+ SwParaPortion *pOldPara;
+ SwRect aOldFrame, aOldPrt;
+public:
+ SwTestFormat( SwTextFrame* pTextFrame, const SwFrame* pPrv, SwTwips nMaxHeight );
+ ~SwTestFormat();
+};
+
+SwTestFormat::SwTestFormat( SwTextFrame* pTextFrame, const SwFrame* pPre, SwTwips nMaxHeight )
+ : pFrame( pTextFrame )
+{
+ aOldFrame = pFrame->getFrameArea();
+ aOldPrt = pFrame->getFramePrintArea();
+
+ SwRectFnSet aRectFnSet(pFrame);
+ SwTwips nLower = aRectFnSet.GetBottomMargin(*pFrame);
+
+ {
+ // indeed, here the GetUpper()->getFramePrintArea() gets copied and manipulated
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame);
+ aFrm.setSwRect(pFrame->GetUpper()->getFramePrintArea());
+ aFrm += pFrame->GetUpper()->getFrameArea().Pos();
+ aRectFnSet.SetHeight( aFrm, nMaxHeight );
+
+ if( pFrame->GetPrev() )
+ {
+ aRectFnSet.SetPosY(
+ aFrm,
+ aRectFnSet.GetBottom(pFrame->GetPrev()->getFrameArea()) - ( aRectFnSet.IsVert() ? nMaxHeight + 1 : 0 ) );
+ }
+ }
+
+ SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFrame );
+ const SwBorderAttrs &rAttrs = *aAccess.Get();
+
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame);
+ aRectFnSet.SetPosX(aPrt, rAttrs.CalcLeft( pFrame ) );
+ }
+
+ if( pPre )
+ {
+ SwTwips nUpper = pFrame->CalcUpperSpace( &rAttrs, pPre );
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame);
+ aRectFnSet.SetPosY(aPrt, nUpper );
+ }
+
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame);
+ aRectFnSet.SetHeight( aPrt, std::max( tools::Long(0) , aRectFnSet.GetHeight(pFrame->getFrameArea()) - aRectFnSet.GetTop(aPrt) - nLower ) );
+ aRectFnSet.SetWidth( aPrt, aRectFnSet.GetWidth(pFrame->getFrameArea()) - ( rAttrs.CalcLeft( pFrame ) + rAttrs.CalcRight( pFrame ) ) );
+ }
+
+ pOldPara = pFrame->HasPara() ? pFrame->GetPara() : nullptr;
+ pFrame->SetPara( new SwParaPortion(), false );
+ OSL_ENSURE( ! pFrame->IsSwapped(), "A frame is swapped before Format_" );
+
+ if ( pFrame->IsVertical() )
+ pFrame->SwapWidthAndHeight();
+
+ SwTextFormatInfo aInf( pFrame->getRootFrame()->GetCurrShell()->GetOut(), pFrame, false, true, true );
+ SwTextFormatter aLine( pFrame, &aInf );
+
+ pFrame->Format_( aLine, aInf );
+
+ if ( pFrame->IsVertical() )
+ pFrame->SwapWidthAndHeight();
+
+ OSL_ENSURE( ! pFrame->IsSwapped(), "A frame is swapped after Format_" );
+}
+
+SwTestFormat::~SwTestFormat()
+{
+ {
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame);
+ aFrm.setSwRect(aOldFrame);
+ }
+
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame);
+ aPrt.setSwRect(aOldPrt);
+ }
+
+ pFrame->SetPara( pOldPara );
+}
+
+bool SwTextFrame::TestFormat( const SwFrame* pPrv, SwTwips &rMaxHeight, bool &bSplit )
+{
+ PROTOCOL_ENTER( this, PROT::TestFormat, DbgAction::NONE, nullptr )
+
+ if( IsLocked() && GetUpper()->getFramePrintArea().Width() <= 0 )
+ return false;
+
+ SwTestFormat aSave( this, pPrv, rMaxHeight );
+
+ return SwTextFrame::WouldFit(rMaxHeight, bSplit, true, false);
+}
+
+/**
+ * We should not and don't need to reformat.
+ * We assume that we already formatted and that the formatting
+ * data is still current.
+ *
+ * We also assume that the frame width of the Master and Follow
+ * are the same. That's why we're not calling FindBreak() for
+ * FindOrphans().
+ * The required height is coming from nMaxHeight.
+ *
+ * @returns true if I can split
+ */
+bool SwTextFrame::WouldFit(SwTwips &rMaxHeight, bool &bSplit, bool bTst, bool bMoveBwd)
+{
+ OSL_ENSURE( ! IsVertical() || ! IsSwapped(),
+ "SwTextFrame::WouldFit with swapped frame" );
+ SwRectFnSet aRectFnSet(this);
+
+ if( IsLocked() )
+ return false;
+
+ // it can happen that the IdleCollector removed the cached information
+ if( !IsEmpty() )
+ GetFormatted();
+
+ // i#27801 - correction: 'short cut' for empty paragraph
+ // can *not* be applied, if test format is in progress. The test format doesn't
+ // adjust the frame and the printing area - see method <SwTextFrame::Format_(..)>,
+ // which is called in <SwTextFrame::TestFormat(..)>
+ if ( IsEmpty() && !bTst )
+ {
+ bSplit = false;
+ SwTwips nHeight = aRectFnSet.IsVert() ? getFramePrintArea().SSize().Width() : getFramePrintArea().SSize().Height();
+ if( rMaxHeight < nHeight )
+ return false;
+ else
+ {
+ rMaxHeight -= nHeight;
+ return true;
+ }
+ }
+
+ // GetPara can still be 0 in edge cases
+ // We return true in order to be reformatted on the new Page
+ OSL_ENSURE( HasPara() || IsHiddenNow(), "WouldFit: GetFormatted() and then !HasPara()" );
+ if( !HasPara() || ( !aRectFnSet.GetHeight(getFrameArea()) && IsHiddenNow() ) )
+ return true;
+
+ // Because the Orphan flag only exists for a short moment, we also check
+ // whether the Framesize is set to very huge by CalcPreps, in order to
+ // force a MoveFwd
+ if (IsWidow() || (aRectFnSet.IsVert()
+ ? (0 == getFrameArea().Left())
+ : (sw::WIDOW_MAGIC - 20000 < getFrameArea().Bottom())))
+ {
+ SetWidow(false);
+ if ( GetFollow() )
+ {
+ // If we've ended up here due to a Widow request by our Follow, we check
+ // whether there's a Follow with a real height at all.
+ // Else (e.g. for newly created SctFrames) we ignore the IsWidow() and
+ // still check if we can find enough room
+ if (((!aRectFnSet.IsVert() && getFrameArea().Bottom() <= sw::WIDOW_MAGIC - 20000) ||
+ ( aRectFnSet.IsVert() && 0 < getFrameArea().Left() ) ) &&
+ ( GetFollow()->IsVertical() ?
+ !GetFollow()->getFrameArea().Width() :
+ !GetFollow()->getFrameArea().Height() ) )
+ {
+ SwTextFrame* pFoll = GetFollow()->GetFollow();
+ while( pFoll &&
+ ( pFoll->IsVertical() ?
+ !pFoll->getFrameArea().Width() :
+ !pFoll->getFrameArea().Height() ) )
+ pFoll = pFoll->GetFollow();
+ if( pFoll )
+ return false;
+ }
+ else
+ return false;
+ }
+ }
+
+ SwSwapIfNotSwapped swap( this );
+
+ SwTextSizeInfo aInf( this );
+ SwTextMargin aLine( this, &aInf );
+
+ WidowsAndOrphans aFrameBreak( this, rMaxHeight, bSplit );
+
+ bool bRet = true;
+
+ aLine.Bottom();
+ // is breaking necessary?
+ bSplit = !aFrameBreak.IsInside( aLine );
+ if ( bSplit )
+ bRet = !aFrameBreak.IsKeepAlways() && aFrameBreak.WouldFit(aLine, rMaxHeight, bTst, bMoveBwd);
+ else
+ {
+ // we need the total height including the current line
+ aLine.Top();
+ do
+ {
+ rMaxHeight -= aLine.GetLineHeight();
+ } while ( aLine.Next() );
+ }
+
+ return bRet;
+}
+
+SwTwips SwTextFrame::GetParHeight() const
+{
+ OSL_ENSURE( ! IsVertical() || ! IsSwapped(),
+ "SwTextFrame::GetParHeight with swapped frame" );
+
+ if( !HasPara() )
+ { // For non-empty paragraphs this is a special case
+ // For UnderSized we can simply just ask 1 Twip more
+ sal_uInt16 nRet = o3tl::narrowing<sal_uInt16>(getFramePrintArea().SSize().Height());
+ if( IsUndersized() )
+ {
+ if( IsEmpty() || GetText().isEmpty() )
+ nRet = o3tl::narrowing<sal_uInt16>(EmptyHeight());
+ else
+ ++nRet;
+ }
+ return nRet;
+ }
+
+ // TODO: Refactor and improve code
+ const SwLineLayout* pLineLayout = GetPara();
+ SwTwips nHeight = pLineLayout ? pLineLayout->GetRealHeight() : 0;
+
+ // Is this paragraph scrolled? Our height until now is at least
+ // one line height too low then
+ if( GetOffset() && !IsFollow() )
+ nHeight *= 2;
+
+ while ( pLineLayout && pLineLayout->GetNext() )
+ {
+ pLineLayout = pLineLayout->GetNext();
+ nHeight = nHeight + pLineLayout->GetRealHeight();
+ }
+
+ return nHeight;
+}
+
+/**
+ * @returns this _always_ in the formatted state!
+ */
+SwTextFrame* SwTextFrame::GetFormatted( bool bForceQuickFormat )
+{
+ vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut();
+ SwSwapIfSwapped swap( this );
+
+ // In case the SwLineLayout was cleared out of the s_pTextCache, recreate it
+ // Not for empty paragraphs
+ if( !HasPara() && !(isFrameAreaDefinitionValid() && IsEmpty()) )
+ {
+ // Calc() must be called, because frame position can be wrong
+ const bool bFormat = isFrameAreaSizeValid();
+ Calc(pRenderContext); // calls Format() if invalid
+
+ // If the flags were valid (hence bFormat=true), Calc did nothing,
+ // so Format() must be called manually in order to recreate
+ // the SwLineLayout that has been deleted from the
+ // SwTextFrame::s_pTextCache (hence !HasPara() above).
+ // Optimization with FormatQuick()
+ if( bFormat && !FormatQuick( bForceQuickFormat ) )
+ Format(getRootFrame()->GetCurrShell()->GetOut());
+ }
+
+ return this;
+}
+
+SwTwips SwTextFrame::CalcFitToContent()
+{
+ // i#31490
+ // If we are currently locked, we better return with a
+ // fairly reasonable value:
+ if ( IsLocked() )
+ return getFramePrintArea().Width();
+
+ SwParaPortion* pOldPara = GetPara();
+ SwParaPortion *pDummy = new SwParaPortion();
+ SetPara( pDummy, false );
+ const SwPageFrame* pPage = FindPageFrame();
+
+ const Point aOldFramePos = getFrameArea().Pos();
+ const SwTwips nOldFrameWidth = getFrameArea().Width();
+ const SwTwips nOldPrtWidth = getFramePrintArea().Width();
+ const SwTwips nPageWidth = GetUpper()->IsVertical() ?
+ pPage->getFramePrintArea().Height() :
+ pPage->getFramePrintArea().Width();
+
+ {
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aFrm.Width( nPageWidth );
+ }
+
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+ aPrt.Width( nPageWidth );
+ }
+
+ // i#25422 objects anchored as character in RTL
+ if ( IsRightToLeft() )
+ {
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aFrm.Pos().AdjustX(nOldFrameWidth - nPageWidth );
+ }
+
+ TextFrameLockGuard aLock( this );
+
+ SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this, false, true, true );
+ aInf.SetIgnoreFly( true );
+ SwTextFormatter aLine( this, &aInf );
+ SwHookOut aHook( aInf );
+
+ // i#54031 - assure minimum of MINLAY twips.
+ const SwTwips nMax = std::max( SwTwips(MINLAY), aLine.CalcFitToContent_() + 1 );
+
+ {
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aFrm.Width( nOldFrameWidth );
+
+ // i#25422 objects anchored as character in RTL
+ if ( IsRightToLeft() )
+ {
+ aFrm.Pos() = aOldFramePos;
+ }
+ }
+
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+ aPrt.Width( nOldPrtWidth );
+ }
+
+ SetPara( pOldPara );
+
+ return nMax;
+}
+
+/**
+ * Simulate format for a list item paragraph, whose list level attributes
+ * are in LABEL_ALIGNMENT mode, in order to determine additional first
+ * line offset for the real text formatting due to the value of label
+ * adjustment attribute of the list level.
+ */
+void SwTextFrame::CalcAdditionalFirstLineOffset()
+{
+ if ( IsLocked() )
+ return;
+
+ // reset additional first line offset
+ mnAdditionalFirstLineOffset = 0;
+
+ const SwTextNode* pTextNode( GetTextNodeForParaProps() );
+ // sw_redlinehide: check that pParaPropsNode is the correct one
+ assert(pTextNode->IsNumbered(getRootFrame()) == pTextNode->IsNumbered(nullptr));
+ if (!(pTextNode->IsNumbered(getRootFrame()) &&
+ pTextNode->IsCountedInList() && pTextNode->GetNumRule()))
+ return;
+
+ int nListLevel = pTextNode->GetActualListLevel();
+
+ if (nListLevel < 0)
+ nListLevel = 0;
+
+ if (nListLevel >= MAXLEVEL)
+ nListLevel = MAXLEVEL - 1;
+
+ const SwNumFormat& rNumFormat =
+ pTextNode->GetNumRule()->Get( o3tl::narrowing<sal_uInt16>(nListLevel) );
+ if ( rNumFormat.GetPositionAndSpaceMode() != SvxNumberFormat::LABEL_ALIGNMENT )
+ return;
+
+ // keep current paragraph portion and apply dummy paragraph portion
+ SwParaPortion* pOldPara = GetPara();
+ SwParaPortion *pDummy = new SwParaPortion();
+ SetPara( pDummy, false );
+
+ // lock paragraph
+ TextFrameLockGuard aLock( this );
+
+ // simulate text formatting
+ SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this, false, true, true );
+ aInf.SetIgnoreFly( true );
+ SwTextFormatter aLine( this, &aInf );
+ SwHookOut aHook( aInf );
+ aLine.CalcFitToContent_();
+
+ // determine additional first line offset
+ const SwLinePortion* pFirstPortion = aLine.GetCurr()->GetFirstPortion();
+ if ( pFirstPortion->InNumberGrp() && !pFirstPortion->IsFootnoteNumPortion() )
+ {
+ SwTwips nNumberPortionWidth( pFirstPortion->Width() );
+
+ const SwLinePortion* pPortion = pFirstPortion->GetNextPortion();
+ while ( pPortion &&
+ pPortion->InNumberGrp() && !pPortion->IsFootnoteNumPortion())
+ {
+ nNumberPortionWidth += pPortion->Width();
+ pPortion = pPortion->GetNextPortion();
+ }
+
+ if ( ( IsRightToLeft() &&
+ rNumFormat.GetNumAdjust() == SvxAdjust::Left ) ||
+ ( !IsRightToLeft() &&
+ rNumFormat.GetNumAdjust() == SvxAdjust::Right ) )
+ {
+ mnAdditionalFirstLineOffset = -nNumberPortionWidth;
+ }
+ else if ( rNumFormat.GetNumAdjust() == SvxAdjust::Center )
+ {
+ mnAdditionalFirstLineOffset = -(nNumberPortionWidth/2);
+ }
+ }
+
+ // restore paragraph portion
+ SetPara( pOldPara );
+}
+
+/**
+ * Determine the height of the last line for the calculation of
+ * the proportional line spacing
+ *
+ * Height of last line will be stored in new member
+ * mnHeightOfLastLine and can be accessed via method
+ * GetHeightOfLastLine()
+ *
+ * @param _bUseFont force the usage of the former algorithm to
+ * determine the height of the last line, which
+ * uses the font
+ */
+void SwTextFrame::CalcHeightOfLastLine( const bool _bUseFont )
+{
+ // i#71281
+ // Invalidate printing area, if height of last line changes
+ const SwTwips nOldHeightOfLastLine( mnHeightOfLastLine );
+
+ // determine output device
+ SwViewShell* pVsh = getRootFrame()->GetCurrShell();
+ OSL_ENSURE( pVsh, "<SwTextFrame::_GetHeightOfLastLineForPropLineSpacing()> - no SwViewShell" );
+
+ // i#78921
+ // There could be no <SwViewShell> instance in the case of loading a binary
+ // StarOffice file format containing an embedded Writer document.
+ if ( !pVsh )
+ {
+ return;
+ }
+ OutputDevice* pOut = pVsh->GetOut();
+ const IDocumentSettingAccess *const pIDSA = &GetDoc().getIDocumentSettingAccess();
+ if ( !pVsh->GetViewOptions()->getBrowseMode() ||
+ pVsh->GetViewOptions()->IsPrtFormat() )
+ {
+ pOut = GetDoc().getIDocumentDeviceAccess().getReferenceDevice( true );
+ }
+ OSL_ENSURE( pOut, "<SwTextFrame::_GetHeightOfLastLineForPropLineSpacing()> - no OutputDevice" );
+
+ if ( !pOut )
+ {
+ return;
+ }
+
+ // determine height of last line
+ if ( _bUseFont || pIDSA->get(DocumentSettingId::OLD_LINE_SPACING ) )
+ {
+ // former determination of last line height for proportional line
+ // spacing - take height of font set at the paragraph
+ // FIXME actually... must the font match across all nodes?
+ SwFont aFont( &GetTextNodeForParaProps()->GetSwAttrSet(), pIDSA );
+
+ // we must ensure that the font is restored correctly on the OutputDevice
+ // otherwise Last!=Owner could occur
+ if ( pLastFont )
+ {
+ SwFntObj *pOldFont = pLastFont;
+ pLastFont = nullptr;
+ aFont.SetFntChg( true );
+ aFont.ChgPhysFnt( pVsh, *pOut );
+ mnHeightOfLastLine = aFont.GetHeight( pVsh, *pOut );
+ assert(pLastFont && "coverity[var_deref_model] - pLastFont should be set in SwSubFont::ChgFnt");
+ pLastFont->Unlock();
+ pLastFont = pOldFont;
+ pLastFont->SetDevFont( pVsh, *pOut );
+ }
+ else
+ {
+ vcl::Font aOldFont = pOut->GetFont();
+ aFont.SetFntChg( true );
+ aFont.ChgPhysFnt( pVsh, *pOut );
+ mnHeightOfLastLine = aFont.GetHeight( pVsh, *pOut );
+ assert(pLastFont && "coverity[var_deref_model] - pLastFont should be set in SwSubFont::ChgFnt");
+ pLastFont->Unlock();
+ pLastFont = nullptr;
+ pOut->SetFont( aOldFont );
+ }
+ }
+ else
+ {
+ // new determination of last line height - take actually height of last line
+ // i#89000
+ // assure same results, if paragraph is undersized
+ if ( IsUndersized() )
+ {
+ mnHeightOfLastLine = 0;
+ }
+ else
+ {
+ bool bCalcHeightOfLastLine = true;
+ if ( ( !HasPara() && IsEmpty( ) ) || GetText().isEmpty() )
+ {
+ mnHeightOfLastLine = EmptyHeight();
+ bCalcHeightOfLastLine = false;
+ }
+
+ if ( bCalcHeightOfLastLine )
+ {
+ OSL_ENSURE( HasPara(),
+ "<SwTextFrame::CalcHeightOfLastLine()> - missing paragraph portions." );
+ const SwLineLayout* pLineLayout = GetPara();
+ while ( pLineLayout && pLineLayout->GetNext() )
+ {
+ // iteration to last line
+ pLineLayout = pLineLayout->GetNext();
+ }
+ if ( pLineLayout )
+ {
+ SwTwips nAscent, nDescent, nDummy1, nDummy2;
+ // i#47162 - suppress consideration of
+ // fly content portions and the line portion.
+ pLineLayout->MaxAscentDescent( nAscent, nDescent,
+ nDummy1, nDummy2,
+ nullptr, true );
+ // i#71281
+ // Suppress wrong invalidation of printing area, if method is
+ // called recursive.
+ // Thus, member <mnHeightOfLastLine> is only set directly, if
+ // no recursive call is needed.
+ const SwTwips nNewHeightOfLastLine = nAscent + nDescent;
+ // i#47162 - if last line only contains
+ // fly content portions, <mnHeightOfLastLine> is zero.
+ // In this case determine height of last line by the font
+ if ( nNewHeightOfLastLine == 0 )
+ {
+ CalcHeightOfLastLine( true );
+ }
+ else
+ {
+ mnHeightOfLastLine = nNewHeightOfLastLine;
+ }
+ }
+ }
+ }
+ }
+ // i#71281
+ // invalidate printing area, if height of last line changes
+ if ( mnHeightOfLastLine != nOldHeightOfLastLine )
+ {
+ InvalidatePrt();
+ }
+}
+
+/**
+ * Method returns the value of the inter line spacing for a text frame.
+ * Such a value exists for proportional line spacings ("1,5 Lines",
+ * "Double", "Proportional" and for leading line spacing ("Leading").
+ *
+ * @param _bNoPropLineSpacing (default = false) control whether the
+ * value of a proportional line spacing is
+ * returned or not
+ */
+tools::Long SwTextFrame::GetLineSpace( const bool _bNoPropLineSpace ) const
+{
+ tools::Long nRet = 0;
+
+ const SvxLineSpacingItem &rSpace = GetTextNodeForParaProps()->GetSwAttrSet().GetLineSpacing();
+
+ switch( rSpace.GetInterLineSpaceRule() )
+ {
+ case SvxInterLineSpaceRule::Prop:
+ {
+ if ( _bNoPropLineSpace )
+ {
+ break;
+ }
+
+ // i#11860 - adjust spacing implementation for object positioning
+ // - compatibility to MS Word
+ nRet = GetHeightOfLastLine();
+
+ tools::Long nTmp = nRet;
+ nTmp *= rSpace.GetPropLineSpace();
+ nTmp /= 100;
+ nTmp -= nRet;
+ if ( nTmp > 0 )
+ nRet = nTmp;
+ else
+ nRet = 0;
+ }
+ break;
+ case SvxInterLineSpaceRule::Fix:
+ {
+ if ( rSpace.GetInterLineSpace() > 0 )
+ nRet = rSpace.GetInterLineSpace();
+ }
+ break;
+ default:
+ break;
+ }
+ return nRet;
+}
+
+sal_uInt16 SwTextFrame::FirstLineHeight() const
+{
+ if ( !HasPara() )
+ {
+ if( IsEmpty() && isFrameAreaDefinitionValid() )
+ return IsVertical() ? o3tl::narrowing<sal_uInt16>(getFramePrintArea().Width()) : o3tl::narrowing<sal_uInt16>(getFramePrintArea().Height());
+ return USHRT_MAX;
+ }
+ const SwParaPortion *pPara = GetPara();
+ if ( !pPara )
+ return USHRT_MAX;
+
+ // tdf#146500 Lines with only fly overlap cannot be "moved", so the idea
+ // here is to continue until there's some text.
+ // FIXME ideally we want to count a fly to the line in which it is anchored
+ // - it may even be anchored in some other paragraph! SwFlyPortion doesn't
+ // have a pointer sadly so no way to find out.
+ sal_uInt16 nHeight(0);
+ for (SwLineLayout const* pLine = pPara; pLine; pLine = pLine->GetNext())
+ {
+ nHeight += pLine->Height();
+ if (::sw::FindNonFlyPortion(*pLine))
+ {
+ break;
+ }
+ }
+ return nHeight;
+}
+
+sal_Int32 SwTextFrame::GetLineCount(TextFrameIndex const nPos)
+{
+ sal_Int32 nRet = 0;
+ SwTextFrame *pFrame = this;
+ do
+ {
+ pFrame->GetFormatted();
+ if( !pFrame->HasPara() )
+ break;
+ SwTextSizeInfo aInf( pFrame );
+ SwTextMargin aLine( pFrame, &aInf );
+ if (TextFrameIndex(COMPLETE_STRING) == nPos)
+ aLine.Bottom();
+ else
+ aLine.CharToLine( nPos );
+ nRet = nRet + aLine.GetLineNr();
+ pFrame = pFrame->GetFollow();
+ } while ( pFrame && pFrame->GetOffset() <= nPos );
+ return nRet;
+}
+
+void SwTextFrame::ChgThisLines()
+{
+ // not necessary to format here (GetFormatted etc.), because we have to come from there!
+ sal_Int32 nNew = 0;
+ const SwLineNumberInfo &rInf = GetDoc().GetLineNumberInfo();
+ if ( !GetText().isEmpty() && HasPara() )
+ {
+ SwTextSizeInfo aInf( this );
+ SwTextMargin aLine( this, &aInf );
+ if ( rInf.IsCountBlankLines() )
+ {
+ aLine.Bottom();
+ nNew = aLine.GetLineNr();
+ }
+ else
+ {
+ do
+ {
+ if( aLine.GetCurr()->HasContent() )
+ ++nNew;
+ } while ( aLine.NextLine() );
+ }
+ }
+ else if ( rInf.IsCountBlankLines() )
+ nNew = 1;
+
+ if ( nNew == mnThisLines )
+ return;
+
+ if (!IsInTab() && GetTextNodeForParaProps()->GetSwAttrSet().GetLineNumber().IsCount())
+ {
+ mnAllLines -= mnThisLines;
+ mnThisLines = nNew;
+ mnAllLines += mnThisLines;
+ SwFrame *pNxt = GetNextContentFrame();
+ while( pNxt && pNxt->IsInTab() )
+ {
+ pNxt = pNxt->FindTabFrame();
+ if( nullptr != pNxt )
+ pNxt = pNxt->FindNextCnt();
+ }
+ if( pNxt )
+ pNxt->InvalidateLineNum();
+
+ // Extend repaint to the bottom.
+ if ( HasPara() )
+ {
+ SwRepaint& rRepaint = GetPara()->GetRepaint();
+ rRepaint.Bottom( std::max( rRepaint.Bottom(),
+ getFrameArea().Top()+getFramePrintArea().Bottom()));
+ }
+ }
+ else // Paragraphs which are not counted should not manipulate the AllLines.
+ mnThisLines = nNew;
+}
+
+void SwTextFrame::RecalcAllLines()
+{
+ ValidateLineNum();
+
+ if ( IsInTab() )
+ return;
+
+ const sal_Int32 nOld = GetAllLines();
+ const SwFormatLineNumber &rLineNum = GetTextNodeForParaProps()->GetSwAttrSet().GetLineNumber();
+ sal_Int32 nNewNum;
+ const bool bRestart = GetDoc().GetLineNumberInfo().IsRestartEachPage();
+
+ if ( !IsFollow() && rLineNum.GetStartValue() && rLineNum.IsCount() )
+ nNewNum = rLineNum.GetStartValue() - 1;
+ // If it is a follow or not has not be considered if it is a restart at each page; the
+ // restart should also take effect at follows.
+ else if ( bRestart && FindPageFrame()->FindFirstBodyContent() == this )
+ {
+ nNewNum = 0;
+ }
+ else
+ {
+ SwContentFrame *pPrv = GetPrevContentFrame();
+ while ( pPrv &&
+ (pPrv->IsInTab() || pPrv->IsInDocBody() != IsInDocBody()) )
+ pPrv = pPrv->GetPrevContentFrame();
+
+ // i#78254 Restart line numbering at page change
+ // First body content may be in table!
+ if ( bRestart && pPrv && pPrv->FindPageFrame() != FindPageFrame() )
+ pPrv = nullptr;
+
+ nNewNum = pPrv ? static_cast<SwTextFrame*>(pPrv)->GetAllLines() : 0;
+ }
+ if ( rLineNum.IsCount() )
+ nNewNum += GetThisLines();
+
+ if ( nOld == nNewNum )
+ return;
+
+ mnAllLines = nNewNum;
+ SwContentFrame *pNxt = GetNextContentFrame();
+ while ( pNxt &&
+ (pNxt->IsInTab() || pNxt->IsInDocBody() != IsInDocBody()) )
+ pNxt = pNxt->GetNextContentFrame();
+ if ( pNxt )
+ {
+ if ( pNxt->GetUpper() != GetUpper() )
+ pNxt->InvalidateLineNum();
+ else
+ pNxt->InvalidateLineNum_();
+ }
+}
+
+void SwTextFrame::VisitPortions( SwPortionHandler& rPH ) const
+{
+ const SwParaPortion* pPara = isFrameAreaDefinitionValid() ? GetPara() : nullptr;
+
+ if (pPara)
+ {
+ if ( IsFollow() )
+ rPH.Skip( GetOffset() );
+
+ const SwLineLayout* pLine = pPara;
+ while ( pLine )
+ {
+ const SwLinePortion* pPor = pLine->GetFirstPortion();
+ while ( pPor )
+ {
+ pPor->HandlePortion( rPH );
+ pPor = pPor->GetNextPortion();
+ }
+
+ rPH.LineBreak();
+ pLine = pLine->GetNext();
+ }
+ }
+
+ rPH.Finish();
+}
+
+const SwScriptInfo* SwTextFrame::GetScriptInfo() const
+{
+ const SwParaPortion* pPara = GetPara();
+ return pPara ? &pPara->GetScriptInfo() : nullptr;
+}
+
+/**
+ * Helper function for SwTextFrame::CalcBasePosForFly()
+ */
+static SwTwips lcl_CalcFlyBasePos( const SwTextFrame& rFrame, SwRect aFlyRect,
+ SwTextFly const & rTextFly )
+{
+ SwRectFnSet aRectFnSet(&rFrame);
+ SwTwips nRet = rFrame.IsRightToLeft() ?
+ aRectFnSet.GetRight(rFrame.getFrameArea()) :
+ aRectFnSet.GetLeft(rFrame.getFrameArea());
+
+ do
+ {
+ SwRect aRect = rTextFly.GetFrame( aFlyRect );
+ if ( 0 != aRectFnSet.GetWidth(aRect) )
+ {
+ if ( rFrame.IsRightToLeft() )
+ {
+ if ( aRectFnSet.GetRight(aRect) -
+ aRectFnSet.GetRight(aFlyRect) >= 0 )
+ {
+ aRectFnSet.SetRight(
+aFlyRect, aRectFnSet.GetLeft(aRect) );
+ nRet = aRectFnSet.GetLeft(aRect);
+ }
+ else
+ break;
+ }
+ else
+ {
+ if ( aRectFnSet.GetLeft(aFlyRect) -
+ aRectFnSet.GetLeft(aRect) >= 0 )
+ {
+ aRectFnSet.SetLeft(
+aFlyRect, aRectFnSet.GetRight(aRect) + 1 );
+ nRet = aRectFnSet.GetRight(aRect);
+ }
+ else
+ break;
+ }
+ }
+ else
+ break;
+ }
+ while ( aRectFnSet.GetWidth(aFlyRect) > 0 );
+
+ return nRet;
+}
+
+void SwTextFrame::CalcBaseOfstForFly()
+{
+ OSL_ENSURE( !IsVertical() || !IsSwapped(),
+ "SwTextFrame::CalcBasePosForFly with swapped frame!" );
+
+ if (!GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_FLY_OFFSETS))
+ return;
+
+ SwRectFnSet aRectFnSet(this);
+
+ SwRect aFlyRect( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() );
+
+ // Get first 'real' line and adjust position and height of line rectangle.
+ // Correct behaviour if no 'real' line exists
+ // (empty paragraph with and without a dummy portion)
+ SwTwips nFlyAnchorVertOfstNoWrap = 0;
+ {
+ SwTwips nTop = aRectFnSet.GetTop(aFlyRect);
+ const SwLineLayout* pLay = GetPara();
+ SwTwips nLineHeight = 200;
+ while( pLay && pLay->IsDummy() && pLay->GetNext() )
+ {
+ nTop += pLay->Height();
+ nFlyAnchorVertOfstNoWrap += pLay->Height();
+ pLay = pLay->GetNext();
+ }
+ if ( pLay )
+ {
+ nLineHeight = pLay->Height();
+ }
+ aRectFnSet.SetTopAndHeight( aFlyRect, nTop, nLineHeight );
+ }
+
+ SwTextFly aTextFly( this );
+ aTextFly.SetIgnoreCurrentFrame( true );
+ aTextFly.SetIgnoreContour( true );
+ // ignore objects in page header|footer for
+ // text frames not in page header|footer
+ aTextFly.SetIgnoreObjsInHeaderFooter( true );
+ SwTwips nRet1 = lcl_CalcFlyBasePos( *this, aFlyRect, aTextFly );
+ aTextFly.SetIgnoreCurrentFrame( false );
+ SwTwips nRet2 = lcl_CalcFlyBasePos( *this, aFlyRect, aTextFly );
+
+ // make values relative to frame start position
+ SwTwips nLeft = IsRightToLeft() ?
+ aRectFnSet.GetRight(getFrameArea()) :
+ aRectFnSet.GetLeft(getFrameArea());
+
+ mnFlyAnchorOfst = nRet1 - nLeft;
+ mnFlyAnchorOfstNoWrap = nRet2 - nLeft;
+
+ if (!GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS))
+ return;
+
+ if (mnFlyAnchorOfstNoWrap > 0)
+ mnFlyAnchorVertOfstNoWrap = nFlyAnchorVertOfstNoWrap;
+}
+
+SwTwips SwTextFrame::GetBaseVertOffsetForFly(bool bIgnoreFlysAnchoredAtThisFrame) const
+{
+ return bIgnoreFlysAnchoredAtThisFrame ? 0 : mnFlyAnchorVertOfstNoWrap;
+}
+
+/**
+ * Repaint all text frames of the given text node
+ */
+void SwTextFrame::repaintTextFrames( const SwTextNode& rNode )
+{
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rNode);
+ for( const SwTextFrame *pFrame = aIter.First(); pFrame; pFrame = aIter.Next() )
+ {
+ SwRect aRec( pFrame->GetPaintArea() );
+ const SwRootFrame *pRootFrame = pFrame->getRootFrame();
+ SwViewShell *pCurShell = pRootFrame ? pRootFrame->GetCurrShell() : nullptr;
+ if( pCurShell )
+ pCurShell->InvalidateWindows( aRec );
+ }
+}
+
+void SwTextFrame::UpdateOutlineContentVisibilityButton(SwWrtShell* pWrtSh) const
+{
+ if (pWrtSh && pWrtSh->GetViewOptions()->IsShowOutlineContentVisibilityButton() &&
+ GetTextNodeFirst()->IsOutline())
+ {
+ SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin();
+ SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager();
+ rMngr.SetOutlineContentVisibilityButton(this);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */