sc: factor out some more code
[LibreOffice.git] / sw / source / core / text / txtfrm.cxx
blob77ab42f418951bfe2e4fe7e3ae126914dab53a2f
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <config_wasm_strip.h>
22 #include <hintids.hxx>
23 #include <hints.hxx>
24 #include <svl/ctloptions.hxx>
25 #include <editeng/lspcitem.hxx>
26 #include <editeng/lrspitem.hxx>
27 #include <editeng/brushitem.hxx>
28 #include <editeng/charhiddenitem.hxx>
29 #include <editeng/pgrditem.hxx>
30 #include <comphelper/configuration.hxx>
31 #include <swmodule.hxx>
32 #include <SwSmartTagMgr.hxx>
33 #include <doc.hxx>
34 #include <IDocumentSettingAccess.hxx>
35 #include <IDocumentDeviceAccess.hxx>
36 #include <IDocumentFieldsAccess.hxx>
37 #include <rootfrm.hxx>
38 #include <pagefrm.hxx>
39 #include <viewsh.hxx>
40 #include <pam.hxx>
41 #include <ndtxt.hxx>
42 #include <paratr.hxx>
43 #include <viewopt.hxx>
44 #include <flyfrm.hxx>
45 #include <tabfrm.hxx>
46 #include <frmatr.hxx>
47 #include <frmtool.hxx>
48 #include <tgrditem.hxx>
49 #include <dbg_lay.hxx>
50 #include <fmtfld.hxx>
51 #include <fmtftn.hxx>
52 #include <txtfld.hxx>
53 #include <txtftn.hxx>
54 #include <ftninfo.hxx>
55 #include <fmtline.hxx>
56 #include <txtfrm.hxx>
57 #include <notxtfrm.hxx>
58 #include <sectfrm.hxx>
59 #include "itrform2.hxx"
60 #include "widorp.hxx"
61 #include "txtcache.hxx"
62 #include <fntcache.hxx>
63 #include <SwGrammarMarkUp.hxx>
64 #include <lineinfo.hxx>
65 #include <SwPortionHandler.hxx>
66 #include <dcontact.hxx>
67 #include <sortedobjs.hxx>
68 #include <txtflcnt.hxx>
69 #include <fmtflcnt.hxx>
70 #include <fmtcntnt.hxx>
71 #include <numrule.hxx>
72 #include <GrammarContact.hxx>
73 #include <calbck.hxx>
74 #include <ftnidx.hxx>
75 #include <ftnfrm.hxx>
77 #include <wrtsh.hxx>
78 #include <view.hxx>
79 #include <edtwin.hxx>
80 #include <FrameControlsManager.hxx>
82 namespace sw {
84 MergedAttrIterBase::MergedAttrIterBase(SwTextFrame const& rFrame)
85 : m_pMerged(rFrame.GetMergedPara())
86 , m_pNode(m_pMerged ? nullptr : rFrame.GetTextNodeFirst())
87 , m_CurrentExtent(0)
88 , m_CurrentHint(0)
92 SwTextAttr const* MergedAttrIter::NextAttr(SwTextNode const** ppNode)
94 if (m_pMerged)
96 while (m_CurrentExtent < m_pMerged->extents.size())
98 sw::Extent const& rExtent(m_pMerged->extents[m_CurrentExtent]);
99 if (SwpHints const*const pHints = rExtent.pNode->GetpSwpHints())
101 while (m_CurrentHint < pHints->Count())
103 SwTextAttr *const pHint(pHints->Get(m_CurrentHint));
104 if (rExtent.nEnd < pHint->GetStart()
105 // <= if it has no end or isn't empty
106 || (rExtent.nEnd == pHint->GetStart()
107 && (!pHint->GetEnd()
108 || *pHint->GetEnd() != pHint->GetStart())))
110 break;
112 ++m_CurrentHint;
113 if (rExtent.nStart <= pHint->GetStart())
115 if (ppNode)
117 *ppNode = rExtent.pNode;
119 return pHint;
123 ++m_CurrentExtent;
124 if (m_CurrentExtent < m_pMerged->extents.size() &&
125 rExtent.pNode != m_pMerged->extents[m_CurrentExtent].pNode)
127 m_CurrentHint = 0; // reset
130 return nullptr;
132 else
134 SwpHints const*const pHints(m_pNode->GetpSwpHints());
135 if (pHints)
137 if (m_CurrentHint < pHints->Count())
139 SwTextAttr const*const pHint(pHints->Get(m_CurrentHint));
140 ++m_CurrentHint;
141 if (ppNode)
143 *ppNode = m_pNode;
145 return pHint;
148 return nullptr;
152 MergedAttrIterByEnd::MergedAttrIterByEnd(SwTextFrame const& rFrame)
153 : m_pNode(rFrame.GetMergedPara() ? nullptr : rFrame.GetTextNodeFirst())
154 , m_CurrentHint(0)
156 if (!m_pNode)
158 MergedAttrIterReverse iter(rFrame);
159 SwTextNode const* pNode(nullptr);
160 while (SwTextAttr const* pHint = iter.PrevAttr(&pNode))
162 m_Hints.emplace_back(pNode, pHint);
167 SwTextAttr const* MergedAttrIterByEnd::NextAttr(SwTextNode const*& rpNode)
169 if (m_pNode)
171 SwpHints const*const pHints(m_pNode->GetpSwpHints());
172 if (pHints)
174 if (m_CurrentHint < pHints->Count())
176 SwTextAttr const*const pHint(
177 pHints->GetSortedByEnd(m_CurrentHint));
178 ++m_CurrentHint;
179 rpNode = m_pNode;
180 return pHint;
183 return nullptr;
185 else
187 if (m_CurrentHint < m_Hints.size())
189 auto const ret = m_Hints[m_Hints.size() - m_CurrentHint - 1];
190 ++m_CurrentHint;
191 rpNode = ret.first;
192 return ret.second;
194 return nullptr;
198 void MergedAttrIterByEnd::PrevAttr()
200 assert(0 < m_CurrentHint); // should only rewind as far as 0
201 --m_CurrentHint;
204 MergedAttrIterReverse::MergedAttrIterReverse(SwTextFrame const& rFrame)
205 : MergedAttrIterBase(rFrame)
207 if (m_pMerged)
209 m_CurrentExtent = m_pMerged->extents.size();
210 SwpHints const*const pHints(0 < m_CurrentExtent
211 ? m_pMerged->extents[m_CurrentExtent-1].pNode->GetpSwpHints()
212 : nullptr);
213 if (pHints)
215 pHints->SortIfNeedBe();
216 m_CurrentHint = pHints->Count();
219 else
221 if (SwpHints const*const pHints = m_pNode->GetpSwpHints())
223 pHints->SortIfNeedBe();
224 m_CurrentHint = pHints->Count();
229 SwTextAttr const* MergedAttrIterReverse::PrevAttr(SwTextNode const** ppNode)
231 if (m_pMerged)
233 while (0 < m_CurrentExtent)
235 sw::Extent const& rExtent(m_pMerged->extents[m_CurrentExtent-1]);
236 if (SwpHints const*const pHints = rExtent.pNode->GetpSwpHints())
238 while (0 < m_CurrentHint)
240 SwTextAttr *const pHint(
241 pHints->GetSortedByEnd(m_CurrentHint - 1));
242 if (pHint->GetAnyEnd() < rExtent.nStart
243 // <= if it has end and isn't empty
244 || (pHint->GetEnd()
245 && *pHint->GetEnd() != pHint->GetStart()
246 && *pHint->GetEnd() == rExtent.nStart))
248 break;
250 --m_CurrentHint;
251 if (pHint->GetAnyEnd() <= rExtent.nEnd)
253 if (ppNode)
255 *ppNode = rExtent.pNode;
257 return pHint;
261 --m_CurrentExtent;
262 if (0 < m_CurrentExtent &&
263 rExtent.pNode != m_pMerged->extents[m_CurrentExtent-1].pNode)
265 SwpHints const*const pHints(
266 m_pMerged->extents[m_CurrentExtent-1].pNode->GetpSwpHints());
267 m_CurrentHint = pHints ? pHints->Count() : 0; // reset
268 if (pHints)
269 pHints->SortIfNeedBe();
272 return nullptr;
274 else
276 SwpHints const*const pHints(m_pNode->GetpSwpHints());
277 if (pHints && 0 < m_CurrentHint)
279 SwTextAttr const*const pHint(pHints->GetSortedByEnd(m_CurrentHint - 1));
280 --m_CurrentHint;
281 if (ppNode)
283 *ppNode = m_pNode;
285 return pHint;
287 return nullptr;
291 bool FrameContainsNode(SwContentFrame const& rFrame, SwNodeOffset const nNodeIndex)
293 if (rFrame.IsTextFrame())
295 SwTextFrame const& rTextFrame(static_cast<SwTextFrame const&>(rFrame));
296 if (sw::MergedPara const*const pMerged = rTextFrame.GetMergedPara())
298 SwNodeOffset const nFirst(pMerged->pFirstNode->GetIndex());
299 SwNodeOffset const nLast(pMerged->pLastNode->GetIndex());
300 return (nFirst <= nNodeIndex && nNodeIndex <= nLast);
302 else
304 return rTextFrame.GetTextNodeFirst()->GetIndex() == nNodeIndex;
307 else
309 assert(rFrame.IsNoTextFrame());
310 return static_cast<SwNoTextFrame const&>(rFrame).GetNode()->GetIndex() == nNodeIndex;
314 bool IsParaPropsNode(SwRootFrame const& rLayout, SwTextNode const& rNode)
316 if (rLayout.HasMergedParas())
318 if (SwTextFrame const*const pFrame = static_cast<SwTextFrame*>(rNode.getLayoutFrame(&rLayout)))
320 sw::MergedPara const*const pMerged(pFrame->GetMergedPara());
321 if (pMerged && pMerged->pParaPropsNode != &rNode)
323 return false;
327 return true;
330 SwTextNode *
331 GetParaPropsNode(SwRootFrame const& rLayout, SwNode const& rPos)
333 const SwTextNode *const pTextNode(rPos.GetTextNode());
334 if (pTextNode && !sw::IsParaPropsNode(rLayout, *pTextNode))
336 return static_cast<SwTextFrame*>(pTextNode->getLayoutFrame(&rLayout))->GetMergedPara()->pParaPropsNode;
338 else
340 return const_cast<SwTextNode*>(pTextNode);
344 SwPosition
345 GetParaPropsPos(SwRootFrame const& rLayout, SwPosition const& rPos)
347 SwPosition pos(rPos);
348 SwTextNode const*const pNode(pos.GetNode().GetTextNode());
349 if (pNode)
350 pos.Assign( *sw::GetParaPropsNode(rLayout, *pNode) );
351 return pos;
354 std::pair<SwTextNode *, SwTextNode *>
355 GetFirstAndLastNode(SwRootFrame const& rLayout, SwNode const& rPos)
357 SwTextNode *const pTextNode(const_cast<SwTextNode*>(rPos.GetTextNode()));
358 if (pTextNode && rLayout.HasMergedParas())
360 if (SwTextFrame const*const pFrame = static_cast<SwTextFrame*>(pTextNode->getLayoutFrame(&rLayout)))
362 if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara())
364 return std::make_pair(pMerged->pFirstNode, const_cast<SwTextNode*>(pMerged->pLastNode));
368 return std::make_pair(pTextNode, pTextNode);
371 SwTextNode const& GetAttrMerged(SfxItemSet & rFormatSet,
372 SwTextNode const& rNode, SwRootFrame const*const pLayout)
374 rNode.SwContentNode::GetAttr(rFormatSet);
375 if (pLayout && pLayout->HasMergedParas())
377 auto pFrame = static_cast<SwTextFrame*>(rNode.getLayoutFrame(pLayout));
378 if (sw::MergedPara const*const pMerged = pFrame ? pFrame->GetMergedPara() : nullptr)
380 if (pMerged->pFirstNode != &rNode)
382 rFormatSet.ClearItem(RES_PAGEDESC);
383 rFormatSet.ClearItem(RES_BREAK);
384 static_assert(RES_PAGEDESC + 1 == sal_uInt16(RES_BREAK),
385 "first-node items must be adjacent");
386 SfxItemSetFixed<RES_PAGEDESC, RES_BREAK> firstSet(*rFormatSet.GetPool());
387 pMerged->pFirstNode->SwContentNode::GetAttr(firstSet);
388 rFormatSet.Put(firstSet);
391 if (pMerged->pParaPropsNode != &rNode)
393 for (sal_uInt16 i = RES_PARATR_BEGIN; i != RES_FRMATR_END; ++i)
395 if (i != RES_PAGEDESC && i != RES_BREAK)
397 rFormatSet.ClearItem(i);
400 for (sal_uInt16 i = XATTR_FILL_FIRST; i <= XATTR_FILL_LAST; ++i)
402 rFormatSet.ClearItem(i);
404 SfxItemSetFixed<RES_PARATR_BEGIN, RES_PAGEDESC,
405 RES_BREAK+1, RES_FRMATR_END,
406 XATTR_FILL_FIRST, XATTR_FILL_LAST+1>
407 propsSet(*rFormatSet.GetPool());
408 pMerged->pParaPropsNode->SwContentNode::GetAttr(propsSet);
409 rFormatSet.Put(propsSet);
410 return *pMerged->pParaPropsNode;
412 // keep all the CHRATR/UNKNOWNATR anyway...
415 return rNode;
418 } // namespace sw
420 /// Switches width and height of the text frame
421 void SwTextFrame::SwapWidthAndHeight()
424 SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
426 if ( ! mbIsSwapped )
428 const tools::Long nPrtOfstX = aPrt.Pos().X();
429 aPrt.Pos().setX( aPrt.Pos().Y() );
431 if( IsVertLR() )
433 aPrt.Pos().setY( nPrtOfstX );
435 else
437 aPrt.Pos().setY( getFrameArea().Width() - ( nPrtOfstX + aPrt.Width() ) );
440 else
442 const tools::Long nPrtOfstY = aPrt.Pos().Y();
443 aPrt.Pos().setY( aPrt.Pos().X() );
445 if( IsVertLR() )
447 aPrt.Pos().setX( nPrtOfstY );
449 else
451 aPrt.Pos().setX( getFrameArea().Height() - ( nPrtOfstY + aPrt.Height() ) );
455 const tools::Long nPrtWidth = aPrt.Width();
456 aPrt.Width( aPrt.Height() );
457 aPrt.Height( nPrtWidth );
461 const tools::Long nFrameWidth = getFrameArea().Width();
462 SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
463 aFrm.Width( aFrm.Height() );
464 aFrm.Height( nFrameWidth );
467 mbIsSwapped = ! mbIsSwapped;
471 * Calculates the coordinates of a rectangle when switching from
472 * horizontal to vertical layout.
474 void SwTextFrame::SwitchHorizontalToVertical( SwRect& rRect ) const
476 // calc offset inside frame
477 tools::Long nOfstX, nOfstY;
478 if ( IsVertLR() )
480 if (IsVertLRBT())
482 // X and Y offsets here mean the position of the point that will be the top left corner
483 // after the switch.
484 nOfstX = rRect.Left() + rRect.Width() - getFrameArea().Left();
485 nOfstY = rRect.Top() - getFrameArea().Top();
487 else
489 nOfstX = rRect.Left() - getFrameArea().Left();
490 nOfstY = rRect.Top() - getFrameArea().Top();
493 else
495 nOfstX = rRect.Left() - getFrameArea().Left();
496 nOfstY = rRect.Top() + rRect.Height() - getFrameArea().Top();
499 const tools::Long nWidth = rRect.Width();
500 const tools::Long nHeight = rRect.Height();
502 if ( IsVertLR() )
504 rRect.Left(getFrameArea().Left() + nOfstY);
506 else
508 if ( mbIsSwapped )
509 rRect.Left( getFrameArea().Left() + getFrameArea().Height() - nOfstY );
510 else
511 // frame is rotated
512 rRect.Left( getFrameArea().Left() + getFrameArea().Width() - nOfstY );
515 if (IsVertLRBT())
517 if (mbIsSwapped)
518 rRect.Top(getFrameArea().Top() + getFrameArea().Width() - nOfstX);
519 else
520 rRect.Top(getFrameArea().Top() + getFrameArea().Height() - nOfstX);
522 else
523 rRect.Top(getFrameArea().Top() + nOfstX);
524 rRect.Width( nHeight );
525 rRect.Height( nWidth );
529 * Calculates the coordinates of a point when switching from
530 * horizontal to vertical layout.
532 void SwTextFrame::SwitchHorizontalToVertical( Point& rPoint ) const
534 if (IsVertLRBT())
536 // The horizontal origo is the top left corner, the LRBT origo is the
537 // bottom left corner. Finally x and y has to be swapped.
538 SAL_WARN_IF(!mbIsSwapped, "sw.core",
539 "SwTextFrame::SwitchHorizontalToVertical, IsVertLRBT, not swapped");
540 Point aPoint(rPoint);
541 rPoint.setX(getFrameArea().Left() + (aPoint.Y() - getFrameArea().Top()));
542 // This would be bottom - x delta, but bottom is top + height, finally
543 // width (and not height), as it's swapped.
544 rPoint.setY(getFrameArea().Top() + getFrameArea().Width()
545 - (aPoint.X() - getFrameArea().Left()));
546 return;
549 // calc offset inside frame
550 const tools::Long nOfstX = rPoint.X() - getFrameArea().Left();
551 const tools::Long nOfstY = rPoint.Y() - getFrameArea().Top();
552 if ( IsVertLR() )
553 rPoint.setX( getFrameArea().Left() + nOfstY );
554 else
556 if ( mbIsSwapped )
557 rPoint.setX( getFrameArea().Left() + getFrameArea().Height() - nOfstY );
558 else
559 // calc rotated coords
560 rPoint.setX( getFrameArea().Left() + getFrameArea().Width() - nOfstY );
563 rPoint.setY( getFrameArea().Top() + nOfstX );
567 * Calculates the a limit value when switching from
568 * horizontal to vertical layout.
570 tools::Long SwTextFrame::SwitchHorizontalToVertical( tools::Long nLimit ) const
572 Point aTmp( 0, nLimit );
573 SwitchHorizontalToVertical( aTmp );
574 return aTmp.X();
578 * Calculates the coordinates of a rectangle when switching from
579 * vertical to horizontal layout.
581 void SwTextFrame::SwitchVerticalToHorizontal( SwRect& rRect ) const
583 tools::Long nOfstX;
585 // calc offset inside frame
586 if ( IsVertLR() )
587 nOfstX = rRect.Left() - getFrameArea().Left();
588 else
590 if ( mbIsSwapped )
591 nOfstX = getFrameArea().Left() + getFrameArea().Height() - ( rRect.Left() + rRect.Width() );
592 else
593 nOfstX = getFrameArea().Left() + getFrameArea().Width() - ( rRect.Left() + rRect.Width() );
596 tools::Long nOfstY;
597 if (IsVertLRBT())
599 // Note that mbIsSwapped only affects the frame area, not rRect, so rRect.Height() is used
600 // here unconditionally.
601 if (mbIsSwapped)
602 nOfstY = getFrameArea().Top() + getFrameArea().Width() - (rRect.Top() + rRect.Height());
603 else
604 nOfstY = getFrameArea().Top() + getFrameArea().Height() - (rRect.Top() + rRect.Height());
606 else
607 nOfstY = rRect.Top() - getFrameArea().Top();
608 const tools::Long nWidth = rRect.Height();
609 const tools::Long nHeight = rRect.Width();
611 // calc rotated coords
612 rRect.Left( getFrameArea().Left() + nOfstY );
613 rRect.Top( getFrameArea().Top() + nOfstX );
614 rRect.Width( nWidth );
615 rRect.Height( nHeight );
619 * Calculates the coordinates of a point when switching from
620 * vertical to horizontal layout.
622 void SwTextFrame::SwitchVerticalToHorizontal( Point& rPoint ) const
624 tools::Long nOfstX;
626 // calc offset inside frame
627 if ( IsVertLR() )
628 // X offset is Y - left.
629 nOfstX = rPoint.X() - getFrameArea().Left();
630 else
632 // X offset is right - X.
633 if ( mbIsSwapped )
634 nOfstX = getFrameArea().Left() + getFrameArea().Height() - rPoint.X();
635 else
636 nOfstX = getFrameArea().Left() + getFrameArea().Width() - rPoint.X();
639 tools::Long nOfstY;
640 if (IsVertLRBT())
642 // Y offset is bottom - Y.
643 if (mbIsSwapped)
644 nOfstY = getFrameArea().Top() + getFrameArea().Width() - rPoint.Y();
645 else
646 nOfstY = getFrameArea().Top() + getFrameArea().Height() - rPoint.Y();
648 else
649 // Y offset is Y - top.
650 nOfstY = rPoint.Y() - getFrameArea().Top();
652 // calc rotated coords
653 rPoint.setX( getFrameArea().Left() + nOfstY );
654 rPoint.setY( getFrameArea().Top() + nOfstX );
658 * Calculates the a limit value when switching from
659 * vertical to horizontal layout.
661 tools::Long SwTextFrame::SwitchVerticalToHorizontal( tools::Long nLimit ) const
663 Point aTmp( nLimit, 0 );
664 SwitchVerticalToHorizontal( aTmp );
665 return aTmp.Y();
668 SwFrameSwapper::SwFrameSwapper( const SwTextFrame* pTextFrame, bool bSwapIfNotSwapped )
669 : pFrame( pTextFrame ), bUndo( false )
671 if (pFrame->IsVertical() && bSwapIfNotSwapped != pFrame->IsSwapped())
673 bUndo = true;
674 const_cast<SwTextFrame*>(pFrame)->SwapWidthAndHeight();
678 SwFrameSwapper::~SwFrameSwapper()
680 if ( bUndo )
681 const_cast<SwTextFrame*>(pFrame)->SwapWidthAndHeight();
684 void SwTextFrame::SwitchLTRtoRTL( SwRect& rRect ) const
686 SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this));
688 tools::Long nWidth = rRect.Width();
689 rRect.Left( 2 * ( getFrameArea().Left() + getFramePrintArea().Left() ) +
690 getFramePrintArea().Width() - rRect.Right() - 1 );
692 rRect.Width( nWidth );
695 void SwTextFrame::SwitchLTRtoRTL( Point& rPoint ) const
697 SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this));
699 rPoint.setX( 2 * ( getFrameArea().Left() + getFramePrintArea().Left() ) + getFramePrintArea().Width() - rPoint.X() - 1 );
702 SwLayoutModeModifier::SwLayoutModeModifier( const OutputDevice& rOutp ) :
703 m_rOut( rOutp ), m_nOldLayoutMode( rOutp.GetLayoutMode() )
707 SwLayoutModeModifier::~SwLayoutModeModifier()
709 const_cast<OutputDevice&>(m_rOut).SetLayoutMode( m_nOldLayoutMode );
712 void SwLayoutModeModifier::Modify( bool bChgToRTL )
714 const_cast<OutputDevice&>(m_rOut).SetLayoutMode( bChgToRTL ?
715 vcl::text::ComplexTextLayoutFlags::BiDiStrong | vcl::text::ComplexTextLayoutFlags::BiDiRtl :
716 vcl::text::ComplexTextLayoutFlags::BiDiStrong );
719 void SwLayoutModeModifier::SetAuto()
721 const vcl::text::ComplexTextLayoutFlags nNewLayoutMode = m_nOldLayoutMode & ~vcl::text::ComplexTextLayoutFlags::BiDiStrong;
722 const_cast<OutputDevice&>(m_rOut).SetLayoutMode( nNewLayoutMode );
725 SwDigitModeModifier::SwDigitModeModifier( const OutputDevice& rOutp, LanguageType eCurLang,
726 SvtCTLOptions::TextNumerals eCTLTextNumerals ) :
727 rOut( rOutp ), nOldLanguageType( rOutp.GetDigitLanguage() )
729 LanguageType eLang = eCurLang;
730 if (comphelper::IsFuzzing())
731 eLang = LANGUAGE_ENGLISH_US;
732 else
734 if ( SvtCTLOptions::NUMERALS_HINDI == eCTLTextNumerals )
735 eLang = LANGUAGE_ARABIC_SAUDI_ARABIA;
736 else if ( SvtCTLOptions::NUMERALS_ARABIC == eCTLTextNumerals )
737 eLang = LANGUAGE_ENGLISH;
738 else if ( SvtCTLOptions::NUMERALS_SYSTEM == eCTLTextNumerals )
739 eLang = ::GetAppLanguage();
742 const_cast<OutputDevice&>(rOut).SetDigitLanguage( eLang );
745 SwDigitModeModifier::~SwDigitModeModifier()
747 const_cast<OutputDevice&>(rOut).SetDigitLanguage( nOldLanguageType );
750 void SwTextFrame::Init()
752 OSL_ENSURE( !IsLocked(), "+SwTextFrame::Init: this is locked." );
753 if( !IsLocked() )
755 ClearPara();
756 SetHasRotatedPortions(false);
757 // set flags directly to save a ResetPreps call,
758 // and thereby an unnecessary GetPara call
759 // don't set bOrphan, bLocked or bWait to false!
760 // bOrphan = bFlag7 = bFlag8 = false;
764 SwTextFrame::SwTextFrame(SwTextNode * const pNode, SwFrame* pSib,
765 sw::FrameMode const eMode)
766 : SwContentFrame( pNode, pSib )
767 , mnAllLines( 0 )
768 , mnThisLines( 0 )
769 , mnFlyAnchorOfst( 0 )
770 , mnFlyAnchorOfstNoWrap( 0 )
771 , mnFlyAnchorVertOfstNoWrap( 0 )
772 , mnFootnoteLine( 0 )
773 , mnHeightOfLastLine( 0 )
774 , mnAdditionalFirstLineOffset( 0 )
775 , mnOffset( 0 )
776 , mnCacheIndex( USHRT_MAX )
777 , mbLocked( false )
778 , mbWidow( false )
779 , mbJustWidow( false )
780 , mbEmpty( false )
781 , mbInFootnoteConnect( false )
782 , mbFootnote( false )
783 , mbRepaint( false )
784 , mbHasRotatedPortions( false )
785 , mbFieldFollow( false )
786 , mbHasAnimation( false )
787 , mbIsSwapped( false )
788 , mbFollowFormatAllowed( true )
790 mnFrameType = SwFrameType::Txt;
791 // note: this may call SwClientNotify if it's in a list so do it last
792 // note: this may change this->pRegisteredIn to m_pMergedPara->listeners
793 m_pMergedPara = CheckParaRedlineMerge(*this, *pNode, eMode);
796 void SwTextFrame::dumpAsXmlAttributes(xmlTextWriterPtr writer) const
798 SwContentFrame::dumpAsXmlAttributes(writer);
800 const SwTextNode *pTextNode = GetTextNodeFirst();
801 (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "txtNodeIndex" ), "%" SAL_PRIdINT32, sal_Int32(pTextNode->GetIndex()) );
803 OString aMode = "Horizontal"_ostr;
804 if (IsVertLRBT())
806 aMode = "VertBTLR"_ostr;
808 else if (IsVertLR())
810 aMode = "VertLR"_ostr;
812 else if (IsVertical())
814 aMode = "Vertical"_ostr;
816 (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("WritingMode"), BAD_CAST(aMode.getStr()));
819 void SwTextFrame::dumpAsXml(xmlTextWriterPtr writer) const
821 (void)xmlTextWriterStartElement(writer, reinterpret_cast<const xmlChar*>("txt"));
822 dumpAsXmlAttributes( writer );
823 if ( HasFollow() )
824 (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "follow" ), "%" SAL_PRIuUINT32, GetFollow()->GetFrameId() );
826 if (m_pPrecede != nullptr)
827 (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "precede" ), "%" SAL_PRIuUINT32, static_cast<SwTextFrame*>(m_pPrecede)->GetFrameId() );
829 (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("offset"), BAD_CAST(OString::number(static_cast<sal_Int32>(mnOffset)).getStr()));
831 sw::MergedPara const*const pMerged(GetMergedPara());
832 if (pMerged)
834 (void)xmlTextWriterStartElement( writer, BAD_CAST( "merged" ) );
835 (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "paraPropsNodeIndex" ), "%" SAL_PRIdINT32, sal_Int32(pMerged->pParaPropsNode->GetIndex()) );
836 for (auto const& e : pMerged->extents)
838 (void)xmlTextWriterStartElement( writer, BAD_CAST( "extent" ) );
839 (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "txtNodeIndex" ), "%" SAL_PRIdINT32, sal_Int32(e.pNode->GetIndex()) );
840 (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "start" ), "%" SAL_PRIdINT32, e.nStart );
841 (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "end" ), "%" SAL_PRIdINT32, e.nEnd );
842 (void)xmlTextWriterEndElement( writer );
844 (void)xmlTextWriterEndElement( writer );
847 (void)xmlTextWriterStartElement(writer, BAD_CAST("infos"));
848 dumpInfosAsXml(writer);
849 (void)xmlTextWriterEndElement(writer);
851 // Dump Anchored objects if any
852 const SwSortedObjs* pAnchored = GetDrawObjs();
853 if ( pAnchored && pAnchored->size() > 0 )
855 (void)xmlTextWriterStartElement( writer, BAD_CAST( "anchored" ) );
857 for (SwAnchoredObject* pObject : *pAnchored)
859 pObject->dumpAsXml( writer );
862 (void)xmlTextWriterEndElement( writer );
865 // Dump the children
866 OUString aText = GetText( );
867 for ( int i = 0; i < 32; i++ )
869 aText = aText.replace( i, '*' );
871 auto nTextOffset = static_cast<sal_Int32>(GetOffset());
872 sal_Int32 nTextLength = aText.getLength() - nTextOffset;
873 if (const SwTextFrame* pTextFrameFollow = GetFollow())
875 nTextLength = static_cast<sal_Int32>(pTextFrameFollow->GetOffset() - GetOffset());
877 if (nTextLength > 0)
879 OString aText8
880 = OUStringToOString(aText.subView(nTextOffset, nTextLength), RTL_TEXTENCODING_UTF8);
881 (void)xmlTextWriterWriteString( writer,
882 reinterpret_cast<const xmlChar *>(aText8.getStr( )) );
884 if (const SwParaPortion* pPara = GetPara())
886 (void)xmlTextWriterStartElement(writer, BAD_CAST("SwParaPortion"));
887 TextFrameIndex nOffset(0);
888 const OUString& rText = GetText();
889 (void)xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("ptr"), "%p", pPara);
890 const SwLineLayout* pLine = pPara;
891 if (IsFollow())
893 nOffset += GetOffset();
895 while (pLine)
897 (void)xmlTextWriterStartElement(writer, BAD_CAST("SwLineLayout"));
898 pLine->dumpAsXmlAttributes(writer, rText, nOffset);
899 const SwLinePortion* pPor = pLine->GetFirstPortion();
900 while (pPor)
902 pPor->dumpAsXml(writer, rText, nOffset);
903 pPor = pPor->GetNextPortion();
905 (void)xmlTextWriterEndElement(writer);
906 pLine = pLine->GetNext();
908 (void)xmlTextWriterEndElement(writer);
911 (void)xmlTextWriterEndElement(writer);
914 namespace sw {
916 SwTextFrame * MakeTextFrame(SwTextNode & rNode, SwFrame *const pSibling,
917 sw::FrameMode const eMode)
919 return new SwTextFrame(&rNode, pSibling, eMode);
922 void RemoveFootnotesForNode(
923 SwRootFrame const& rLayout, SwTextNode const& rTextNode,
924 std::vector<std::pair<sal_Int32, sal_Int32>> const*const pExtents)
926 if (pExtents && pExtents->empty())
928 return; // nothing to do
930 const SwFootnoteIdxs &rFootnoteIdxs = rTextNode.GetDoc().GetFootnoteIdxs();
931 size_t nPos = 0;
932 SwNodeOffset const nIndex = rTextNode.GetIndex();
933 rFootnoteIdxs.SeekEntry( rTextNode, &nPos );
934 if (nPos < rFootnoteIdxs.size())
936 while (nPos > 0 && rTextNode == (rFootnoteIdxs[ nPos ]->GetTextNode()))
937 --nPos;
938 if (nPos || rTextNode != (rFootnoteIdxs[ nPos ]->GetTextNode()))
939 ++nPos;
941 size_t iter(0);
942 for ( ; nPos < rFootnoteIdxs.size(); ++nPos)
944 SwTextFootnote* pTextFootnote = rFootnoteIdxs[ nPos ];
945 if (pTextFootnote->GetTextNode().GetIndex() > nIndex)
946 break;
947 if (pExtents)
949 while ((*pExtents)[iter].second <= pTextFootnote->GetStart())
951 ++iter;
952 if (iter == pExtents->size())
954 return;
957 if (pTextFootnote->GetStart() < (*pExtents)[iter].first)
959 continue;
962 pTextFootnote->DelFrames(&rLayout);
966 } // namespace sw
968 void SwTextFrame::DestroyImpl()
970 // Remove associated SwParaPortion from s_pTextCache
971 ClearPara();
973 assert(!GetDoc().IsInDtor()); // this shouldn't be happening with ViewShell owning layout
974 if (!GetDoc().IsInDtor() && HasFootnote())
976 if (m_pMergedPara)
978 SwTextNode const* pNode(nullptr);
979 for (auto const& e : m_pMergedPara->extents)
981 if (e.pNode != pNode)
983 pNode = e.pNode;
984 // sw_redlinehide: not sure if it's necessary to check
985 // if the nodes are still alive here, which would require
986 // accessing WriterMultiListener::m_vDepends
987 sw::RemoveFootnotesForNode(*getRootFrame(), *pNode, nullptr);
991 else
993 SwTextNode *const pNode(static_cast<SwTextNode*>(GetDep()));
994 if (pNode)
996 sw::RemoveFootnotesForNode(*getRootFrame(), *pNode, nullptr);
1001 if (!GetDoc().IsInDtor())
1003 if (SwView* pView = GetActiveView())
1004 pView->GetEditWin().GetFrameControlsManager().RemoveControls(this);
1007 SwContentFrame::DestroyImpl();
1010 SwTextFrame::~SwTextFrame()
1012 RemoveFromCache();
1015 namespace sw {
1017 // 1. if real insert => correct nStart/nEnd for full nLen
1018 // 2. if rl un-delete => do not correct nStart/nEnd but just include un-deleted
1019 static TextFrameIndex UpdateMergedParaForInsert(MergedPara & rMerged,
1020 bool const isRealInsert,
1021 SwTextNode const& rNode, sal_Int32 const nIndex, sal_Int32 const nLen)
1023 assert(!isRealInsert || nLen); // can 0 happen? yes, for redline in empty node
1024 assert(nIndex <= rNode.Len());
1025 assert(nIndex + nLen <= rNode.Len());
1026 assert(rMerged.pFirstNode->GetIndex() <= rNode.GetIndex() && rNode.GetIndex() <= rMerged.pLastNode->GetIndex());
1027 if (!nLen)
1029 return TextFrameIndex(0);
1031 OUStringBuffer text(rMerged.mergedText);
1032 sal_Int32 nTFIndex(0); // index used for insertion at the end
1033 sal_Int32 nInserted(0);
1034 bool bInserted(false);
1035 bool bFoundNode(false);
1036 auto itInsert(rMerged.extents.end());
1037 for (auto it = rMerged.extents.begin(); it != rMerged.extents.end(); ++it)
1039 if (it->pNode == &rNode)
1041 if (isRealInsert)
1043 bFoundNode = true;
1044 if (it->nStart <= nIndex && nIndex <= it->nEnd)
1045 { // note: this can happen only once
1046 text.insert(nTFIndex + (nIndex - it->nStart),
1047 rNode.GetText().subView(nIndex, nLen));
1048 it->nEnd += nLen;
1049 nInserted = nLen;
1050 assert(!bInserted);
1051 bInserted = true;
1053 else if (nIndex < it->nStart)
1055 if (itInsert == rMerged.extents.end())
1057 itInsert = it;
1059 it->nStart += nLen;
1060 it->nEnd += nLen;
1063 else
1065 assert(it == rMerged.extents.begin() || (it-1)->pNode != &rNode || (it-1)->nEnd < nIndex);
1066 if (nIndex + nLen < it->nStart)
1068 itInsert = it;
1069 break;
1071 if (nIndex < it->nStart)
1073 text.insert(nTFIndex,
1074 rNode.GetText().subView(nIndex, it->nStart - nIndex));
1075 nInserted += it->nStart - nIndex;
1076 it->nStart = nIndex;
1077 bInserted = true;
1079 assert(it->nStart <= nIndex);
1080 if (nIndex <= it->nEnd)
1082 nTFIndex += it->nEnd - it->nStart;
1083 while (it->nEnd < nIndex + nLen)
1085 auto *const pNext(
1086 (it+1) != rMerged.extents.end() && (it+1)->pNode == it->pNode
1087 ? &*(it+1)
1088 : nullptr);
1089 if (pNext && pNext->nStart <= nIndex + nLen)
1091 text.insert(nTFIndex,
1092 rNode.GetText().subView(it->nEnd, pNext->nStart - it->nEnd));
1093 nTFIndex += pNext->nStart - it->nEnd;
1094 nInserted += pNext->nStart - it->nEnd;
1095 pNext->nStart = it->nStart;
1096 it = rMerged.extents.erase(it);
1098 else
1100 text.insert(nTFIndex,
1101 rNode.GetText().subView(it->nEnd, nIndex + nLen - it->nEnd));
1102 nTFIndex += nIndex + nLen - it->nEnd;
1103 nInserted += nIndex + nLen - it->nEnd;
1104 it->nEnd = nIndex + nLen;
1107 bInserted = true;
1108 break;
1112 else if (rNode.GetIndex() < it->pNode->GetIndex() || bFoundNode)
1114 if (itInsert == rMerged.extents.end())
1116 itInsert = it;
1118 break;
1120 if (itInsert == rMerged.extents.end())
1122 nTFIndex += it->nEnd - it->nStart;
1125 // assert((bFoundNode || rMerged.extents.empty()) && "text node not found - why is it sending hints to us");
1126 if (!bInserted)
1127 { // must be in a gap
1128 rMerged.extents.emplace(itInsert, const_cast<SwTextNode*>(&rNode), nIndex, nIndex + nLen);
1129 text.insert(nTFIndex, rNode.GetText().subView(nIndex, nLen));
1130 nInserted = nLen;
1131 if (rMerged.extents.size() == 1 // also if it was empty!
1132 || rMerged.pParaPropsNode->GetIndex() < rNode.GetIndex())
1133 { // text inserted after current para-props node
1134 rMerged.pParaPropsNode->RemoveFromListRLHidden();
1135 rMerged.pParaPropsNode = &const_cast<SwTextNode&>(rNode);
1136 rMerged.pParaPropsNode->AddToListRLHidden();
1138 // called from SwRangeRedline::InvalidateRange()
1139 if (rNode.GetRedlineMergeFlag() == SwNode::Merge::Hidden)
1141 const_cast<SwTextNode&>(rNode).SetRedlineMergeFlag(SwNode::Merge::NonFirst);
1144 rMerged.mergedText = text.makeStringAndClear();
1145 return TextFrameIndex(nInserted);
1148 // 1. if real delete => correct nStart/nEnd for full nLen
1149 // 2. if rl delete => do not correct nStart/nEnd but just exclude deleted
1150 TextFrameIndex UpdateMergedParaForDelete(MergedPara & rMerged,
1151 bool const isRealDelete,
1152 SwTextNode const& rNode, sal_Int32 nIndex, sal_Int32 const nLen)
1154 assert(nIndex <= rNode.Len());
1155 assert(rMerged.pFirstNode->GetIndex() <= rNode.GetIndex() && rNode.GetIndex() <= rMerged.pLastNode->GetIndex());
1156 OUStringBuffer text(rMerged.mergedText);
1157 sal_Int32 nTFIndex(0);
1158 sal_Int32 nToDelete(nLen);
1159 sal_Int32 nDeleted(0);
1160 size_t nFoundNode(0);
1161 size_t nErased(0);
1162 auto it = rMerged.extents.begin();
1163 for (; it != rMerged.extents.end(); )
1165 bool bErase(false);
1166 if (it->pNode == &rNode)
1168 ++nFoundNode;
1169 if (nIndex + nToDelete < it->nStart)
1171 nToDelete = 0;
1172 if (!isRealDelete)
1174 break;
1176 it->nStart -= nLen;
1177 it->nEnd -= nLen;
1179 else
1181 if (nIndex < it->nStart)
1183 // do not adjust nIndex into the text frame index space!
1184 nToDelete -= it->nStart - nIndex;
1185 nIndex = it->nStart;
1186 // note: continue with the if check below, no else!
1188 if (it->nStart <= nIndex && nIndex < it->nEnd)
1190 sal_Int32 const nDeleteHere(nIndex + nToDelete <= it->nEnd
1191 ? nToDelete
1192 : it->nEnd - nIndex);
1193 text.remove(nTFIndex + (nIndex - it->nStart), nDeleteHere);
1194 bErase = nDeleteHere == it->nEnd - it->nStart;
1195 if (bErase)
1197 ++nErased;
1198 assert(it->nStart == nIndex);
1199 it = rMerged.extents.erase(it);
1201 else if (isRealDelete)
1202 { // adjust for deleted text
1203 it->nStart -= (nLen - nToDelete);
1204 it->nEnd -= (nLen - nToDelete + nDeleteHere);
1205 if (it != rMerged.extents.begin()
1206 && (it-1)->pNode == &rNode
1207 && (it-1)->nEnd == it->nStart)
1208 { // merge adjacent extents
1209 nTFIndex += it->nEnd - it->nStart;
1210 (it-1)->nEnd = it->nEnd;
1211 it = rMerged.extents.erase(it);
1212 bErase = true; // skip increment
1215 else
1216 { // exclude text marked as deleted
1217 if (nIndex + nDeleteHere == it->nEnd)
1219 it->nEnd -= nDeleteHere;
1221 else
1223 if (nIndex == it->nStart)
1225 it->nStart += nDeleteHere;
1227 else
1229 sal_Int32 const nOldEnd(it->nEnd);
1230 it->nEnd = nIndex;
1231 it = rMerged.extents.emplace(it+1,
1232 it->pNode, nIndex + nDeleteHere, nOldEnd);
1234 assert(nDeleteHere == nToDelete);
1237 nDeleted += nDeleteHere;
1238 nToDelete -= nDeleteHere;
1239 nIndex += nDeleteHere;
1240 if (!isRealDelete && nToDelete == 0)
1242 break;
1247 else if (nFoundNode != 0)
1249 break;
1251 if (!bErase)
1253 nTFIndex += it->nEnd - it->nStart;
1254 ++it;
1257 // assert(nFoundNode != 0 && "text node not found - why is it sending hints to us");
1258 assert(nIndex <= rNode.Len() + nLen);
1259 // if there's a remaining deletion, it must be in gap at the end of the node
1260 // can't do: might be last one in node was erased assert(nLen == 0 || rMerged.empty() || (it-1)->nEnd <= nIndex);
1261 // note: if first node gets deleted then that must call DelFrames as
1262 // pFirstNode is never updated
1263 if (nErased && nErased == nFoundNode)
1264 { // all visible text from node was erased
1265 #if 1
1266 if (rMerged.pParaPropsNode == &rNode)
1268 rMerged.pParaPropsNode->RemoveFromListRLHidden();
1269 rMerged.pParaPropsNode = rMerged.extents.empty()
1270 ? const_cast<SwTextNode*>(rMerged.pLastNode)
1271 : rMerged.extents.front().pNode;
1272 rMerged.pParaPropsNode->AddToListRLHidden();
1274 #endif
1275 // NOPE must listen on all non-hidden nodes; particularly on pLastNode rMerged.listener.EndListening(&const_cast<SwTextNode&>(rNode));
1277 rMerged.mergedText = text.makeStringAndClear();
1278 return TextFrameIndex(nDeleted);
1281 std::pair<SwTextNode*, sal_Int32>
1282 MapViewToModel(MergedPara const& rMerged, TextFrameIndex const i_nIndex)
1284 sal_Int32 nIndex(i_nIndex);
1285 sw::Extent const* pExtent(nullptr);
1286 for (const auto& rExt : rMerged.extents)
1288 pExtent = &rExt;
1289 if (nIndex < (pExtent->nEnd - pExtent->nStart))
1291 return std::make_pair(pExtent->pNode, pExtent->nStart + nIndex);
1293 nIndex = nIndex - (pExtent->nEnd - pExtent->nStart);
1295 assert(nIndex == 0 && "view index out of bounds");
1296 return pExtent
1297 ? std::make_pair(pExtent->pNode, pExtent->nEnd) //1-past-the-end index
1298 : std::make_pair(const_cast<SwTextNode*>(rMerged.pLastNode), rMerged.pLastNode->Len());
1301 TextFrameIndex MapModelToView(MergedPara const& rMerged, SwTextNode const*const pNode, sal_Int32 const nIndex)
1303 assert(rMerged.pFirstNode->GetIndex() <= pNode->GetIndex()
1304 && pNode->GetIndex() <= rMerged.pLastNode->GetIndex());
1305 sal_Int32 nRet(0);
1306 bool bFoundNode(false);
1307 for (auto const& e : rMerged.extents)
1309 if (pNode->GetIndex() < e.pNode->GetIndex())
1311 return TextFrameIndex(nRet);
1313 if (e.pNode == pNode)
1315 if (e.nStart <= nIndex && nIndex <= e.nEnd)
1317 return TextFrameIndex(nRet + (nIndex - e.nStart));
1319 else if (nIndex < e.nStart)
1321 // in gap before this extent => map to 0 here TODO???
1322 return TextFrameIndex(nRet);
1324 bFoundNode = true;
1326 else if (bFoundNode)
1328 break;
1330 nRet += e.nEnd - e.nStart;
1332 if (bFoundNode)
1334 // must be in a gap at the end of the node
1335 assert(nIndex <= pNode->Len());
1336 return TextFrameIndex(nRet);
1338 else if (rMerged.extents.empty())
1340 assert(nIndex <= pNode->Len());
1341 return TextFrameIndex(0);
1343 return TextFrameIndex(rMerged.mergedText.getLength());
1346 } // namespace sw
1348 std::pair<SwTextNode*, sal_Int32>
1349 SwTextFrame::MapViewToModel(TextFrameIndex const nIndex) const
1351 //nope assert(GetPara());
1352 sw::MergedPara const*const pMerged(GetMergedPara());
1353 if (pMerged)
1355 return sw::MapViewToModel(*pMerged, nIndex);
1357 else
1359 return std::make_pair(static_cast<SwTextNode*>(const_cast<sw::BroadcastingModify*>(
1360 SwFrame::GetDep())), sal_Int32(nIndex));
1364 SwPosition SwTextFrame::MapViewToModelPos(TextFrameIndex const nIndex) const
1366 std::pair<SwTextNode*, sal_Int32> const ret(MapViewToModel(nIndex));
1367 return SwPosition(*ret.first, ret.second);
1370 TextFrameIndex SwTextFrame::MapModelToView(SwTextNode const*const pNode, sal_Int32 const nIndex) const
1372 //nope assert(GetPara());
1373 sw::MergedPara const*const pMerged(GetMergedPara());
1374 if (pMerged)
1376 return sw::MapModelToView(*pMerged, pNode, nIndex);
1378 else
1380 return TextFrameIndex(nIndex);
1384 TextFrameIndex SwTextFrame::MapModelToViewPos(SwPosition const& rPos) const
1386 SwTextNode const*const pNode(rPos.GetNode().GetTextNode());
1387 sal_Int32 const nIndex(rPos.GetContentIndex());
1388 return MapModelToView(pNode, nIndex);
1391 void SwTextFrame::SetMergedPara(std::unique_ptr<sw::MergedPara> p)
1393 SwTextNode *const pFirst(m_pMergedPara ? m_pMergedPara->pFirstNode : nullptr);
1394 m_pMergedPara = std::move(p);
1395 if (pFirst)
1397 if (m_pMergedPara)
1399 assert(pFirst == m_pMergedPara->pFirstNode);
1401 else
1403 pFirst->Add(*this); // must register at node again
1406 // postcondition: frame must be listening somewhere
1407 assert(m_pMergedPara || GetDep());
1410 const OUString& SwTextFrame::GetText() const
1412 //nope assert(GetPara());
1413 sw::MergedPara const*const pMerged(GetMergedPara());
1414 if (pMerged)
1415 return pMerged->mergedText;
1416 else
1417 return static_cast<SwTextNode const*>(SwFrame::GetDep())->GetText();
1420 SwTextNode const* SwTextFrame::GetTextNodeForParaProps() const
1422 // FIXME can GetPara be 0 ? yes... this is needed in SwContentNotify::SwContentNotify() which is called before any formatting is started
1423 //nope assert(GetPara());
1424 sw::MergedPara const*const pMerged(GetMergedPara());
1425 if (pMerged)
1427 // assert(pMerged->pFirstNode == pMerged->pParaPropsNode); // surprising news!
1428 return pMerged->pParaPropsNode;
1430 else
1431 return static_cast<SwTextNode const*>(SwFrame::GetDep());
1434 SwTextNode const* SwTextFrame::GetTextNodeForFirstText() const
1436 sw::MergedPara const*const pMerged(GetMergedPara());
1437 if (pMerged)
1438 return pMerged->extents.empty()
1439 ? pMerged->pFirstNode
1440 : pMerged->extents.front().pNode;
1441 else
1442 return static_cast<SwTextNode const*>(SwFrame::GetDep());
1445 SwTextNode const* SwTextFrame::GetTextNodeFirst() const
1447 //nope assert(GetPara());
1448 sw::MergedPara const*const pMerged(GetMergedPara());
1449 if (pMerged)
1450 return pMerged->pFirstNode;
1451 else
1452 return static_cast<SwTextNode const*>(SwFrame::GetDep());
1455 SwDoc const& SwTextFrame::GetDoc() const
1457 return GetTextNodeFirst()->GetDoc();
1460 LanguageType SwTextFrame::GetLangOfChar(TextFrameIndex const nIndex,
1461 sal_uInt16 const nScript, bool const bNoChar, bool const bNoneIfNoHyphenation) const
1463 // a single character can be mapped uniquely!
1464 std::pair<SwTextNode const*, sal_Int32> const pos(MapViewToModel(nIndex));
1465 return pos.first->GetLang(pos.second, bNoChar ? 0 : 1, nScript, bNoneIfNoHyphenation);
1468 void SwTextFrame::ResetPreps()
1470 if ( GetCacheIdx() != USHRT_MAX )
1472 if (SwParaPortion *pPara = GetPara())
1473 pPara->ResetPreps();
1477 static auto FindCellFrame(SwFrame const* pLower) -> SwLayoutFrame const*
1479 while (pLower)
1481 if (pLower->IsCellFrame())
1483 return static_cast<SwLayoutFrame const*>(pLower);
1485 pLower = pLower->GetUpper();
1487 return nullptr;
1490 bool SwTextFrame::IsHiddenNow() const
1492 SwFrameSwapper aSwapper( this, true );
1494 if( !getFrameArea().Width() && isFrameAreaDefinitionValid() && GetUpper()->isFrameAreaDefinitionValid() ) // invalid when stack overflows (StackHack)!
1496 // OSL_FAIL( "SwTextFrame::IsHiddenNow: thin frame" );
1497 return true;
1500 // TODO: what is the above check good for and can it be removed?
1501 return IsHiddenNowImpl();
1504 bool SwTextFrame::IsHiddenNowImpl() const
1506 if (SwContentFrame::IsHiddenNow())
1507 return true;
1509 bool bHiddenCharsHidePara(false);
1510 bool bHiddenParaField(false);
1511 if (m_pMergedPara)
1513 TextFrameIndex nHiddenStart(COMPLETE_STRING);
1514 TextFrameIndex nHiddenEnd(0);
1515 if (auto const pScriptInfo = GetScriptInfo())
1517 pScriptInfo->GetBoundsOfHiddenRange(TextFrameIndex(0),
1518 nHiddenStart, nHiddenEnd);
1520 else // ParaPortion is created in Format, but this is called earlier
1522 SwScriptInfo aInfo;
1523 aInfo.InitScriptInfo(*m_pMergedPara->pFirstNode, m_pMergedPara.get(), IsRightToLeft());
1524 aInfo.GetBoundsOfHiddenRange(TextFrameIndex(0),
1525 nHiddenStart, nHiddenEnd);
1527 if (TextFrameIndex(0) == nHiddenStart &&
1528 TextFrameIndex(GetText().getLength()) <= nHiddenEnd)
1530 bHiddenCharsHidePara = true;
1532 sw::MergedAttrIter iter(*this);
1533 SwTextNode const* pNode(nullptr);
1534 int nNewResultWeight = 0;
1535 for (SwTextAttr const* pHint = iter.NextAttr(&pNode); pHint; pHint = iter.NextAttr(&pNode))
1537 if (pHint->Which() == RES_TXTATR_FIELD)
1539 // see also SwpHints::CalcHiddenParaField()
1540 const SwFormatField& rField = pHint->GetFormatField();
1541 int nCurWeight = pNode->GetDoc().FieldCanHideParaWeight(rField.GetField()->GetTyp()->Which());
1542 if (nCurWeight > nNewResultWeight)
1544 nNewResultWeight = nCurWeight;
1545 bHiddenParaField = pNode->GetDoc().FieldHidesPara(*rField.GetField());
1547 else if (nCurWeight == nNewResultWeight && bHiddenParaField)
1549 // Currently, for both supported hiding types (HiddenPara, Database), "Don't hide"
1550 // takes precedence - i.e., if there's a "Don't hide" field of that weight, we only
1551 // care about fields of higher weight.
1552 bHiddenParaField = pNode->GetDoc().FieldHidesPara(*rField.GetField());
1557 else
1559 bHiddenCharsHidePara = static_cast<SwTextNode const*>(SwFrame::GetDep())->HasHiddenCharAttribute( true );
1560 bHiddenParaField = static_cast<SwTextNode const*>(SwFrame::GetDep())->IsHiddenByParaField();
1562 if (bHiddenCharsHidePara && GetDoc().getIDocumentSettingAccess().get(
1563 DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH))
1565 // apparently in Word it's always the last para marker that determines hidden?
1566 // even in case when they are merged by delete redline (it's obvious when they are merged by hidden-attribute
1567 SwTextNode const*const pNode{ m_pMergedPara
1568 ? m_pMergedPara->pLastNode
1569 : static_cast<SwTextNode const*>(SwFrame::GetDep()) };
1570 // Word ignores hidden formatting on the cell end marker
1571 bool isLastInCell{false};
1572 if (SwLayoutFrame const*const pCellFrame{FindCellFrame(this)})
1574 SwContentFrame const* pNext{GetNextContentFrame()};
1575 // skip frame in hidden section ("this" is *not* in hidden section!)
1576 while (pNext && pNext->SwContentFrame::IsHiddenNow())
1578 pNext = pNext->GetNextContentFrame();
1580 isLastInCell = pNext == nullptr || !pCellFrame->IsAnLower(pNext);
1582 if (!isLastInCell)
1584 SwFormatAutoFormat const& rListAutoFormat{pNode->GetAttr(RES_PARATR_LIST_AUTOFMT)};
1585 std::shared_ptr<SfxItemSet> const pSet{rListAutoFormat.GetStyleHandle()};
1586 SvxCharHiddenItem const* pItem{pSet ? pSet->GetItemIfSet(RES_CHRATR_HIDDEN) : nullptr};
1587 if (!pItem)
1589 // don't use node's mpAttrSet, it doesn't apply to para marker
1590 SwFormatColl const*const pStyle{pNode->GetFormatColl()};
1591 if (pStyle)
1593 pItem = &pStyle->GetFormatAttr(RES_CHRATR_HIDDEN);
1596 if (!pItem || !pItem->GetValue())
1598 bHiddenCharsHidePara = false;
1602 const SwViewShell* pVsh = getRootFrame()->GetCurrShell();
1604 if ( pVsh && ( bHiddenCharsHidePara || bHiddenParaField ) )
1607 if (
1608 ( bHiddenParaField &&
1609 ( !pVsh->GetViewOptions()->IsShowHiddenPara() &&
1610 !pVsh->GetViewOptions()->IsFieldName() ) ) ||
1611 ( bHiddenCharsHidePara &&
1612 !pVsh->GetViewOptions()->IsShowHiddenChar() ) )
1614 // in order to put the cursor in the body text, one paragraph must
1615 // be visible - check this for the 1st body paragraph
1616 if (IsInDocBody() && FindPrevCnt() == nullptr)
1618 for (SwContentFrame const* pNext = FindNextCnt(true);
1619 pNext != nullptr; pNext = pNext->FindNextCnt(true))
1621 if (!pNext->IsHiddenNow())
1622 return true;
1624 SAL_INFO("sw.core", "unhiding one body paragraph");
1625 return false;
1627 return true;
1631 return false;
1634 /// Removes Textfrm's attachments, when it's hidden
1635 void SwTextFrame::HideHidden()
1637 OSL_ENSURE( !GetFollow() && IsHiddenNow(),
1638 "HideHidden on visible frame of hidden frame has follow" );
1640 HideFootnotes(GetOffset(), TextFrameIndex(COMPLETE_STRING));
1641 HideAndShowObjects();
1643 // format information is obsolete
1644 ClearPara();
1647 void SwTextFrame::HideFootnotes(TextFrameIndex const nStart, TextFrameIndex const nEnd)
1649 SwPageFrame *pPage = nullptr;
1650 sw::MergedAttrIter iter(*this);
1651 SwTextNode const* pNode(nullptr);
1652 for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode))
1654 if (pHt->Which() == RES_TXTATR_FTN)
1656 TextFrameIndex const nIdx(MapModelToView(pNode, pHt->GetStart()));
1657 if (nEnd < nIdx)
1658 break;
1659 if (nStart <= nIdx)
1661 if (!pPage)
1662 pPage = FindPageFrame();
1663 pPage->RemoveFootnote( this, static_cast<const SwTextFootnote*>(pHt) );
1670 * as-character anchored graphics, which are used for a graphic bullet list.
1671 * As long as these graphic bullet list aren't imported, do not hide a
1672 * at-character anchored object, if
1673 * (a) the document is an imported WW8 document -
1674 * checked by checking certain compatibility options -
1675 * (b) the paragraph is the last content in the document and
1676 * (c) the anchor character is an as-character anchored graphic.
1678 bool sw_HideObj( const SwTextFrame& _rFrame,
1679 const RndStdIds _eAnchorType,
1680 SwFormatAnchor const& rFormatAnchor,
1681 SwAnchoredObject* _pAnchoredObj )
1683 bool bRet( true );
1685 if (_eAnchorType == RndStdIds::FLY_AT_CHAR)
1687 const IDocumentSettingAccess *const pIDSA = &_rFrame.GetDoc().getIDocumentSettingAccess();
1688 if ( !pIDSA->get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) &&
1689 !pIDSA->get(DocumentSettingId::OLD_LINE_SPACING) &&
1690 !pIDSA->get(DocumentSettingId::USE_FORMER_OBJECT_POS) &&
1691 pIDSA->get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) &&
1692 _rFrame.IsInDocBody() && !_rFrame.FindNextCnt() )
1694 SwTextNode const& rNode(*rFormatAnchor.GetAnchorNode()->GetTextNode());
1695 assert(FrameContainsNode(_rFrame, rNode.GetIndex()));
1696 sal_Int32 const nObjAnchorPos(rFormatAnchor.GetAnchorContentOffset());
1697 const sal_Unicode cAnchorChar = nObjAnchorPos < rNode.Len()
1698 ? rNode.GetText()[nObjAnchorPos]
1699 : 0;
1700 if (cAnchorChar == CH_TXTATR_BREAKWORD)
1702 const SwTextAttr* const pHint(
1703 rNode.GetTextAttrForCharAt(nObjAnchorPos, RES_TXTATR_FLYCNT));
1704 if ( pHint )
1706 const SwFrameFormat* pFrameFormat =
1707 static_cast<const SwTextFlyCnt*>(pHint)->GetFlyCnt().GetFrameFormat();
1708 if ( pFrameFormat->Which() == RES_FLYFRMFMT )
1710 SwNodeIndex nContentIndex = *(pFrameFormat->GetContent().GetContentIdx());
1711 ++nContentIndex;
1712 if ( nContentIndex.GetNode().IsNoTextNode() )
1714 bRet = false;
1715 // set needed data structure values for object positioning
1716 SwRectFnSet aRectFnSet(&_rFrame);
1717 SwRect aLastCharRect( _rFrame.getFrameArea() );
1718 aRectFnSet.SetWidth( aLastCharRect, 1 );
1719 _pAnchoredObj->maLastCharRect = aLastCharRect;
1720 _pAnchoredObj->mnLastTopOfLine = aRectFnSet.GetTop(aLastCharRect);
1728 return bRet;
1732 * Hide/show objects
1734 * Method hides respectively shows objects, which are anchored at paragraph,
1735 * at/as a character of the paragraph, corresponding to the paragraph and
1736 * paragraph portion visibility.
1738 * - is called from HideHidden() - should hide objects in hidden paragraphs and
1739 * - from Format_() - should hide/show objects in partly visible paragraphs
1741 void SwTextFrame::HideAndShowObjects()
1743 if ( GetDrawObjs() )
1745 if ( IsHiddenNow() )
1747 // complete paragraph is hidden. Thus, hide all objects
1748 for (SwAnchoredObject* i : *GetDrawObjs())
1750 SdrObject* pObj = i->DrawObj();
1751 SwContact* pContact = static_cast<SwContact*>(pObj->GetUserCall());
1752 // under certain conditions
1753 const RndStdIds eAnchorType( pContact->GetAnchorId() );
1754 if ((eAnchorType != RndStdIds::FLY_AT_CHAR) ||
1755 sw_HideObj(*this, eAnchorType, pContact->GetAnchorFormat(),
1756 i ))
1758 pContact->MoveObjToInvisibleLayer( pObj );
1762 else
1764 // paragraph is visible, but can contain hidden text portion.
1765 // first we check if objects are allowed to be hidden:
1766 const SwViewShell* pVsh = getRootFrame()->GetCurrShell();
1767 const bool bShouldBeHidden = !pVsh || !pVsh->GetWin() ||
1768 !pVsh->GetViewOptions()->IsShowHiddenChar();
1770 // Thus, show all objects, which are anchored at paragraph and
1771 // hide/show objects, which are anchored at/as character, according
1772 // to the visibility of the anchor character.
1773 for (SwAnchoredObject* i : *GetDrawObjs())
1775 SdrObject* pObj = i->DrawObj();
1776 SwContact* pContact = static_cast<SwContact*>(pObj->GetUserCall());
1777 // Determine anchor type only once
1778 const RndStdIds eAnchorType( pContact->GetAnchorId() );
1780 if (eAnchorType == RndStdIds::FLY_AT_PARA)
1782 pContact->MoveObjToVisibleLayer( pObj );
1784 else if ((eAnchorType == RndStdIds::FLY_AT_CHAR) ||
1785 (eAnchorType == RndStdIds::FLY_AS_CHAR))
1787 sal_Int32 nHiddenStart;
1788 sal_Int32 nHiddenEnd;
1789 const SwFormatAnchor& rAnchorFormat = pContact->GetAnchorFormat();
1790 SwScriptInfo::GetBoundsOfHiddenRange(
1791 *rAnchorFormat.GetAnchorNode()->GetTextNode(),
1792 rAnchorFormat.GetAnchorContentOffset(), nHiddenStart, nHiddenEnd);
1793 // Under certain conditions
1794 if ( nHiddenStart != COMPLETE_STRING && bShouldBeHidden &&
1795 sw_HideObj(*this, eAnchorType, rAnchorFormat, i))
1797 pContact->MoveObjToInvisibleLayer( pObj );
1799 else
1800 pContact->MoveObjToVisibleLayer( pObj );
1802 else
1804 OSL_FAIL( "<SwTextFrame::HideAndShowObjects()> - object not anchored at/inside paragraph!?" );
1810 if (IsFollow())
1812 SwTextFrame *pMaster = FindMaster();
1813 OSL_ENSURE(pMaster, "SwTextFrame without master");
1814 if (pMaster)
1815 pMaster->HideAndShowObjects();
1819 void SwLayoutFrame::HideAndShowObjects()
1821 for (SwFrame * pLower = Lower(); pLower; pLower = pLower->GetNext())
1823 pLower->HideAndShowObjects();
1827 void SwFrame::HideAndShowObjects()
1832 * Returns the first possible break point in the current line.
1833 * This method is used in SwTextFrame::Format() to decide whether the previous
1834 * line has to be formatted as well.
1835 * nFound is <= nEndLine.
1837 TextFrameIndex SwTextFrame::FindBrk(std::u16string_view aText,
1838 const TextFrameIndex nStart,
1839 const TextFrameIndex nEnd)
1841 sal_Int32 nFound = sal_Int32(nStart);
1842 const sal_Int32 nEndLine = std::min(sal_Int32(nEnd), sal_Int32(aText.size()) - 1);
1844 // Skip all leading blanks.
1845 while( nFound <= nEndLine && ' ' == aText[nFound] )
1847 nFound++;
1850 // A tricky situation with the TextAttr-Dummy-character (in this case "$"):
1851 // "Dr.$Meyer" at the beginning of the second line. Typing a blank after that
1852 // doesn't result in the word moving into first line, even though that would work.
1853 // For this reason we don't skip the dummy char.
1854 while( nFound <= nEndLine && ' ' != aText[nFound] )
1856 nFound++;
1859 return TextFrameIndex(nFound);
1862 bool SwTextFrame::IsIdxInside(TextFrameIndex const nPos, TextFrameIndex const nLen) const
1864 if (nPos == TextFrameIndex(COMPLETE_STRING)) // the "not found" range
1865 return false;
1866 // Silence over-eager warning emitted at least by GCC trunk towards 6:
1867 #if defined __GNUC__ && !defined __clang__
1868 #pragma GCC diagnostic push
1869 #pragma GCC diagnostic ignored "-Wstrict-overflow"
1870 #endif
1871 if (nLen != TextFrameIndex(COMPLETE_STRING) && GetOffset() > nPos + nLen) // the range preceded us
1872 #if defined __GNUC__ && !defined __clang__
1873 #pragma GCC diagnostic pop
1874 #endif
1875 return false;
1877 if( !GetFollow() ) // the range doesn't precede us,
1878 return true; // nobody follows us.
1880 TextFrameIndex const nMax = GetFollow()->GetOffset();
1882 // either the range overlap or our text has been deleted
1883 // sw_redlinehide: GetText() should be okay here because it has already
1884 // been updated in the INS/DEL hint case
1885 if (nMax > nPos || nMax > TextFrameIndex(GetText().getLength()))
1886 return true;
1888 // changes made in the first line of a follow can modify the master
1889 const SwParaPortion* pPara = GetFollow()->GetPara();
1890 return pPara && ( nPos <= nMax + pPara->GetLen() );
1893 inline void SwTextFrame::InvalidateRange(const SwCharRange &aRange, const tools::Long nD)
1895 if ( IsIdxInside( aRange.Start(), aRange.Len() ) )
1896 InvalidateRange_( aRange, nD );
1899 void SwTextFrame::InvalidateRange_( const SwCharRange &aRange, const tools::Long nD)
1901 if ( !HasPara() )
1902 { InvalidateSize();
1903 return;
1906 SetWidow( false );
1907 SwParaPortion *pPara = GetPara();
1909 bool bInv = false;
1910 if( 0 != nD )
1912 // In nDelta the differences between old and new
1913 // linelengths are being added, that's why it's negative
1914 // if chars have been added and positive, if chars have
1915 // deleted
1916 pPara->SetDelta(pPara->GetDelta() + nD);
1917 bInv = true;
1919 SwCharRange &rReformat = pPara->GetReformat();
1920 if(aRange != rReformat) {
1921 if (TextFrameIndex(COMPLETE_STRING) == rReformat.Len())
1922 rReformat = aRange;
1923 else
1924 rReformat += aRange;
1925 bInv = true;
1927 if(bInv)
1929 InvalidateSize();
1933 void SwTextFrame::CalcLineSpace()
1935 OSL_ENSURE( ! IsVertical() || ! IsSwapped(),
1936 "SwTextFrame::CalcLineSpace with swapped frame!" );
1938 if( IsLocked() || !HasPara() )
1939 return;
1941 if( GetDrawObjs() ||
1942 GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent().IsAutoFirst())
1944 Init();
1945 return;
1948 SwParaPortion *const pPara(GetPara());
1949 assert(pPara);
1950 if (pPara->IsFixLineHeight())
1952 Init();
1953 return;
1956 Size aNewSize( getFramePrintArea().SSize() );
1958 SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this );
1959 SwTextFormatter aLine( this, &aInf );
1960 if( aLine.GetDropLines() )
1962 Init();
1963 return;
1966 aLine.Top();
1967 aLine.RecalcRealHeight();
1969 aNewSize.setHeight( (aLine.Y() - getFrameArea().Top()) + aLine.GetLineHeight() );
1971 SwTwips nDelta = aNewSize.Height() - getFramePrintArea().Height();
1972 // Underflow with free-flying frames
1973 if( aInf.GetTextFly().IsOn() )
1975 SwRect aTmpFrame( getFrameArea() );
1976 if( nDelta < 0 )
1977 aTmpFrame.Height( getFramePrintArea().Height() );
1978 else
1979 aTmpFrame.Height( aNewSize.Height() );
1980 if( aInf.GetTextFly().Relax( aTmpFrame ) )
1982 Init();
1983 return;
1987 if( !nDelta )
1988 return;
1990 SwTextFrameBreak aBreak( this );
1991 if( GetFollow() || aBreak.IsBreakNow( aLine ) )
1993 // if there is a Follow() or if we need to break here, reformat
1994 Init();
1996 else
1998 // everything is business as usual...
1999 pPara->SetPrepAdjust();
2000 pPara->SetPrep();
2004 static void lcl_SetWrong( SwTextFrame& rFrame, SwTextNode const& rNode,
2005 sal_Int32 const nPos, sal_Int32 const nCnt, bool const bMove)
2007 if ( !rFrame.IsFollow() )
2009 SwTextNode& rTextNode = const_cast<SwTextNode&>(rNode);
2010 sw::GrammarContact* pGrammarContact = sw::getGrammarContactFor(rTextNode);
2011 SwGrammarMarkUp* pWrongGrammar = pGrammarContact ?
2012 pGrammarContact->getGrammarCheck( rTextNode, false ) :
2013 rTextNode.GetGrammarCheck();
2014 bool bGrammarProxy = pWrongGrammar != rTextNode.GetGrammarCheck();
2015 if( bMove )
2017 if( rTextNode.GetWrong() )
2018 rTextNode.GetWrong()->Move( nPos, nCnt );
2019 if( pWrongGrammar )
2020 pWrongGrammar->MoveGrammar( nPos, nCnt );
2021 if( bGrammarProxy && rTextNode.GetGrammarCheck() )
2022 rTextNode.GetGrammarCheck()->MoveGrammar( nPos, nCnt );
2023 if( rTextNode.GetSmartTags() )
2024 rTextNode.GetSmartTags()->Move( nPos, nCnt );
2026 else
2028 if( rTextNode.GetWrong() )
2029 rTextNode.GetWrong()->Invalidate( nPos, nCnt );
2030 if( pWrongGrammar )
2031 pWrongGrammar->Invalidate( nPos, nCnt );
2032 if( rTextNode.GetSmartTags() )
2033 rTextNode.GetSmartTags()->Invalidate( nPos, nCnt );
2035 const sal_Int32 nEnd = nPos + (nCnt > 0 ? nCnt : 1 );
2036 if ( !rTextNode.GetWrong() && !rTextNode.IsWrongDirty() )
2038 rTextNode.SetWrong( std::make_unique<SwWrongList>( WRONGLIST_SPELL ) );
2039 rTextNode.GetWrong()->SetInvalid( nPos, nEnd );
2041 if ( !rTextNode.GetSmartTags() && !rTextNode.IsSmartTagDirty() )
2043 rTextNode.SetSmartTags( std::make_unique<SwWrongList>( WRONGLIST_SMARTTAG ) );
2044 rTextNode.GetSmartTags()->SetInvalid( nPos, nEnd );
2046 rTextNode.SetWrongDirty(sw::WrongState::TODO);
2047 rTextNode.SetGrammarCheckDirty( true );
2048 rTextNode.SetWordCountDirty( true );
2049 rTextNode.SetAutoCompleteWordDirty( true );
2050 rTextNode.SetSmartTagDirty( true );
2053 SwRootFrame *pRootFrame = rFrame.getRootFrame();
2054 if (pRootFrame)
2056 pRootFrame->SetNeedGrammarCheck( true );
2059 SwPageFrame *pPage = rFrame.FindPageFrame();
2060 if( pPage )
2062 pPage->InvalidateSpelling();
2063 pPage->InvalidateAutoCompleteWords();
2064 pPage->InvalidateWordCount();
2065 pPage->InvalidateSmartTags();
2069 static void lcl_SetScriptInval(SwTextFrame& rFrame, TextFrameIndex const nPos)
2071 if( rFrame.GetPara() )
2072 rFrame.GetPara()->GetScriptInfo().SetInvalidityA( nPos );
2075 // note: SwClientNotify will be called once for every frame => just fix own Ofst
2076 static void lcl_ModifyOfst(SwTextFrame & rFrame,
2077 TextFrameIndex const nPos, TextFrameIndex const nLen,
2078 TextFrameIndex (* op)(TextFrameIndex const&, TextFrameIndex const&))
2080 assert(nLen != TextFrameIndex(COMPLETE_STRING));
2081 if (rFrame.IsFollow() && nPos < rFrame.GetOffset())
2083 rFrame.ManipOfst( std::max(nPos, op(rFrame.GetOffset(), nLen)) );
2084 assert(sal_Int32(rFrame.GetOffset()) <= rFrame.GetText().getLength());
2088 namespace {
2090 void UpdateMergedParaForMove(sw::MergedPara & rMerged,
2091 SwTextFrame & rTextFrame,
2092 bool & o_rbRecalcFootnoteFlag,
2093 SwTextNode const& rDestNode,
2094 SwTextNode const& rNode,
2095 sal_Int32 const nDestStart,
2096 sal_Int32 const nSourceStart,
2097 sal_Int32 const nLen)
2099 std::vector<std::pair<sal_Int32, sal_Int32>> deleted;
2100 sal_Int32 const nSourceEnd(nSourceStart + nLen);
2101 sal_Int32 nLastEnd(0);
2102 for (const auto& rExt : rMerged.extents)
2104 if (rExt.pNode == &rNode)
2106 sal_Int32 const nStart(std::max(nLastEnd, nSourceStart));
2107 sal_Int32 const nEnd(std::min(rExt.nStart, nSourceEnd));
2108 if (nStart < nEnd)
2110 deleted.emplace_back(nStart, nEnd);
2112 nLastEnd = rExt.nEnd;
2113 if (nSourceEnd <= rExt.nEnd)
2115 break;
2118 else if (rNode.GetIndex() < rExt.pNode->GetIndex())
2120 break;
2123 if (nLastEnd != rNode.Len()) // without nLen, string yet to be removed
2125 if (nLastEnd < nSourceEnd)
2127 deleted.emplace_back(std::max(nLastEnd, nSourceStart), nSourceEnd);
2130 if (deleted.empty())
2131 return;
2133 o_rbRecalcFootnoteFlag = true;
2134 for (auto const& it : deleted)
2136 sal_Int32 const nStart(it.first - nSourceStart + nDestStart);
2137 TextFrameIndex const nDeleted = UpdateMergedParaForDelete(rMerged, false,
2138 rDestNode, nStart, it.second - it.first);
2139 //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);
2140 // assert(nDeleted == it.second - it.first);
2141 if(nDeleted)
2143 // InvalidateRange/lcl_SetScriptInval was called sufficiently for InsertText
2144 lcl_SetWrong(rTextFrame, rDestNode, nStart, it.first - it.second, false);
2145 TextFrameIndex const nIndex(sw::MapModelToView(rMerged, &rDestNode, nStart));
2146 lcl_ModifyOfst(rTextFrame, nIndex, nDeleted, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>);
2151 } // namespace
2154 * Related: fdo#56031 filter out attribute changes that don't matter for
2155 * humans/a11y to stop flooding the destination mortal with useless noise
2157 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
2158 static bool isA11yRelevantAttribute(sal_uInt16 nWhich)
2160 return nWhich != RES_CHRATR_RSID;
2163 static bool hasA11yRelevantAttribute( const std::vector<sal_uInt16>& rWhichFmtAttr )
2165 for( sal_uInt16 nWhich : rWhichFmtAttr )
2166 if ( isA11yRelevantAttribute( nWhich ) )
2167 return true;
2169 return false;
2171 #endif // ENABLE_WASM_STRIP_ACCESSIBILITY
2173 // Note: for now this overrides SwClient::SwClientNotify; the intermediary
2174 // classes still override SwClient::Modify, which should continue to work
2175 // as their implementation of SwClientNotify is SwClient's which calls Modify.
2176 // Therefore we also don't need to call SwClient::SwClientNotify(rModify, rHint)
2177 // because that's all it does, and this implementation calls
2178 // SwContentFrame::SwClientNotify() when appropriate.
2179 void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint)
2181 SfxPoolItem const* pOld(nullptr);
2182 SfxPoolItem const* pNew(nullptr);
2183 sw::MoveText const* pMoveText(nullptr);
2184 sw::InsertText const* pInsertText(nullptr);
2185 sw::DeleteText const* pDeleteText(nullptr);
2186 sw::DeleteChar const* pDeleteChar(nullptr);
2187 sw::RedlineDelText const* pRedlineDelText(nullptr);
2188 sw::RedlineUnDelText const* pRedlineUnDelText(nullptr);
2189 SwFormatChangeHint const * pFormatChangedHint(nullptr);
2190 sw::AttrSetChangeHint const* pAttrSetChangeHint(nullptr);
2191 sw::UpdateAttrHint const* pUpdateAttrHint(nullptr);
2193 sal_uInt16 nWhich = 0;
2194 if (rHint.GetId() == SfxHintId::SwLegacyModify)
2196 auto pHint = static_cast<const sw::LegacyModifyHint*>(&rHint);
2197 pOld = pHint->m_pOld;
2198 pNew = pHint->m_pNew;
2199 nWhich = pHint->GetWhich();
2201 else if (rHint.GetId() == SfxHintId::SwUpdateAttr)
2203 pUpdateAttrHint = static_cast<const sw::UpdateAttrHint*>(&rHint);
2205 else if (rHint.GetId() == SfxHintId::SwInsertText)
2207 pInsertText = static_cast<const sw::InsertText*>(&rHint);
2209 else if (rHint.GetId() == SfxHintId::SwDeleteText)
2211 pDeleteText = static_cast<const sw::DeleteText*>(&rHint);
2213 else if (rHint.GetId() == SfxHintId::SwDeleteChar)
2215 pDeleteChar = static_cast<const sw::DeleteChar*>(&rHint);
2217 else if (rHint.GetId() == SfxHintId::SwDocPosUpdateAtIndex)
2219 auto pDocPosAt = static_cast<const sw::DocPosUpdateAtIndex*>(&rHint);
2220 Broadcast(SfxHint()); // notify SwAccessibleParagraph
2221 if(IsLocked())
2222 return;
2223 if(pDocPosAt->m_nDocPos > getFrameArea().Top())
2224 return;
2225 TextFrameIndex const nIndex(MapModelToView(
2226 &pDocPosAt->m_rNode,
2227 pDocPosAt->m_nIndex));
2228 InvalidateRange(SwCharRange(nIndex, TextFrameIndex(1)));
2229 return;
2231 else if (rHint.GetId() == SfxHintId::SwVirtPageNumHint)
2233 auto& rVirtPageNumHint = const_cast<sw::VirtPageNumHint&>(static_cast<const sw::VirtPageNumHint&>(rHint));
2234 if(!IsInDocBody() || IsFollow() || rVirtPageNumHint.IsFound())
2235 return;
2236 if(const SwPageFrame* pPage = FindPageFrame())
2237 pPage->UpdateVirtPageNumInfo(rVirtPageNumHint, this);
2238 return;
2240 else if (rHint.GetId() == SfxHintId::SwMoveText)
2242 pMoveText = static_cast<sw::MoveText const*>(&rHint);
2244 else if (rHint.GetId() == SfxHintId::SwRedlineDelText)
2246 pRedlineDelText = static_cast<sw::RedlineDelText const*>(&rHint);
2248 else if (rHint.GetId() == SfxHintId::SwRedlineUnDelText)
2250 pRedlineUnDelText = static_cast<sw::RedlineUnDelText const*>(&rHint);
2252 else if (rHint.GetId() == SfxHintId::SwFormatChange)
2254 pFormatChangedHint = static_cast<const SwFormatChangeHint*>(&rHint);
2256 else if (rHint.GetId() == SfxHintId::SwAttrSetChange)
2258 pAttrSetChangeHint = static_cast<const sw::AttrSetChangeHint*>(&rHint);
2260 else
2262 assert(!"unexpected hint");
2265 if (m_pMergedPara)
2267 assert(m_pMergedPara->listener.IsListeningTo(&rModify));
2270 SwTextNode const& rNode(static_cast<SwTextNode const&>(rModify));
2272 // modifications concerning frame attributes are processed by the base class
2273 if( IsInRange( aFrameFormatSetRange, nWhich ) || pFormatChangedHint )
2275 if (m_pMergedPara)
2276 { // ignore item set changes that don't apply
2277 SwTextNode const*const pAttrNode(
2278 (nWhich == RES_PAGEDESC || nWhich == RES_BREAK)
2279 ? m_pMergedPara->pFirstNode
2280 : m_pMergedPara->pParaPropsNode);
2281 if (pAttrNode != &rModify)
2283 return;
2286 SwContentFrame::SwClientNotify(rModify, rHint);
2287 if( pFormatChangedHint && getRootFrame()->GetCurrShell() )
2289 // collection has changed
2290 Prepare();
2291 InvalidatePrt_();
2292 lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false );
2293 SetDerivedR2L( false );
2294 CheckDirChange();
2295 // Force complete paint due to existing indents.
2296 SetCompletePaint();
2297 InvalidateLineNum();
2299 return;
2302 if (m_pMergedPara && m_pMergedPara->pParaPropsNode != &rModify)
2304 if (isPARATR(nWhich) || isPARATR_LIST(nWhich)) // FRMATR handled above
2306 return; // ignore it
2310 Broadcast(SfxHint()); // notify SwAccessibleParagraph
2312 // while locked ignore all modifications
2313 if( IsLocked() )
2314 return;
2316 // save stack
2317 // warning: one has to ensure that all variables are set
2318 TextFrameIndex nPos;
2319 TextFrameIndex nLen;
2320 bool bSetFieldsDirty = false;
2321 bool bRecalcFootnoteFlag = false;
2323 if (pRedlineDelText)
2325 if (m_pMergedPara)
2327 sal_Int32 const nNPos = pRedlineDelText->nStart;
2328 sal_Int32 const nNLen = pRedlineDelText->nLen;
2329 nPos = MapModelToView(&rNode, nNPos);
2330 // update merged before doing anything else
2331 nLen = UpdateMergedParaForDelete(*m_pMergedPara, false, rNode, nNPos, nNLen);
2332 const sal_Int32 m = -nNLen;
2333 if (nLen && IsIdxInside(nPos, nLen))
2335 InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), m );
2337 lcl_SetWrong( *this, rNode, nNPos, m, false );
2338 if (nLen)
2340 lcl_SetScriptInval( *this, nPos );
2341 bSetFieldsDirty = bRecalcFootnoteFlag = true;
2342 lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>);
2346 else if (pRedlineUnDelText)
2348 if (m_pMergedPara)
2350 sal_Int32 const nNPos = pRedlineUnDelText->nStart;
2351 sal_Int32 const nNLen = pRedlineUnDelText->nLen;
2352 nPos = MapModelToView(&rNode, nNPos);
2353 nLen = UpdateMergedParaForInsert(*m_pMergedPara, false, rNode, nNPos, nNLen);
2354 if (IsIdxInside(nPos, nLen))
2356 if (!nLen)
2358 // Refresh NumPortions even when line is empty!
2359 if (nPos)
2360 InvalidateSize();
2361 else
2362 Prepare();
2364 else
2365 InvalidateRange_( SwCharRange( nPos, nLen ), nNLen );
2367 lcl_SetWrong( *this, rNode, nNPos, nNLen, false );
2368 lcl_SetScriptInval( *this, nPos );
2369 bSetFieldsDirty = true;
2370 lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator+<sal_Int32, Tag_TextFrameIndex>);
2373 else if (pMoveText)
2375 if (m_pMergedPara
2376 && m_pMergedPara->pFirstNode->GetIndex() <= pMoveText->pDestNode->GetIndex()
2377 && pMoveText->pDestNode->GetIndex() <= m_pMergedPara->pLastNode->GetIndex())
2378 { // if it's not 2 nodes in merged frame, assume the target node doesn't have frames at all
2379 assert(abs(rNode.GetIndex() - pMoveText->pDestNode->GetIndex()) == SwNodeOffset(1));
2380 UpdateMergedParaForMove(*m_pMergedPara,
2381 *this,
2382 bRecalcFootnoteFlag,
2383 *pMoveText->pDestNode, rNode,
2384 pMoveText->nDestStart,
2385 pMoveText->nSourceStart,
2386 pMoveText->nLen);
2388 else
2390 // 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
2391 // assert(!m_pMergedPara || !getRootFrame()->IsHideRedlines() || !pMoveText->pDestNode->getLayoutFrame(getRootFrame()));
2394 else if (pInsertText)
2396 nPos = MapModelToView(&rNode, pInsertText->nPos);
2397 // unlike redlines, inserting into fieldmark must be explicitly handled
2398 bool isHidden(false);
2399 switch (getRootFrame()->GetFieldmarkMode())
2401 case sw::FieldmarkMode::ShowCommand:
2402 isHidden = pInsertText->isInsideFieldmarkResult;
2403 break;
2404 case sw::FieldmarkMode::ShowResult:
2405 isHidden = pInsertText->isInsideFieldmarkCommand;
2406 break;
2407 case sw::FieldmarkMode::ShowBoth: // just to avoid the warning
2408 break;
2410 if (!isHidden)
2412 nLen = TextFrameIndex(pInsertText->nLen);
2413 if (m_pMergedPara)
2415 UpdateMergedParaForInsert(*m_pMergedPara, true, rNode, pInsertText->nPos, pInsertText->nLen);
2417 if( IsIdxInside( nPos, nLen ) )
2419 if( !nLen )
2421 // Refresh NumPortions even when line is empty!
2422 if( nPos )
2423 InvalidateSize();
2424 else
2425 Prepare();
2427 else
2428 InvalidateRange_( SwCharRange( nPos, nLen ), pInsertText->nLen );
2430 lcl_SetScriptInval( *this, nPos );
2431 bSetFieldsDirty = true;
2432 lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator+<sal_Int32, Tag_TextFrameIndex>);
2434 lcl_SetWrong( *this, rNode, pInsertText->nPos, pInsertText->nLen, true );
2436 else if (pDeleteText)
2438 nPos = MapModelToView(&rNode, pDeleteText->nStart);
2439 if (m_pMergedPara)
2440 { // update merged before doing anything else
2441 nLen = UpdateMergedParaForDelete(*m_pMergedPara, true, rNode, pDeleteText->nStart, pDeleteText->nLen);
2443 else
2445 nLen = TextFrameIndex(pDeleteText->nLen);
2447 const sal_Int32 m = -pDeleteText->nLen;
2448 if ((!m_pMergedPara || nLen) && IsIdxInside(nPos, nLen))
2450 if( !nLen )
2451 InvalidateSize();
2452 else
2453 InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), m );
2455 lcl_SetWrong( *this, rNode, pDeleteText->nStart, m, true );
2456 if (nLen)
2458 lcl_SetScriptInval( *this, nPos );
2459 bSetFieldsDirty = bRecalcFootnoteFlag = true;
2460 lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>);
2463 else if (pDeleteChar)
2465 nPos = MapModelToView(&rNode, pDeleteChar->m_nPos);
2466 if (m_pMergedPara)
2468 nLen = UpdateMergedParaForDelete(*m_pMergedPara, true, rNode, pDeleteChar->m_nPos, 1);
2470 else
2472 nLen = TextFrameIndex(1);
2474 lcl_SetWrong( *this, rNode, pDeleteChar->m_nPos, -1, true );
2475 if (nLen)
2477 InvalidateRange( SwCharRange(nPos, nLen), -1 );
2478 lcl_SetScriptInval( *this, nPos );
2479 bSetFieldsDirty = bRecalcFootnoteFlag = true;
2480 lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>);
2483 else if (pAttrSetChangeHint)
2485 InvalidateLineNum();
2487 const SwAttrSet& rNewSet = *pAttrSetChangeHint->m_pNew->GetChgSet();
2488 int nClear = 0;
2489 sal_uInt16 nCount = rNewSet.Count();
2491 if( const SwFormatFootnote* pItem = rNewSet.GetItemIfSet( RES_TXTATR_FTN, false ) )
2493 nPos = MapModelToView(&rNode, pItem->GetTextFootnote()->GetStart());
2494 if (IsIdxInside(nPos, TextFrameIndex(1)))
2495 Prepare( PrepareHint::FootnoteInvalidation, pAttrSetChangeHint->m_pNew );
2496 nClear = 0x01;
2497 --nCount;
2500 if( const SwFormatField* pItem = rNewSet.GetItemIfSet( RES_TXTATR_FIELD, false ) )
2502 nPos = MapModelToView(&rNode, pItem->GetTextField()->GetStart());
2503 if (IsIdxInside(nPos, TextFrameIndex(1)))
2505 const SwFormatField* pOldItem = pAttrSetChangeHint->m_pOld ?
2506 &(pAttrSetChangeHint->m_pOld->GetChgSet()->Get(RES_TXTATR_FIELD)) : nullptr;
2507 if (SfxPoolItem::areSame( pItem, pOldItem ))
2509 InvalidatePage();
2510 SetCompletePaint();
2512 else
2513 InvalidateRange_(SwCharRange(nPos, TextFrameIndex(1)));
2515 nClear |= 0x02;
2516 --nCount;
2518 bool bLineSpace = SfxItemState::SET == rNewSet.GetItemState(
2519 RES_PARATR_LINESPACING, false ),
2520 bRegister = SfxItemState::SET == rNewSet.GetItemState(
2521 RES_PARATR_REGISTER, false );
2522 if ( bLineSpace || bRegister )
2524 if (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)
2526 Prepare( bRegister ? PrepareHint::Register : PrepareHint::AdjustSizeWithoutFormatting );
2527 CalcLineSpace();
2528 InvalidateSize();
2529 InvalidatePrt_();
2531 // i#11859
2532 // (1) Also invalidate next frame on next page/column.
2533 // (2) Skip empty sections and hidden paragraphs
2534 // Thus, use method <InvalidateNextPrtArea()>
2535 InvalidateNextPrtArea();
2537 SetCompletePaint();
2539 nClear |= 0x04;
2540 if ( bLineSpace )
2542 --nCount;
2543 if ((!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)
2544 && IsInSct() && !GetPrev())
2546 SwSectionFrame *pSect = FindSctFrame();
2547 if( pSect->ContainsAny() == this )
2548 pSect->InvalidatePrt();
2551 if ( bRegister )
2552 --nCount;
2554 if ( SfxItemState::SET == rNewSet.GetItemState( RES_PARATR_SPLIT,
2555 false ))
2557 if (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)
2559 if (GetPrev())
2560 CheckKeep();
2561 Prepare();
2562 InvalidateSize();
2564 nClear |= 0x08;
2565 --nCount;
2568 if( SfxItemState::SET == rNewSet.GetItemState( RES_BACKGROUND, false)
2569 && (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)
2570 && !IsFollow() && GetDrawObjs() )
2572 SwSortedObjs *pObjs = GetDrawObjs();
2573 for ( size_t i = 0; GetDrawObjs() && i < pObjs->size(); ++i )
2575 SwAnchoredObject* pAnchoredObj = (*pObjs)[i];
2576 if ( auto pFly = pAnchoredObj->DynCastFlyFrame() )
2578 if( !pFly->IsFlyInContentFrame() )
2580 const SvxBrushItem &rBack =
2581 pFly->GetAttrSet()->GetBackground();
2582 // #GetTransChg#
2583 // following condition determines, if the fly frame
2584 // "inherites" the background color of text frame.
2585 // This is the case, if fly frame background
2586 // color is "no fill"/"auto fill" and if the fly frame
2587 // has no background graphic.
2588 // Thus, check complete fly frame background
2589 // color and *not* only its transparency value
2590 if ( (rBack.GetColor() == COL_TRANSPARENT) &&
2591 rBack.GetGraphicPos() == GPOS_NONE )
2593 pFly->SetCompletePaint();
2594 pFly->InvalidatePage();
2601 if ( SfxItemState::SET ==
2602 rNewSet.GetItemState( RES_TXTATR_CHARFMT, false ) )
2604 lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false );
2605 lcl_SetScriptInval( *this, TextFrameIndex(0) );
2607 else if ( SfxItemState::SET ==
2608 rNewSet.GetItemState( RES_CHRATR_LANGUAGE, false ) ||
2609 SfxItemState::SET ==
2610 rNewSet.GetItemState( RES_CHRATR_CJK_LANGUAGE, false ) ||
2611 SfxItemState::SET ==
2612 rNewSet.GetItemState( RES_CHRATR_CTL_LANGUAGE, false ) )
2613 lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false );
2614 else if ( SfxItemState::SET ==
2615 rNewSet.GetItemState( RES_CHRATR_FONT, false ) ||
2616 SfxItemState::SET ==
2617 rNewSet.GetItemState( RES_CHRATR_CJK_FONT, false ) ||
2618 SfxItemState::SET ==
2619 rNewSet.GetItemState( RES_CHRATR_CTL_FONT, false ) )
2620 lcl_SetScriptInval( *this, TextFrameIndex(0) );
2621 else if ( SfxItemState::SET ==
2622 rNewSet.GetItemState( RES_FRAMEDIR, false )
2623 && (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify))
2625 SetDerivedR2L( false );
2626 CheckDirChange();
2627 // Force complete paint due to existing indents.
2628 SetCompletePaint();
2631 if( nCount )
2633 if( getRootFrame()->GetCurrShell() )
2635 Prepare();
2636 InvalidatePrt_();
2639 if (nClear || (m_pMergedPara &&
2640 (m_pMergedPara->pParaPropsNode != &rModify ||
2641 m_pMergedPara->pFirstNode != &rModify)))
2643 assert(pAttrSetChangeHint->m_pOld);
2644 SwAttrSetChg aOldSet( *pAttrSetChangeHint->m_pOld );
2645 SwAttrSetChg aNewSet( *pAttrSetChangeHint->m_pNew );
2647 if (m_pMergedPara && m_pMergedPara->pParaPropsNode != &rModify)
2649 for (sal_uInt16 i = RES_PARATR_BEGIN; i != RES_FRMATR_END; ++i)
2651 if (i != RES_BREAK && i != RES_PAGEDESC)
2653 aOldSet.ClearItem(i);
2654 aNewSet.ClearItem(i);
2657 for (sal_uInt16 i = XATTR_FILL_FIRST; i <= XATTR_FILL_LAST; ++i)
2659 aOldSet.ClearItem(i);
2660 aNewSet.ClearItem(i);
2663 if (m_pMergedPara && m_pMergedPara->pFirstNode != &rModify)
2665 aOldSet.ClearItem(RES_BREAK);
2666 aNewSet.ClearItem(RES_BREAK);
2667 aOldSet.ClearItem(RES_PAGEDESC);
2668 aNewSet.ClearItem(RES_PAGEDESC);
2671 if( 0x01 & nClear )
2673 aOldSet.ClearItem( RES_TXTATR_FTN );
2674 aNewSet.ClearItem( RES_TXTATR_FTN );
2676 if( 0x02 & nClear )
2678 aOldSet.ClearItem( RES_TXTATR_FIELD );
2679 aNewSet.ClearItem( RES_TXTATR_FIELD );
2681 if ( 0x04 & nClear )
2683 if ( bLineSpace )
2685 aOldSet.ClearItem( RES_PARATR_LINESPACING );
2686 aNewSet.ClearItem( RES_PARATR_LINESPACING );
2688 if ( bRegister )
2690 aOldSet.ClearItem( RES_PARATR_REGISTER );
2691 aNewSet.ClearItem( RES_PARATR_REGISTER );
2694 if ( 0x08 & nClear )
2696 aOldSet.ClearItem( RES_PARATR_SPLIT );
2697 aNewSet.ClearItem( RES_PARATR_SPLIT );
2699 if (aOldSet.Count() || aNewSet.Count())
2701 SwContentFrame::SwClientNotify(rModify, sw::AttrSetChangeHint(&aOldSet, &aNewSet));
2704 else
2705 SwContentFrame::SwClientNotify(rModify, rHint);
2708 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
2709 if (isA11yRelevantAttribute(nWhich))
2711 SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr;
2712 if ( pViewSh )
2714 pViewSh->InvalidateAccessibleParaAttrs( *this );
2717 #endif
2719 else if (rHint.GetId() == SfxHintId::SwObjectDying)
2720 ; // do nothing
2721 else if (pUpdateAttrHint)
2723 const SwUpdateAttr* pNewUpdate = pUpdateAttrHint->m_pNew;
2725 sal_Int32 const nNPos = pNewUpdate->getStart();
2726 sal_Int32 const nNLen = pNewUpdate->getEnd() - nNPos;
2727 nPos = MapModelToView(&rNode, nNPos);
2728 nLen = MapModelToView(&rNode, nNPos + nNLen) - nPos;
2729 if( IsIdxInside( nPos, nLen ) )
2731 // We need to reformat anyways, even if the invalidated
2732 // range is empty.
2733 // E.g.: empty line, set 14 pt!
2735 // FootnoteNumbers need to be formatted
2736 if( !nLen )
2737 nLen = TextFrameIndex(1);
2739 InvalidateRange_( SwCharRange( nPos, nLen) );
2740 const sal_uInt16 nTmp = pNewUpdate->getWhichAttr();
2742 if( ! nTmp || RES_TXTATR_CHARFMT == nTmp || RES_TXTATR_INETFMT == nTmp || RES_TXTATR_AUTOFMT == nTmp ||
2743 RES_UPDATEATTR_FMT_CHG == nTmp || RES_UPDATEATTR_ATTRSET_CHG == nTmp )
2745 lcl_SetWrong( *this, rNode, nNPos, nNPos + nNLen, false );
2746 lcl_SetScriptInval( *this, nPos );
2750 #if !ENABLE_WASM_STRIP_ACCESSIBILITY
2751 if( isA11yRelevantAttribute( pNewUpdate->getWhichAttr() ) &&
2752 hasA11yRelevantAttribute( pNewUpdate->getFmtAttrs() ) )
2754 SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr;
2755 if ( pViewSh )
2757 pViewSh->InvalidateAccessibleParaAttrs( *this );
2760 #endif
2762 else switch (nWhich)
2764 case RES_LINENUMBER:
2766 assert(false); // should have been forwarded to SwContentFrame
2767 InvalidateLineNum();
2769 break;
2771 case RES_PARATR_LINESPACING:
2773 CalcLineSpace();
2774 InvalidateSize();
2775 InvalidatePrt_();
2776 if( IsInSct() && !GetPrev() )
2778 SwSectionFrame *pSect = FindSctFrame();
2779 if( pSect->ContainsAny() == this )
2780 pSect->InvalidatePrt();
2783 // i#11859
2784 // (1) Also invalidate next frame on next page/column.
2785 // (2) Skip empty sections and hidden paragraphs
2786 // Thus, use method <InvalidateNextPrtArea()>
2787 InvalidateNextPrtArea();
2789 SetCompletePaint();
2791 break;
2793 case RES_TXTATR_FIELD:
2794 case RES_TXTATR_ANNOTATION:
2796 sal_Int32 const nNPos = static_cast<const SwFormatField*>(pNew)->GetTextField()->GetStart();
2797 nPos = MapModelToView(&rNode, nNPos);
2798 if (IsIdxInside(nPos, TextFrameIndex(1)))
2800 if (SfxPoolItem::areSame( pNew, pOld ))
2802 // only repaint
2803 // opt: invalidate window?
2804 InvalidatePage();
2805 SetCompletePaint();
2807 else
2808 InvalidateRange_(SwCharRange(nPos, TextFrameIndex(1)));
2810 bSetFieldsDirty = true;
2811 // ST2
2812 if ( SwSmartTagMgr::Get().IsSmartTagsEnabled() )
2813 lcl_SetWrong( *this, rNode, nNPos, nNPos + 1, false );
2815 break;
2817 case RES_TXTATR_FTN :
2819 if (!IsInFootnote())
2820 { // the hint may be sent from the anchor node, or from a
2821 // node in the footnote; the anchor index is only valid in the
2822 // anchor node!
2823 assert(rNode == static_cast<const SwFormatFootnote*>(pNew)->GetTextFootnote()->GetTextNode());
2824 nPos = MapModelToView(&rNode,
2825 static_cast<const SwFormatFootnote*>(pNew)->GetTextFootnote()->GetStart());
2827 #ifdef _MSC_VER
2828 else nPos = TextFrameIndex(42); // shut up MSVC 2017 spurious warning C4701
2829 #endif
2830 if (IsInFootnote() || IsIdxInside(nPos, TextFrameIndex(1)))
2831 Prepare( PrepareHint::FootnoteInvalidation, static_cast<const SwFormatFootnote*>(pNew)->GetTextFootnote() );
2832 break;
2835 case RES_PARATR_SPLIT:
2836 if ( GetPrev() )
2837 CheckKeep();
2838 Prepare();
2839 bSetFieldsDirty = true;
2840 break;
2841 case RES_FRAMEDIR :
2842 assert(false); // should have been forwarded to SwContentFrame
2843 SetDerivedR2L( false );
2844 CheckDirChange();
2845 break;
2846 default:
2848 Prepare();
2849 InvalidatePrt_();
2850 if ( !nWhich )
2852 // is called by e. g. HiddenPara with 0
2853 SwFrame *pNxt = FindNext();
2854 if ( nullptr != pNxt )
2855 pNxt->InvalidatePrt();
2858 } // switch
2860 if( bSetFieldsDirty )
2861 GetDoc().getIDocumentFieldsAccess().SetFieldsDirty( true, &rNode, SwNodeOffset(1) );
2863 if ( bRecalcFootnoteFlag )
2864 CalcFootnoteFlag();
2867 void SwTextFrame::PrepWidows( const sal_uInt16 nNeed, bool bNotify )
2869 OSL_ENSURE(GetFollow() && nNeed, "+SwTextFrame::Prepare: lost all friends");
2871 SwParaPortion *pPara = GetPara();
2872 if ( !pPara )
2873 return;
2874 pPara->SetPrepWidows();
2876 sal_uInt16 nHave = nNeed;
2878 // We yield a few lines and shrink in CalcPreps()
2879 SwSwapIfNotSwapped swap( this );
2881 SwTextSizeInfo aInf( this );
2882 SwTextMargin aLine( this, &aInf );
2883 aLine.Bottom();
2884 TextFrameIndex nTmpLen = aLine.GetCurr()->GetLen();
2885 while( nHave && aLine.PrevLine() )
2887 if( nTmpLen )
2888 --nHave;
2889 nTmpLen = aLine.GetCurr()->GetLen();
2892 // If it's certain that we can yield lines, the Master needs
2893 // to check the widow rule
2894 if( !nHave )
2896 bool bSplit = true;
2897 if( !IsFollow() ) // only a master decides about orphans
2899 const WidowsAndOrphans aWidOrp( this );
2900 bSplit = ( aLine.GetLineNr() >= aWidOrp.GetOrphansLines() &&
2901 aLine.GetLineNr() >= aLine.GetDropLines() );
2904 if( bSplit )
2906 GetFollow()->SetOffset( aLine.GetEnd() );
2907 aLine.TruncLines( true );
2908 if( pPara->IsFollowField() )
2909 GetFollow()->SetFieldFollow( true );
2912 if ( bNotify )
2914 InvalidateSize_();
2915 InvalidatePage();
2919 static bool lcl_ErgoVadis(SwTextFrame* pFrame, TextFrameIndex & rPos, const PrepareHint ePrep)
2921 const SwFootnoteInfo &rFootnoteInfo = pFrame->GetDoc().GetFootnoteInfo();
2922 if( ePrep == PrepareHint::ErgoSum )
2924 if( rFootnoteInfo.m_aErgoSum.isEmpty() )
2925 return false;
2926 rPos = pFrame->GetOffset();
2928 else
2930 if( rFootnoteInfo.m_aQuoVadis.isEmpty() )
2931 return false;
2932 if( pFrame->HasFollow() )
2933 rPos = pFrame->GetFollow()->GetOffset();
2934 else
2935 rPos = TextFrameIndex(pFrame->GetText().getLength());
2936 if( rPos )
2937 --rPos; // our last character
2939 return true;
2942 // Silence over-eager warning emitted at least by GCC 5.3.1
2943 #if defined __GNUC__ && !defined __clang__
2944 # pragma GCC diagnostic push
2945 # pragma GCC diagnostic ignored "-Wstrict-overflow"
2946 #endif
2947 bool SwTextFrame::Prepare( const PrepareHint ePrep, const void* pVoid,
2948 bool bNotify )
2950 bool bParaPossiblyInvalid = false;
2952 SwFrameSwapper aSwapper( this, false );
2954 if ( IsEmpty() )
2956 switch ( ePrep )
2958 case PrepareHint::BossChanged:
2959 SetInvalidVert( true ); // Test
2960 [[fallthrough]];
2961 case PrepareHint::WidowsOrphans:
2962 case PrepareHint::Widows:
2963 case PrepareHint::FootnoteInvalidationGone : return bParaPossiblyInvalid;
2965 case PrepareHint::FramePositionChanged :
2967 // We also need an InvalidateSize for Areas (with and without columns),
2968 // so that we format and bUndersized is set (if needed)
2969 if( IsInFly() || IsInSct() )
2971 SwTwips nTmpBottom = GetUpper()->getFrameArea().Top() +
2972 GetUpper()->getFramePrintArea().Bottom();
2973 if( nTmpBottom < getFrameArea().Bottom() )
2974 break;
2976 // Are there any free-flying frames on this page?
2977 SwTextFly aTextFly( this );
2978 if( aTextFly.IsOn() )
2980 // Does any free-flying frame overlap?
2981 if ( aTextFly.Relax() || IsUndersized() )
2982 break;
2984 if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue())
2985 break;
2987 SwTextGridItem const*const pGrid(GetGridItem(FindPageFrame()));
2988 if (pGrid && GetTextNodeForParaProps()->GetSwAttrSet().GetParaGrid().GetValue())
2989 break;
2991 // i#28701 - consider anchored objects
2992 if ( GetDrawObjs() )
2993 break;
2995 return bParaPossiblyInvalid;
2997 default:
2998 break;
3002 // Split fly anchors are technically empty (have no SwParaPortion), but otherwise behave like
3003 // other split text frames, which are non-empty.
3004 bool bSplitFlyAnchor = GetOffset() == TextFrameIndex(0) && HasFollow()
3005 && GetFollow()->GetOffset() == TextFrameIndex(0);
3007 if( !HasPara() && !bSplitFlyAnchor && PrepareHint::MustFit != ePrep )
3009 OSL_ENSURE( !IsLocked(), "SwTextFrame::Prepare: three of a perfect pair" );
3010 // check while ignoring frame width (testParagraphMarkInCell)
3011 // because it's called from InvalidateAllContent()
3012 if (!IsHiddenNowImpl())
3014 SetInvalidVert( true ); // Test
3015 if ( bNotify )
3016 InvalidateSize();
3017 else
3018 InvalidateSize_();
3020 return bParaPossiblyInvalid;
3023 // Get object from cache while locking
3024 SwTextLineAccess aAccess( this );
3025 SwParaPortion *pPara = aAccess.GetPara();
3027 switch( ePrep )
3029 case PrepareHint::FootnoteMove :
3031 SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
3032 aFrm.Height(0);
3036 SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
3037 aPrt.Height(0);
3040 InvalidatePrt_();
3041 InvalidateSize_();
3042 [[fallthrough]];
3043 case PrepareHint::AdjustSizeWithoutFormatting :
3044 pPara->SetPrepAdjust();
3045 if( IsFootnoteNumFrame() != pPara->IsFootnoteNum() ||
3046 IsUndersized() )
3048 InvalidateRange(SwCharRange(TextFrameIndex(0), TextFrameIndex(1)), 1);
3049 if( GetOffset() && !IsFollow() )
3050 SetOffset_(TextFrameIndex(0));
3052 break;
3053 case PrepareHint::MustFit :
3054 pPara->SetPrepMustFit(true);
3055 [[fallthrough]];
3056 case PrepareHint::WidowsOrphans :
3057 pPara->SetPrepAdjust();
3058 break;
3059 case PrepareHint::Widows :
3060 // MustFit is stronger than anything else
3061 if( pPara->IsPrepMustFit() )
3062 return bParaPossiblyInvalid;
3063 // see comment in WidowsAndOrphans::FindOrphans and CalcPreps()
3064 PrepWidows( *static_cast<const sal_uInt16 *>(pVoid), bNotify );
3065 break;
3067 case PrepareHint::FootnoteInvalidation :
3069 SwTextFootnote const *pFootnote = static_cast<SwTextFootnote const *>(pVoid);
3070 if( IsInFootnote() )
3072 // Am I the first TextFrame of a footnote?
3073 if( !GetPrev() )
3074 // So we're a TextFrame of the footnote, which has
3075 // to display the footnote number or the ErgoSum text
3076 InvalidateRange(SwCharRange(TextFrameIndex(0), TextFrameIndex(1)), 1);
3078 if( !GetNext() )
3080 // We're the last Footnote; we need to update the
3081 // QuoVadis texts now
3082 const SwFootnoteInfo &rFootnoteInfo = GetDoc().GetFootnoteInfo();
3083 if( !pPara->UpdateQuoVadis( rFootnoteInfo.m_aQuoVadis ) )
3085 TextFrameIndex nPos = pPara->GetParLen();
3086 if( nPos )
3087 --nPos;
3088 InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), 1);
3092 else
3094 // We are the TextFrame _with_ the footnote
3095 TextFrameIndex const nPos = MapModelToView(
3096 &pFootnote->GetTextNode(), pFootnote->GetStart());
3097 InvalidateRange(SwCharRange(nPos, TextFrameIndex(1)), 1);
3099 break;
3101 case PrepareHint::BossChanged :
3103 // Test
3105 SetInvalidVert( false );
3106 bool bOld = IsVertical();
3107 SetInvalidVert( true );
3108 if( bOld != IsVertical() )
3109 InvalidateRange(SwCharRange(GetOffset(), TextFrameIndex(COMPLETE_STRING)));
3112 if( HasFollow() )
3114 TextFrameIndex nNxtOfst = GetFollow()->GetOffset();
3115 if( nNxtOfst )
3116 --nNxtOfst;
3117 InvalidateRange(SwCharRange( nNxtOfst, TextFrameIndex(1)), 1);
3119 if( IsInFootnote() )
3121 TextFrameIndex nPos;
3122 if( lcl_ErgoVadis( this, nPos, PrepareHint::QuoVadis ) )
3123 InvalidateRange( SwCharRange( nPos, TextFrameIndex(1)) );
3124 if( lcl_ErgoVadis( this, nPos, PrepareHint::ErgoSum ) )
3125 InvalidateRange( SwCharRange( nPos, TextFrameIndex(1)) );
3127 // If we have a page number field, we must invalidate those spots
3128 SwTextNode const* pNode(nullptr);
3129 sw::MergedAttrIter iter(*this);
3130 TextFrameIndex const nEnd = GetFollow()
3131 ? GetFollow()->GetOffset() : TextFrameIndex(COMPLETE_STRING);
3132 for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode))
3134 TextFrameIndex const nStart(MapModelToView(pNode, pHt->GetStart()));
3135 if (nStart >= GetOffset())
3137 if (nStart >= nEnd)
3138 break;
3140 // If we're flowing back and own a Footnote, the Footnote also flows
3141 // with us. So that it doesn't obstruct us, we send ourselves
3142 // an ADJUST_FRM.
3143 // pVoid != 0 means MoveBwd()
3144 const sal_uInt16 nWhich = pHt->Which();
3145 if (RES_TXTATR_FIELD == nWhich ||
3146 (HasFootnote() && pVoid && RES_TXTATR_FTN == nWhich))
3147 InvalidateRange(SwCharRange(nStart, TextFrameIndex(1)), 1);
3150 // A new boss, a new chance for growing
3151 if( IsUndersized() )
3153 InvalidateSize_();
3154 InvalidateRange(SwCharRange(GetOffset(), TextFrameIndex(1)), 1);
3156 break;
3159 case PrepareHint::FramePositionChanged :
3161 if ( isFramePrintAreaValid() )
3163 SwTextGridItem const*const pGrid(GetGridItem(FindPageFrame()));
3164 if (pGrid && GetTextNodeForParaProps()->GetSwAttrSet().GetParaGrid().GetValue())
3165 InvalidatePrt();
3168 // If we don't overlap with anybody:
3169 // did any free-flying frame overlapped _before_ the position change?
3170 bool bFormat = pPara->HasFly();
3171 if( !bFormat )
3173 if( IsInFly() )
3175 SwTwips nTmpBottom = GetUpper()->getFrameArea().Top() +
3176 GetUpper()->getFramePrintArea().Bottom();
3177 if( nTmpBottom < getFrameArea().Bottom() )
3178 bFormat = true;
3180 if( !bFormat )
3182 if ( GetDrawObjs() )
3184 const size_t nCnt = GetDrawObjs()->size();
3185 for ( size_t i = 0; i < nCnt; ++i )
3187 SwAnchoredObject* pAnchoredObj = (*GetDrawObjs())[i];
3188 // i#28701 - consider all
3189 // to-character anchored objects
3190 if ( pAnchoredObj->GetFrameFormat()->GetAnchor().GetAnchorId()
3191 == RndStdIds::FLY_AT_CHAR )
3193 bFormat = true;
3194 break;
3198 if( !bFormat )
3200 // Are there any free-flying frames on this page?
3201 SwTextFly aTextFly( this );
3202 if( aTextFly.IsOn() )
3204 // Does any free-flying frame overlap?
3205 const bool bRelaxed = aTextFly.Relax();
3206 bFormat = bRelaxed || IsUndersized();
3207 if (bRelaxed)
3209 // It's possible that pPara was deleted above; retrieve it again
3210 pPara = aAccess.GetPara();
3217 if( bFormat )
3219 if( !IsLocked() )
3221 if( pPara->GetRepaint().HasArea() )
3222 SetCompletePaint();
3223 Init();
3224 pPara = nullptr;
3225 InvalidateSize_();
3228 else
3230 if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue())
3231 bParaPossiblyInvalid = Prepare( PrepareHint::Register, nullptr, bNotify );
3232 // The Frames need to be readjusted, which caused by changes
3233 // in position
3234 else if( HasFootnote() )
3236 bParaPossiblyInvalid = Prepare( PrepareHint::AdjustSizeWithoutFormatting, nullptr, bNotify );
3237 InvalidateSize_();
3239 else
3240 return bParaPossiblyInvalid; // So that there's no SetPrep()
3242 if (bParaPossiblyInvalid)
3244 // It's possible that pPara was deleted above; retrieve it again
3245 pPara = aAccess.GetPara();
3249 break;
3251 case PrepareHint::Register:
3252 if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue())
3254 pPara->SetPrepAdjust();
3255 CalcLineSpace();
3257 // It's possible that pPara was deleted above; retrieve it again
3258 bParaPossiblyInvalid = true;
3259 pPara = aAccess.GetPara();
3261 InvalidateSize();
3262 InvalidatePrt_();
3263 SwFrame* pNxt = GetIndNext();
3264 if ( nullptr != pNxt )
3266 pNxt->InvalidatePrt_();
3267 if ( pNxt->IsLayoutFrame() )
3268 pNxt->InvalidatePage();
3270 SetCompletePaint();
3272 break;
3273 case PrepareHint::FootnoteInvalidationGone :
3275 // If a Follow is calling us, because a footnote is being deleted, our last
3276 // line has to be formatted, so that the first line of the Follow can flow up.
3277 // Which had flowed to the next page to be together with the footnote (this is
3278 // especially true for areas with columns)
3279 OSL_ENSURE( GetFollow(), "PrepareHint::FootnoteInvalidationGone may only be called by Follow" );
3280 TextFrameIndex nPos = GetFollow()->GetOffset();
3281 if( IsFollow() && GetOffset() == nPos ) // If we don't have a mass of text, we call our
3282 FindMaster()->Prepare( PrepareHint::FootnoteInvalidationGone ); // Master's Prepare
3283 if( nPos )
3284 --nPos; // The char preceding our Follow
3285 InvalidateRange(SwCharRange(nPos, TextFrameIndex(1)));
3286 return bParaPossiblyInvalid;
3288 case PrepareHint::ErgoSum:
3289 case PrepareHint::QuoVadis:
3291 TextFrameIndex nPos;
3292 if( lcl_ErgoVadis( this, nPos, ePrep ) )
3293 InvalidateRange(SwCharRange(nPos, TextFrameIndex(1)));
3295 break;
3296 case PrepareHint::FlyFrameAttributesChanged:
3298 if( pVoid )
3300 TextFrameIndex const nWhere = CalcFlyPos( static_cast<SwFrameFormat const *>(pVoid) );
3301 OSL_ENSURE( TextFrameIndex(COMPLETE_STRING) != nWhere, "Prepare: Why me?" );
3302 InvalidateRange(SwCharRange(nWhere, TextFrameIndex(1)));
3303 return bParaPossiblyInvalid;
3305 [[fallthrough]]; // else: continue with default case block
3307 case PrepareHint::Clear:
3308 default:
3310 if( IsLocked() )
3312 if( PrepareHint::FlyFrameArrive == ePrep || PrepareHint::FlyFrameLeave == ePrep )
3314 TextFrameIndex const nLen = (GetFollow()
3315 ? GetFollow()->GetOffset()
3316 : TextFrameIndex(COMPLETE_STRING))
3317 - GetOffset();
3318 InvalidateRange( SwCharRange( GetOffset(), nLen ) );
3321 else
3323 if( pPara->GetRepaint().HasArea() )
3324 SetCompletePaint();
3325 Init();
3326 pPara = nullptr;
3327 if( GetOffset() && !IsFollow() )
3328 SetOffset_( TextFrameIndex(0) );
3329 if ( bNotify )
3330 InvalidateSize();
3331 else
3332 InvalidateSize_();
3334 return bParaPossiblyInvalid; // no SetPrep() happened
3337 if( pPara )
3339 pPara->SetPrep();
3342 return bParaPossiblyInvalid;
3344 #if defined __GNUC__ && !defined __clang__
3345 # pragma GCC diagnostic pop
3346 #endif
3349 * Small Helper class:
3350 * Prepares a test format.
3351 * The frame is changed in size and position, its SwParaPortion is moved aside
3352 * and a new one is created.
3353 * To achieve this, run formatting with bTestFormat flag set.
3354 * In the destructor the TextFrame is reset to its original state.
3356 class SwTestFormat
3358 SwTextFrame *pFrame;
3359 SwParaPortion *pOldPara;
3360 SwRect aOldFrame, aOldPrt;
3361 public:
3362 SwTestFormat( SwTextFrame* pTextFrame, const SwFrame* pPrv, SwTwips nMaxHeight );
3363 ~SwTestFormat();
3366 SwTestFormat::SwTestFormat( SwTextFrame* pTextFrame, const SwFrame* pPre, SwTwips nMaxHeight )
3367 : pFrame( pTextFrame )
3369 aOldFrame = pFrame->getFrameArea();
3370 aOldPrt = pFrame->getFramePrintArea();
3372 SwRectFnSet aRectFnSet(pFrame);
3373 SwTwips nLower = aRectFnSet.GetBottomMargin(*pFrame);
3376 // indeed, here the GetUpper()->getFramePrintArea() gets copied and manipulated
3377 SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame);
3378 aFrm.setSwRect(pFrame->GetUpper()->getFramePrintArea());
3379 aFrm += pFrame->GetUpper()->getFrameArea().Pos();
3380 aRectFnSet.SetHeight( aFrm, nMaxHeight );
3382 if( pFrame->GetPrev() )
3384 aRectFnSet.SetPosY(
3385 aFrm,
3386 aRectFnSet.GetBottom(pFrame->GetPrev()->getFrameArea()) - ( aRectFnSet.IsVert() ? nMaxHeight + 1 : 0 ) );
3390 SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFrame );
3391 const SwBorderAttrs &rAttrs = *aAccess.Get();
3394 SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame);
3395 aRectFnSet.SetPosX(aPrt, rAttrs.CalcLeft( pFrame ) );
3398 if( pPre )
3400 SwTwips nUpper = pFrame->CalcUpperSpace( &rAttrs, pPre );
3401 SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame);
3402 aRectFnSet.SetPosY(aPrt, nUpper );
3406 SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame);
3407 aRectFnSet.SetHeight( aPrt, std::max( tools::Long(0) , aRectFnSet.GetHeight(pFrame->getFrameArea()) - aRectFnSet.GetTop(aPrt) - nLower ) );
3408 aRectFnSet.SetWidth( aPrt, aRectFnSet.GetWidth(pFrame->getFrameArea()) - ( rAttrs.CalcLeft( pFrame ) + rAttrs.CalcRight( pFrame ) ) );
3411 pOldPara = pFrame->HasPara() ? pFrame->GetPara() : nullptr;
3412 pFrame->SetPara( new SwParaPortion(), false );
3413 OSL_ENSURE( ! pFrame->IsSwapped(), "A frame is swapped before Format_" );
3415 if ( pFrame->IsVertical() )
3416 pFrame->SwapWidthAndHeight();
3418 SwTextFormatInfo aInf( pFrame->getRootFrame()->GetCurrShell()->GetOut(), pFrame, false, true, true );
3419 SwTextFormatter aLine( pFrame, &aInf );
3421 pFrame->Format_( aLine, aInf );
3423 if ( pFrame->IsVertical() )
3424 pFrame->SwapWidthAndHeight();
3426 OSL_ENSURE( ! pFrame->IsSwapped(), "A frame is swapped after Format_" );
3429 SwTestFormat::~SwTestFormat()
3432 SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame);
3433 aFrm.setSwRect(aOldFrame);
3437 SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame);
3438 aPrt.setSwRect(aOldPrt);
3441 pFrame->SetPara( pOldPara );
3444 bool SwTextFrame::TestFormat( const SwFrame* pPrv, SwTwips &rMaxHeight, bool &bSplit )
3446 PROTOCOL_ENTER( this, PROT::TestFormat, DbgAction::NONE, nullptr )
3448 if( IsLocked() && GetUpper()->getFramePrintArea().Width() <= 0 )
3449 return false;
3451 SwTestFormat aSave( this, pPrv, rMaxHeight );
3453 return SwTextFrame::WouldFit(rMaxHeight, bSplit, true, false);
3457 * We should not and don't need to reformat.
3458 * We assume that we already formatted and that the formatting
3459 * data is still current.
3461 * We also assume that the frame width of the Master and Follow
3462 * are the same. That's why we're not calling FindBreak() for
3463 * FindOrphans().
3464 * The required height is coming from nMaxHeight.
3466 * @returns true if I can split
3468 bool SwTextFrame::WouldFit(SwTwips &rMaxHeight, bool &bSplit, bool bTst, bool bMoveBwd)
3470 OSL_ENSURE( ! IsVertical() || ! IsSwapped(),
3471 "SwTextFrame::WouldFit with swapped frame" );
3472 SwRectFnSet aRectFnSet(this);
3474 if( IsLocked() )
3475 return false;
3477 // it can happen that the IdleCollector removed the cached information
3478 if( !IsEmpty() )
3479 GetFormatted();
3481 // i#27801 - correction: 'short cut' for empty paragraph
3482 // can *not* be applied, if test format is in progress. The test format doesn't
3483 // adjust the frame and the printing area - see method <SwTextFrame::Format_(..)>,
3484 // which is called in <SwTextFrame::TestFormat(..)>
3485 if ( IsEmpty() && !bTst )
3487 bSplit = false;
3488 SwTwips nHeight = aRectFnSet.IsVert() ? getFramePrintArea().SSize().Width() : getFramePrintArea().SSize().Height();
3489 if( rMaxHeight < nHeight )
3490 return false;
3491 else
3493 rMaxHeight -= nHeight;
3494 return true;
3498 // GetPara can still be 0 in edge cases
3499 // We return true in order to be reformatted on the new Page
3500 OSL_ENSURE( HasPara() || IsHiddenNow(), "WouldFit: GetFormatted() and then !HasPara()" );
3501 if( !HasPara() || ( !aRectFnSet.GetHeight(getFrameArea()) && IsHiddenNow() ) )
3502 return true;
3504 // Because the Orphan flag only exists for a short moment, we also check
3505 // whether the Framesize is set to very huge by CalcPreps, in order to
3506 // force a MoveFwd
3507 if (IsWidow() || (aRectFnSet.IsVert()
3508 ? (0 == getFrameArea().Left())
3509 : (sw::WIDOW_MAGIC - 20000 < getFrameArea().Bottom())))
3511 SetWidow(false);
3512 if ( GetFollow() )
3514 // If we've ended up here due to a Widow request by our Follow, we check
3515 // whether there's a Follow with a real height at all.
3516 // Else (e.g. for newly created SctFrames) we ignore the IsWidow() and
3517 // still check if we can find enough room
3518 if (((!aRectFnSet.IsVert() && getFrameArea().Bottom() <= sw::WIDOW_MAGIC - 20000) ||
3519 ( aRectFnSet.IsVert() && 0 < getFrameArea().Left() ) ) &&
3520 ( GetFollow()->IsVertical() ?
3521 !GetFollow()->getFrameArea().Width() :
3522 !GetFollow()->getFrameArea().Height() ) )
3524 SwTextFrame* pFoll = GetFollow()->GetFollow();
3525 while( pFoll &&
3526 ( pFoll->IsVertical() ?
3527 !pFoll->getFrameArea().Width() :
3528 !pFoll->getFrameArea().Height() ) )
3529 pFoll = pFoll->GetFollow();
3530 if( pFoll )
3531 return false;
3533 else
3534 return false;
3538 SwSwapIfNotSwapped swap( this );
3540 SwTextSizeInfo aInf( this );
3541 SwTextMargin aLine( this, &aInf );
3543 WidowsAndOrphans aFrameBreak( this, rMaxHeight, bSplit );
3545 bool bRet = true;
3547 aLine.Bottom();
3548 // is breaking necessary?
3549 bSplit = !aFrameBreak.IsInside( aLine );
3550 if ( bSplit )
3551 bRet = !aFrameBreak.IsKeepAlways() && aFrameBreak.WouldFit(aLine, rMaxHeight, bTst, bMoveBwd);
3552 else
3554 // we need the total height including the current line
3555 aLine.Top();
3558 rMaxHeight -= aLine.GetLineHeight();
3559 } while ( aLine.Next() );
3562 return bRet;
3565 SwTwips SwTextFrame::GetParHeight() const
3567 OSL_ENSURE( ! IsVertical() || ! IsSwapped(),
3568 "SwTextFrame::GetParHeight with swapped frame" );
3570 if( !HasPara() )
3571 { // For non-empty paragraphs this is a special case
3572 // For UnderSized we can simply just ask 1 Twip more
3573 sal_uInt16 nRet = o3tl::narrowing<sal_uInt16>(getFramePrintArea().SSize().Height());
3574 if( IsUndersized() )
3576 if( IsEmpty() || GetText().isEmpty() )
3577 nRet = o3tl::narrowing<sal_uInt16>(EmptyHeight());
3578 else
3579 ++nRet;
3581 return nRet;
3584 // TODO: Refactor and improve code
3585 const SwLineLayout* pLineLayout = GetPara();
3586 SwTwips nHeight = pLineLayout ? pLineLayout->GetRealHeight() : 0;
3588 // Is this paragraph scrolled? Our height until now is at least
3589 // one line height too low then
3590 if( GetOffset() && !IsFollow() )
3591 nHeight *= 2;
3593 while ( pLineLayout && pLineLayout->GetNext() )
3595 pLineLayout = pLineLayout->GetNext();
3596 nHeight = nHeight + pLineLayout->GetRealHeight();
3599 return nHeight;
3603 * @returns this _always_ in the formatted state!
3605 SwTextFrame* SwTextFrame::GetFormatted( bool bForceQuickFormat )
3607 vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut();
3608 SwSwapIfSwapped swap( this );
3610 // In case the SwLineLayout was cleared out of the s_pTextCache, recreate it
3611 // Not for empty paragraphs
3612 if( !HasPara() && !(isFrameAreaDefinitionValid() && IsEmpty()) )
3614 // Calc() must be called, because frame position can be wrong
3615 const bool bFormat = isFrameAreaSizeValid();
3616 Calc(pRenderContext); // calls Format() if invalid
3618 // If the flags were valid (hence bFormat=true), Calc did nothing,
3619 // so Format() must be called manually in order to recreate
3620 // the SwLineLayout that has been deleted from the
3621 // SwTextFrame::s_pTextCache (hence !HasPara() above).
3622 // Optimization with FormatQuick()
3623 if( bFormat && !FormatQuick( bForceQuickFormat ) )
3624 Format(getRootFrame()->GetCurrShell()->GetOut());
3627 return this;
3630 SwTwips SwTextFrame::CalcFitToContent()
3632 // i#31490
3633 // If we are currently locked, we better return with a
3634 // fairly reasonable value:
3635 if ( IsLocked() )
3636 return getFramePrintArea().Width();
3638 SwParaPortion* pOldPara = GetPara();
3639 SwParaPortion *pDummy = new SwParaPortion();
3640 SetPara( pDummy, false );
3641 const SwPageFrame* pPage = FindPageFrame();
3643 const Point aOldFramePos = getFrameArea().Pos();
3644 const SwTwips nOldFrameWidth = getFrameArea().Width();
3645 const SwTwips nOldPrtWidth = getFramePrintArea().Width();
3646 const SwTwips nPageWidth = GetUpper()->IsVertical() ?
3647 pPage->getFramePrintArea().Height() :
3648 pPage->getFramePrintArea().Width();
3651 SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
3652 aFrm.Width( nPageWidth );
3656 SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
3657 aPrt.Width( nPageWidth );
3660 // i#25422 objects anchored as character in RTL
3661 if ( IsRightToLeft() )
3663 SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
3664 aFrm.Pos().AdjustX(nOldFrameWidth - nPageWidth );
3667 TextFrameLockGuard aLock( this );
3669 SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this, false, true, true );
3670 aInf.SetIgnoreFly( true );
3671 SwTextFormatter aLine( this, &aInf );
3672 SwHookOut aHook( aInf );
3674 // i#54031 - assure minimum of MINLAY twips.
3675 const SwTwips nMax = std::max( SwTwips(MINLAY), aLine.CalcFitToContent_() + 1 );
3678 SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
3679 aFrm.Width( nOldFrameWidth );
3681 // i#25422 objects anchored as character in RTL
3682 if ( IsRightToLeft() )
3684 aFrm.Pos() = aOldFramePos;
3689 SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
3690 aPrt.Width( nOldPrtWidth );
3693 SetPara( pOldPara );
3695 return nMax;
3699 * Simulate format for a list item paragraph, whose list level attributes
3700 * are in LABEL_ALIGNMENT mode, in order to determine additional first
3701 * line offset for the real text formatting due to the value of label
3702 * adjustment attribute of the list level.
3704 void SwTextFrame::CalcAdditionalFirstLineOffset()
3706 if ( IsLocked() )
3707 return;
3709 // reset additional first line offset
3710 mnAdditionalFirstLineOffset = 0;
3712 const SwTextNode* pTextNode( GetTextNodeForParaProps() );
3713 // sw_redlinehide: check that pParaPropsNode is the correct one
3714 assert(pTextNode->IsNumbered(getRootFrame()) == pTextNode->IsNumbered(nullptr));
3715 if (!(pTextNode->IsNumbered(getRootFrame()) &&
3716 pTextNode->IsCountedInList() && pTextNode->GetNumRule()))
3717 return;
3719 int nListLevel = pTextNode->GetActualListLevel();
3721 if (nListLevel < 0)
3722 nListLevel = 0;
3724 if (nListLevel >= MAXLEVEL)
3725 nListLevel = MAXLEVEL - 1;
3727 const SwNumFormat& rNumFormat =
3728 pTextNode->GetNumRule()->Get( o3tl::narrowing<sal_uInt16>(nListLevel) );
3729 if ( rNumFormat.GetPositionAndSpaceMode() != SvxNumberFormat::LABEL_ALIGNMENT )
3730 return;
3732 // keep current paragraph portion and apply dummy paragraph portion
3733 SwParaPortion* pOldPara = GetPara();
3734 SwParaPortion *pDummy = new SwParaPortion();
3735 SetPara( pDummy, false );
3737 // lock paragraph
3738 TextFrameLockGuard aLock( this );
3740 // simulate text formatting
3741 SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this, false, true, true );
3742 aInf.SetIgnoreFly( true );
3743 SwTextFormatter aLine( this, &aInf );
3744 SwHookOut aHook( aInf );
3745 aLine.CalcFitToContent_();
3747 // determine additional first line offset
3748 const SwLinePortion* pFirstPortion = aLine.GetCurr()->GetFirstPortion();
3749 if ( pFirstPortion->InNumberGrp() && !pFirstPortion->IsFootnoteNumPortion() )
3751 SwTwips nNumberPortionWidth( pFirstPortion->Width() );
3753 const SwLinePortion* pPortion = pFirstPortion->GetNextPortion();
3754 while ( pPortion &&
3755 pPortion->InNumberGrp() && !pPortion->IsFootnoteNumPortion())
3757 nNumberPortionWidth += pPortion->Width();
3758 pPortion = pPortion->GetNextPortion();
3761 if ( ( IsRightToLeft() &&
3762 rNumFormat.GetNumAdjust() == SvxAdjust::Left ) ||
3763 ( !IsRightToLeft() &&
3764 rNumFormat.GetNumAdjust() == SvxAdjust::Right ) )
3766 mnAdditionalFirstLineOffset = -nNumberPortionWidth;
3768 else if ( rNumFormat.GetNumAdjust() == SvxAdjust::Center )
3770 mnAdditionalFirstLineOffset = -(nNumberPortionWidth/2);
3774 // restore paragraph portion
3775 SetPara( pOldPara );
3779 * Determine the height of the last line for the calculation of
3780 * the proportional line spacing
3782 * Height of last line will be stored in new member
3783 * mnHeightOfLastLine and can be accessed via method
3784 * GetHeightOfLastLine()
3786 * @param _bUseFont force the usage of the former algorithm to
3787 * determine the height of the last line, which
3788 * uses the font
3790 void SwTextFrame::CalcHeightOfLastLine( const bool _bUseFont )
3792 // i#71281
3793 // Invalidate printing area, if height of last line changes
3794 const SwTwips nOldHeightOfLastLine( mnHeightOfLastLine );
3796 // determine output device
3797 SwViewShell* pVsh = getRootFrame()->GetCurrShell();
3798 OSL_ENSURE( pVsh, "<SwTextFrame::_GetHeightOfLastLineForPropLineSpacing()> - no SwViewShell" );
3800 // i#78921
3801 // There could be no <SwViewShell> instance in the case of loading a binary
3802 // StarOffice file format containing an embedded Writer document.
3803 if ( !pVsh )
3805 return;
3807 OutputDevice* pOut = pVsh->GetOut();
3808 const IDocumentSettingAccess *const pIDSA = &GetDoc().getIDocumentSettingAccess();
3809 if ( !pVsh->GetViewOptions()->getBrowseMode() ||
3810 pVsh->GetViewOptions()->IsPrtFormat() )
3812 pOut = GetDoc().getIDocumentDeviceAccess().getReferenceDevice( true );
3814 OSL_ENSURE( pOut, "<SwTextFrame::_GetHeightOfLastLineForPropLineSpacing()> - no OutputDevice" );
3816 if ( !pOut )
3818 return;
3821 // determine height of last line
3822 if ( _bUseFont || pIDSA->get(DocumentSettingId::OLD_LINE_SPACING ) )
3824 // former determination of last line height for proportional line
3825 // spacing - take height of font set at the paragraph
3826 // FIXME actually... must the font match across all nodes?
3827 SwFont aFont( &GetTextNodeForParaProps()->GetSwAttrSet(), pIDSA );
3829 // we must ensure that the font is restored correctly on the OutputDevice
3830 // otherwise Last!=Owner could occur
3831 if ( pLastFont )
3833 SwFntObj *pOldFont = pLastFont;
3834 pLastFont = nullptr;
3835 aFont.SetFntChg( true );
3836 aFont.ChgPhysFnt( pVsh, *pOut );
3837 mnHeightOfLastLine = aFont.GetHeight( pVsh, *pOut );
3838 assert(pLastFont && "coverity[var_deref_model] - pLastFont should be set in SwSubFont::ChgFnt");
3839 pLastFont->Unlock();
3840 pLastFont = pOldFont;
3841 pLastFont->SetDevFont( pVsh, *pOut );
3843 else
3845 vcl::Font aOldFont = pOut->GetFont();
3846 aFont.SetFntChg( true );
3847 aFont.ChgPhysFnt( pVsh, *pOut );
3848 mnHeightOfLastLine = aFont.GetHeight( pVsh, *pOut );
3849 assert(pLastFont && "coverity[var_deref_model] - pLastFont should be set in SwSubFont::ChgFnt");
3850 pLastFont->Unlock();
3851 pLastFont = nullptr;
3852 pOut->SetFont( aOldFont );
3855 else
3857 // new determination of last line height - take actually height of last line
3858 // i#89000
3859 // assure same results, if paragraph is undersized
3860 if ( IsUndersized() )
3862 mnHeightOfLastLine = 0;
3864 else
3866 bool bCalcHeightOfLastLine = true;
3867 if ( ( !HasPara() && IsEmpty( ) ) || GetText().isEmpty() )
3869 mnHeightOfLastLine = EmptyHeight();
3870 bCalcHeightOfLastLine = false;
3873 if ( bCalcHeightOfLastLine )
3875 OSL_ENSURE( HasPara(),
3876 "<SwTextFrame::CalcHeightOfLastLine()> - missing paragraph portions." );
3877 const SwLineLayout* pLineLayout = GetPara();
3878 while ( pLineLayout && pLineLayout->GetNext() )
3880 // iteration to last line
3881 pLineLayout = pLineLayout->GetNext();
3883 if ( pLineLayout )
3885 SwTwips nAscent, nDescent, nDummy1, nDummy2;
3886 // i#47162 - suppress consideration of
3887 // fly content portions and the line portion.
3888 pLineLayout->MaxAscentDescent( nAscent, nDescent,
3889 nDummy1, nDummy2,
3890 nullptr, true );
3891 // i#71281
3892 // Suppress wrong invalidation of printing area, if method is
3893 // called recursive.
3894 // Thus, member <mnHeightOfLastLine> is only set directly, if
3895 // no recursive call is needed.
3896 const SwTwips nNewHeightOfLastLine = nAscent + nDescent;
3897 // i#47162 - if last line only contains
3898 // fly content portions, <mnHeightOfLastLine> is zero.
3899 // In this case determine height of last line by the font
3900 if ( nNewHeightOfLastLine == 0 )
3902 CalcHeightOfLastLine( true );
3904 else
3906 mnHeightOfLastLine = nNewHeightOfLastLine;
3912 // i#71281
3913 // invalidate printing area, if height of last line changes
3914 if ( mnHeightOfLastLine != nOldHeightOfLastLine )
3916 InvalidatePrt();
3921 * Method returns the value of the inter line spacing for a text frame.
3922 * Such a value exists for proportional line spacings ("1,5 Lines",
3923 * "Double", "Proportional" and for leading line spacing ("Leading").
3925 * @param _bNoPropLineSpacing (default = false) control whether the
3926 * value of a proportional line spacing is
3927 * returned or not
3929 tools::Long SwTextFrame::GetLineSpace( const bool _bNoPropLineSpace ) const
3931 tools::Long nRet = 0;
3933 const SvxLineSpacingItem &rSpace = GetTextNodeForParaProps()->GetSwAttrSet().GetLineSpacing();
3935 switch( rSpace.GetInterLineSpaceRule() )
3937 case SvxInterLineSpaceRule::Prop:
3939 if ( _bNoPropLineSpace )
3941 break;
3944 // i#11860 - adjust spacing implementation for object positioning
3945 // - compatibility to MS Word
3946 nRet = GetHeightOfLastLine();
3948 tools::Long nTmp = nRet;
3949 nTmp *= rSpace.GetPropLineSpace();
3950 nTmp /= 100;
3951 nTmp -= nRet;
3952 if ( nTmp > 0 )
3953 nRet = nTmp;
3954 else
3955 nRet = 0;
3957 break;
3958 case SvxInterLineSpaceRule::Fix:
3960 if ( rSpace.GetInterLineSpace() > 0 )
3961 nRet = rSpace.GetInterLineSpace();
3963 break;
3964 default:
3965 break;
3967 return nRet;
3970 SwTwips SwTextFrame::FirstLineHeight() const
3972 if ( !HasPara() )
3974 if( IsEmpty() && isFrameAreaDefinitionValid() )
3975 return IsVertical() ? getFramePrintArea().Width() : getFramePrintArea().Height();
3976 return std::numeric_limits<SwTwips>::max();
3978 const SwParaPortion *pPara = GetPara();
3979 if ( !pPara )
3980 return std::numeric_limits<SwTwips>::max();
3982 // tdf#146500 Lines with only fly overlap cannot be "moved", so the idea
3983 // here is to continue until there's some text.
3984 // FIXME ideally we want to count a fly to the line in which it is anchored
3985 // - it may even be anchored in some other paragraph! SwFlyPortion doesn't
3986 // have a pointer sadly so no way to find out.
3987 SwTwips nHeight(0);
3988 for (SwLineLayout const* pLine = pPara; pLine; pLine = pLine->GetNext())
3990 nHeight += pLine->Height();
3991 if (::sw::FindNonFlyPortion(*pLine))
3993 break;
3996 return nHeight;
3999 sal_Int32 SwTextFrame::GetLineCount(TextFrameIndex const nPos)
4001 sal_Int32 nRet = 0;
4002 SwTextFrame *pFrame = this;
4005 pFrame->GetFormatted();
4006 if( !pFrame->HasPara() )
4007 break;
4008 SwTextSizeInfo aInf( pFrame );
4009 SwTextMargin aLine( pFrame, &aInf );
4010 if (TextFrameIndex(COMPLETE_STRING) == nPos)
4011 aLine.Bottom();
4012 else
4013 aLine.CharToLine( nPos );
4014 nRet = nRet + aLine.GetLineNr();
4015 pFrame = pFrame->GetFollow();
4016 } while ( pFrame && pFrame->GetOffset() <= nPos );
4017 return nRet;
4020 void SwTextFrame::ChgThisLines()
4022 // not necessary to format here (GetFormatted etc.), because we have to come from there!
4023 sal_Int32 nNew = 0;
4024 const SwLineNumberInfo &rInf = GetDoc().GetLineNumberInfo();
4025 if ( !GetText().isEmpty() && HasPara() )
4027 SwTextSizeInfo aInf( this );
4028 SwTextMargin aLine( this, &aInf );
4029 if ( rInf.IsCountBlankLines() )
4031 aLine.Bottom();
4032 nNew = aLine.GetLineNr();
4034 else
4038 if( aLine.GetCurr()->HasContent() )
4039 ++nNew;
4040 } while ( aLine.NextLine() );
4043 else if ( rInf.IsCountBlankLines() )
4044 nNew = 1;
4046 if ( nNew == mnThisLines )
4047 return;
4049 if (!IsInTab() && GetTextNodeForParaProps()->GetSwAttrSet().GetLineNumber().IsCount())
4051 mnAllLines -= mnThisLines;
4052 mnThisLines = nNew;
4053 mnAllLines += mnThisLines;
4054 SwFrame *pNxt = GetNextContentFrame();
4055 while( pNxt && pNxt->IsInTab() )
4057 pNxt = pNxt->FindTabFrame();
4058 if( nullptr != pNxt )
4059 pNxt = pNxt->FindNextCnt();
4061 if( pNxt )
4062 pNxt->InvalidateLineNum();
4064 // Extend repaint to the bottom.
4065 if ( HasPara() )
4067 SwRepaint& rRepaint = GetPara()->GetRepaint();
4068 rRepaint.Bottom( std::max( rRepaint.Bottom(),
4069 getFrameArea().Top()+getFramePrintArea().Bottom()));
4072 else // Paragraphs which are not counted should not manipulate the AllLines.
4073 mnThisLines = nNew;
4076 void SwTextFrame::RecalcAllLines()
4078 ValidateLineNum();
4080 if ( IsInTab() )
4081 return;
4083 const sal_Int32 nOld = GetAllLines();
4084 const SwFormatLineNumber &rLineNum = GetTextNodeForParaProps()->GetSwAttrSet().GetLineNumber();
4085 sal_Int32 nNewNum;
4086 const bool bRestart = GetDoc().GetLineNumberInfo().IsRestartEachPage();
4088 if ( !IsFollow() && rLineNum.GetStartValue() && rLineNum.IsCount() )
4089 nNewNum = rLineNum.GetStartValue() - 1;
4090 // If it is a follow or not has not be considered if it is a restart at each page; the
4091 // restart should also take effect at follows.
4092 else if ( bRestart && FindPageFrame()->FindFirstBodyContent() == this )
4094 nNewNum = 0;
4096 else
4098 SwContentFrame *pPrv = GetPrevContentFrame();
4099 while ( pPrv &&
4100 (pPrv->IsInTab() || pPrv->IsInDocBody() != IsInDocBody()) )
4101 pPrv = pPrv->GetPrevContentFrame();
4103 // i#78254 Restart line numbering at page change
4104 // First body content may be in table!
4105 if ( bRestart && pPrv && pPrv->FindPageFrame() != FindPageFrame() )
4106 pPrv = nullptr;
4108 nNewNum = pPrv ? static_cast<SwTextFrame*>(pPrv)->GetAllLines() : 0;
4110 if ( rLineNum.IsCount() )
4111 nNewNum += GetThisLines();
4113 if ( nOld == nNewNum )
4114 return;
4116 mnAllLines = nNewNum;
4117 SwContentFrame *pNxt = GetNextContentFrame();
4118 while ( pNxt &&
4119 (pNxt->IsInTab() || pNxt->IsInDocBody() != IsInDocBody()) )
4120 pNxt = pNxt->GetNextContentFrame();
4121 if ( pNxt )
4123 if ( pNxt->GetUpper() != GetUpper() )
4124 pNxt->InvalidateLineNum();
4125 else
4126 pNxt->InvalidateLineNum_();
4130 void SwTextFrame::VisitPortions( SwPortionHandler& rPH ) const
4132 const SwParaPortion* pPara = isFrameAreaDefinitionValid() ? GetPara() : nullptr;
4134 if (pPara)
4136 if ( IsFollow() )
4137 rPH.Skip( GetOffset() );
4139 const SwLineLayout* pLine = pPara;
4140 while ( pLine )
4142 const SwLinePortion* pPor = pLine->GetFirstPortion();
4143 while ( pPor )
4145 pPor->HandlePortion( rPH );
4146 pPor = pPor->GetNextPortion();
4149 rPH.LineBreak();
4150 pLine = pLine->GetNext();
4154 rPH.Finish();
4157 const SwScriptInfo* SwTextFrame::GetScriptInfo() const
4159 const SwParaPortion* pPara = GetPara();
4160 return pPara ? &pPara->GetScriptInfo() : nullptr;
4164 * Helper function for SwTextFrame::CalcBasePosForFly()
4166 static SwTwips lcl_CalcFlyBasePos( const SwTextFrame& rFrame, SwRect aFlyRect,
4167 SwTextFly const & rTextFly )
4169 SwRectFnSet aRectFnSet(&rFrame);
4170 SwTwips nRet = rFrame.IsRightToLeft() ?
4171 aRectFnSet.GetRight(rFrame.getFrameArea()) :
4172 aRectFnSet.GetLeft(rFrame.getFrameArea());
4176 SwRect aRect = rTextFly.GetFrame( aFlyRect );
4177 if ( 0 != aRectFnSet.GetWidth(aRect) )
4179 if ( rFrame.IsRightToLeft() )
4181 if ( aRectFnSet.GetRight(aRect) -
4182 aRectFnSet.GetRight(aFlyRect) >= 0 )
4184 aRectFnSet.SetRight(
4185 aFlyRect, aRectFnSet.GetLeft(aRect) );
4186 nRet = aRectFnSet.GetLeft(aRect);
4188 else
4189 break;
4191 else
4193 if ( aRectFnSet.GetLeft(aFlyRect) -
4194 aRectFnSet.GetLeft(aRect) >= 0 )
4196 aRectFnSet.SetLeft(
4197 aFlyRect, aRectFnSet.GetRight(aRect) + 1 );
4198 nRet = aRectFnSet.GetRight(aRect);
4200 else
4201 break;
4204 else
4205 break;
4207 while ( aRectFnSet.GetWidth(aFlyRect) > 0 );
4209 return nRet;
4212 void SwTextFrame::CalcBaseOfstForFly()
4214 OSL_ENSURE( !IsVertical() || !IsSwapped(),
4215 "SwTextFrame::CalcBasePosForFly with swapped frame!" );
4217 if (!GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_FLY_OFFSETS))
4218 return;
4220 SwRectFnSet aRectFnSet(this);
4222 SwRect aFlyRect( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() );
4224 // Get first 'real' line and adjust position and height of line rectangle.
4225 // Correct behaviour if no 'real' line exists
4226 // (empty paragraph with and without a dummy portion)
4227 SwTwips nFlyAnchorVertOfstNoWrap = 0;
4229 SwTwips nTop = aRectFnSet.GetTop(aFlyRect);
4230 const SwLineLayout* pLay = GetPara();
4231 SwTwips nLineHeight = 200;
4232 while( pLay && pLay->IsDummy() && pLay->GetNext() )
4234 nTop += pLay->Height();
4235 nFlyAnchorVertOfstNoWrap += pLay->Height();
4236 pLay = pLay->GetNext();
4238 if ( pLay )
4240 nLineHeight = pLay->Height();
4242 aRectFnSet.SetTopAndHeight( aFlyRect, nTop, nLineHeight );
4245 SwTextFly aTextFly( this );
4246 aTextFly.SetIgnoreCurrentFrame( true );
4247 aTextFly.SetIgnoreContour( true );
4248 // ignore objects in page header|footer for
4249 // text frames not in page header|footer
4250 aTextFly.SetIgnoreObjsInHeaderFooter( true );
4251 SwTwips nRet1 = lcl_CalcFlyBasePos( *this, aFlyRect, aTextFly );
4252 aTextFly.SetIgnoreCurrentFrame( false );
4253 SwTwips nRet2 = lcl_CalcFlyBasePos( *this, aFlyRect, aTextFly );
4255 // make values relative to frame start position
4256 SwTwips nLeft = IsRightToLeft() ?
4257 aRectFnSet.GetRight(getFrameArea()) :
4258 aRectFnSet.GetLeft(getFrameArea());
4260 mnFlyAnchorOfst = nRet1 - nLeft;
4261 mnFlyAnchorOfstNoWrap = nRet2 - nLeft;
4263 if (!GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS))
4264 return;
4266 if (mnFlyAnchorOfstNoWrap > 0)
4267 mnFlyAnchorVertOfstNoWrap = nFlyAnchorVertOfstNoWrap;
4270 SwTwips SwTextFrame::GetBaseVertOffsetForFly(bool bIgnoreFlysAnchoredAtThisFrame) const
4272 return bIgnoreFlysAnchoredAtThisFrame ? 0 : mnFlyAnchorVertOfstNoWrap;
4276 * Repaint all text frames of the given text node
4278 void SwTextFrame::repaintTextFrames( const SwTextNode& rNode )
4280 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rNode);
4281 for( const SwTextFrame *pFrame = aIter.First(); pFrame; pFrame = aIter.Next() )
4283 SwRect aRec( pFrame->GetPaintArea() );
4284 const SwRootFrame *pRootFrame = pFrame->getRootFrame();
4285 SwViewShell *pCurShell = pRootFrame ? pRootFrame->GetCurrShell() : nullptr;
4286 if( pCurShell )
4287 pCurShell->InvalidateWindows( aRec );
4291 void SwTextFrame::UpdateOutlineContentVisibilityButton(SwWrtShell* pWrtSh) const
4293 if (pWrtSh && pWrtSh->GetViewOptions()->IsShowOutlineContentVisibilityButton() &&
4294 GetTextNodeFirst()->IsOutline())
4296 SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin();
4297 SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager();
4298 rMngr.SetOutlineContentVisibilityButton(this);
4302 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */