summaryrefslogtreecommitdiffstats
path: root/sw/source/core/txtnode
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sw/source/core/txtnode
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/core/txtnode')
-rw-r--r--sw/source/core/txtnode/GrammarContact.cxx166
-rw-r--r--sw/source/core/txtnode/OnlineAccessibilityCheck.cxx333
-rw-r--r--sw/source/core/txtnode/atrfld.cxx797
-rw-r--r--sw/source/core/txtnode/atrflyin.cxx306
-rw-r--r--sw/source/core/txtnode/atrftn.cxx612
-rw-r--r--sw/source/core/txtnode/atrref.cxx225
-rw-r--r--sw/source/core/txtnode/atrtox.cxx90
-rw-r--r--sw/source/core/txtnode/attrcontentcontrol.cxx875
-rw-r--r--sw/source/core/txtnode/attrlinebreak.cxx139
-rw-r--r--sw/source/core/txtnode/chrfmt.cxx144
-rw-r--r--sw/source/core/txtnode/fmtatr2.cxx867
-rw-r--r--sw/source/core/txtnode/fntcache.cxx2274
-rw-r--r--sw/source/core/txtnode/fntcap.cxx782
-rw-r--r--sw/source/core/txtnode/justify.cxx250
-rw-r--r--sw/source/core/txtnode/justify.hxx65
-rw-r--r--sw/source/core/txtnode/modeltoviewhelper.cxx468
-rw-r--r--sw/source/core/txtnode/ndhints.cxx486
-rw-r--r--sw/source/core/txtnode/ndtxt.cxx5587
-rw-r--r--sw/source/core/txtnode/swfntcch.cxx75
-rw-r--r--sw/source/core/txtnode/swfont.cxx1497
-rw-r--r--sw/source/core/txtnode/thints.cxx3588
-rw-r--r--sw/source/core/txtnode/txatbase.cxx187
-rw-r--r--sw/source/core/txtnode/txatritr.cxx213
-rw-r--r--sw/source/core/txtnode/txtatr2.cxx314
-rw-r--r--sw/source/core/txtnode/txtedt.cxx2404
25 files changed, 22744 insertions, 0 deletions
diff --git a/sw/source/core/txtnode/GrammarContact.cxx b/sw/source/core/txtnode/GrammarContact.cxx
new file mode 100644
index 0000000000..47e2fac264
--- /dev/null
+++ b/sw/source/core/txtnode/GrammarContact.cxx
@@ -0,0 +1,166 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <GrammarContact.hxx>
+#include <doc.hxx>
+#include <pam.hxx>
+#include <ndtxt.hxx>
+#include <txtfrm.hxx>
+
+namespace sw
+{
+
+GrammarContact::GrammarContact()
+ : m_aTimer( "sw::SwGrammarContact TimerRepaint" ),
+ m_isFinished( false ),
+ m_pTextNode(nullptr)
+{
+ m_aTimer.SetTimeout( 2000 ); // Repaint of grammar check after 'setChecked'
+ m_aTimer.SetInvokeHandler( LINK(this, GrammarContact, TimerRepaint) );
+}
+
+void GrammarContact::CheckBroadcaster()
+{
+ if (HasBroadcaster())
+ return;
+ m_pTextNode = nullptr;
+ m_pProxyList.reset();
+}
+
+IMPL_LINK( GrammarContact, TimerRepaint, Timer *, pTimer, void )
+{
+ CheckBroadcaster();
+ if( pTimer )
+ {
+ pTimer->Stop();
+ if( m_pTextNode )
+ { //Replace the old wrong list by the proxy list and repaint all frames
+ m_pTextNode->SetGrammarCheck( std::move(m_pProxyList) );
+ SwTextFrame::repaintTextFrames( *m_pTextNode );
+ }
+ }
+}
+
+/* I'm always a client of the current paragraph */
+void GrammarContact::updateCursorPosition( const SwPosition& rNewPos )
+{
+ CheckBroadcaster();
+ SwTextNode* pTextNode = rNewPos.GetNode().GetTextNode();
+ if( pTextNode == m_pTextNode ) // paragraph has been changed
+ return;
+
+ m_aTimer.Stop();
+ if( m_pTextNode ) // My last paragraph has been left
+ {
+ if( m_pProxyList )
+ { // replace old list by the proxy list and repaint
+ m_pTextNode->SetGrammarCheck( std::move(m_pProxyList) );
+ SwTextFrame::repaintTextFrames( *m_pTextNode );
+ }
+ EndListeningAll();
+ }
+ if( pTextNode )
+ {
+ m_pTextNode = pTextNode;
+ EndListeningAll();
+ StartListening(pTextNode->GetNotifier()); // welcome new paragraph
+ }
+}
+
+/* deliver a grammar check list for the given text node */
+SwGrammarMarkUp* GrammarContact::getGrammarCheck( SwTextNode& rTextNode, bool bCreate )
+{
+ SwGrammarMarkUp *pRet = nullptr;
+ CheckBroadcaster();
+ if( m_pTextNode == &rTextNode ) // hey, that's my current paragraph!
+ { // so you will get a proxy list...
+ if( bCreate )
+ {
+ if( m_isFinished )
+ {
+ m_pProxyList.reset();
+ }
+ if( !m_pProxyList )
+ {
+ if( rTextNode.GetGrammarCheck() )
+ m_pProxyList.reset( static_cast<SwGrammarMarkUp*>(rTextNode.GetGrammarCheck()->Clone()) );
+ else
+ {
+ m_pProxyList.reset( new SwGrammarMarkUp() );
+ m_pProxyList->SetInvalid( 0, COMPLETE_STRING );
+ }
+ }
+ m_isFinished = false;
+ }
+ pRet = m_pProxyList.get();
+ }
+ else
+ {
+ pRet = rTextNode.GetGrammarCheck(); // do you have already a list?
+ if( bCreate && !pRet ) // do you want to create a list?
+ {
+ pRet = new SwGrammarMarkUp();
+ pRet->SetInvalid( 0, COMPLETE_STRING );
+ rTextNode.SetGrammarCheck( std::unique_ptr<SwGrammarMarkUp>(pRet) );
+ rTextNode.SetGrammarCheckDirty( true );
+ }
+ }
+ return pRet;
+}
+
+void GrammarContact::finishGrammarCheck( SwTextNode& rTextNode )
+{
+ CheckBroadcaster();
+ if( &rTextNode != m_pTextNode ) // not my paragraph
+ SwTextFrame::repaintTextFrames( rTextNode ); // can be repainted directly
+ else
+ {
+ if( m_pProxyList )
+ {
+ m_isFinished = true;
+ m_aTimer.Start(); // will replace old list and repaint with delay
+ }
+ else if( m_pTextNode->GetGrammarCheck() )
+ { // all grammar problems seems to be gone, no delay needed
+ m_pTextNode->ClearGrammarCheck();
+ SwTextFrame::repaintTextFrames( *m_pTextNode );
+ }
+ }
+}
+
+sw::GrammarContact* getGrammarContactFor(const SwTextNode& rTextNode)
+{
+ const SwDoc& rDoc = rTextNode.GetDoc();
+ if (rDoc.IsInDtor())
+ return nullptr;
+ return rDoc.getGrammarContact().get();
+}
+
+void finishGrammarCheckFor(SwTextNode& rTextNode)
+{
+ sw::GrammarContact* pGrammarContact = getGrammarContactFor(rTextNode);
+ if (pGrammarContact)
+ {
+ pGrammarContact->finishGrammarCheck(rTextNode);
+ }
+}
+
+} // end sw
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/OnlineAccessibilityCheck.cxx b/sw/source/core/txtnode/OnlineAccessibilityCheck.cxx
new file mode 100644
index 0000000000..d85379ffb1
--- /dev/null
+++ b/sw/source/core/txtnode/OnlineAccessibilityCheck.cxx
@@ -0,0 +1,333 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <OnlineAccessibilityCheck.hxx>
+#include <doc.hxx>
+#include <pam.hxx>
+#include <txtfrm.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/dispatch.hxx>
+#include <docsh.hxx>
+#include <cmdid.h>
+#include <officecfg/Office/Common.hxx>
+#include <unotools/configmgr.hxx>
+
+namespace sw
+{
+WeakNodeContainer::WeakNodeContainer(SwNode* pNode)
+ : m_pNode(pNode)
+{
+ if (m_pNode)
+ {
+ auto* pBroadcast = dynamic_cast<sw::BroadcastingModify*>(m_pNode);
+ if (pBroadcast)
+ {
+ EndListeningAll();
+ StartListening(pBroadcast->GetNotifier());
+ }
+ else
+ {
+ m_pNode = nullptr;
+ }
+ }
+}
+
+WeakNodeContainer::~WeakNodeContainer() { EndListeningAll(); }
+
+bool WeakNodeContainer::isAlive()
+{
+ if (!m_pNode)
+ return false;
+ if (!HasBroadcaster())
+ m_pNode = nullptr;
+ return m_pNode;
+}
+
+SwNode* WeakNodeContainer::getNode()
+{
+ if (isAlive())
+ return m_pNode;
+ return nullptr;
+}
+
+OnlineAccessibilityCheck::OnlineAccessibilityCheck(SwDoc& rDocument)
+ : m_rDocument(rDocument)
+ , m_aAccessibilityCheck(&m_rDocument)
+ , m_nPreviousNodeIndex(-1)
+ , m_nAccessibilityIssues(0)
+ , m_bInitialCheck(false)
+ , m_bOnlineCheckStatus(
+ !utl::ConfigManager::IsFuzzing()
+ ? officecfg::Office::Common::Accessibility::OnlineAccessibilityCheck::get()
+ : false)
+{
+}
+
+void OnlineAccessibilityCheck::updateNodeStatus(SwNode* pNode, bool bIssueObjectNameChanged)
+{
+ if (!pNode->IsContentNode() && !pNode->IsTableNode())
+ return;
+
+ m_nAccessibilityIssues = 0;
+
+ if (bIssueObjectNameChanged)
+ return;
+
+ auto it = m_aNodes.find(pNode);
+ if (it == m_aNodes.end())
+ {
+ m_aNodes.emplace(pNode, std::make_unique<WeakNodeContainer>(pNode));
+ }
+
+ for (auto iterator = m_aNodes.begin(); iterator != m_aNodes.end();)
+ {
+ auto& pWeakContentNode = iterator->second;
+ if (pWeakContentNode->isAlive())
+ {
+ auto& rStatus = pWeakContentNode->getNode()->getAccessibilityCheckStatus();
+ if (rStatus.pCollection)
+ {
+ m_nAccessibilityIssues += rStatus.pCollection->getIssues().size();
+ ++iterator;
+ }
+ else
+ {
+ iterator = m_aNodes.erase(iterator);
+ }
+ }
+ else
+ {
+ iterator = m_aNodes.erase(iterator);
+ }
+ }
+}
+
+void OnlineAccessibilityCheck::updateStatusbar()
+{
+ SfxBindings* pBindings = m_rDocument.GetDocShell() && m_rDocument.GetDocShell()->GetDispatcher()
+ ? m_rDocument.GetDocShell()->GetDispatcher()->GetBindings()
+ : nullptr;
+ if (pBindings)
+ pBindings->Invalidate(FN_STAT_ACCESSIBILITY_CHECK);
+}
+
+void OnlineAccessibilityCheck::runAccessibilityCheck(SwNode* pNode)
+{
+ m_aAccessibilityCheck.getIssueCollection().clear();
+
+ m_aAccessibilityCheck.checkNode(pNode);
+
+ for (SwFrameFormat* const& pFrameFormat : pNode->GetAnchoredFlys())
+ {
+ SdrObject* pObject = pFrameFormat->FindSdrObject();
+ if (pObject)
+ m_aAccessibilityCheck.checkObject(pNode, pObject);
+ }
+
+ auto aCollection = m_aAccessibilityCheck.getIssueCollection();
+
+ pNode->getAccessibilityCheckStatus().pCollection
+ = std::make_unique<sfx::AccessibilityIssueCollection>(aCollection);
+}
+
+void OnlineAccessibilityCheck::runDocumentLevelAccessibilityCheck()
+{
+ m_aAccessibilityCheck.getIssueCollection().clear();
+ m_aAccessibilityCheck.checkDocumentProperties();
+ auto aCollection = m_aAccessibilityCheck.getIssueCollection();
+ m_pDocumentAccessibilityIssues
+ = std::make_unique<sfx::AccessibilityIssueCollection>(aCollection);
+}
+
+void OnlineAccessibilityCheck::initialCheck()
+{
+ if (m_bInitialCheck)
+ return;
+
+ runDocumentLevelAccessibilityCheck();
+
+ auto const& pNodes = m_rDocument.GetNodes();
+ for (SwNodeOffset n(0); n < pNodes.Count(); ++n)
+ {
+ SwNode* pNode = pNodes[n];
+ if (pNode)
+ {
+ runAccessibilityCheck(pNode);
+ updateNodeStatus(pNode);
+ }
+ }
+
+ updateStatusbar();
+
+ m_bInitialCheck = true;
+}
+
+void OnlineAccessibilityCheck::updateCheckerActivity()
+{
+ bool bOnlineCheckStatus
+ = !utl::ConfigManager::IsFuzzing()
+ && officecfg::Office::Common::Accessibility::OnlineAccessibilityCheck::get();
+
+ if (bOnlineCheckStatus != m_bOnlineCheckStatus)
+ {
+ m_pPreviousNode.reset();
+ m_nPreviousNodeIndex = SwNodeOffset(-1);
+ m_bInitialCheck = false; // force initial check
+
+ if (!bOnlineCheckStatus)
+ {
+ clearAccessibilityIssuesFromAllNodes(); // cleanup all accessibility check data on nodes
+ m_nAccessibilityIssues = -1;
+ }
+ else
+ {
+ m_nAccessibilityIssues = 0;
+ }
+
+ m_bOnlineCheckStatus = bOnlineCheckStatus;
+
+ updateStatusbar();
+ }
+}
+
+void OnlineAccessibilityCheck::update(const SwPosition& rNewPos)
+{
+ updateCheckerActivity();
+
+ if (!m_bOnlineCheckStatus)
+ return;
+
+ initialCheck();
+
+ lookForPreviousNodeAndUpdate(rNewPos);
+}
+
+void OnlineAccessibilityCheck::lookForPreviousNodeAndUpdate(const SwPosition& rNewPos)
+{
+ auto nCurrenNodeIndex = rNewPos.GetNodeIndex();
+ auto* pCurrentNode = &rNewPos.GetNode();
+
+ if (!pCurrentNode->IsContentNode() && !pCurrentNode->IsTableNode())
+ return;
+
+ auto pCurrentWeak = std::make_unique<WeakNodeContainer>(pCurrentNode);
+ if (!pCurrentWeak->isAlive())
+ return;
+
+ // Check if previous node was deleted
+ if (!m_pPreviousNode || !m_pPreviousNode->isAlive())
+ {
+ m_pPreviousNode = std::move(pCurrentWeak);
+ m_nPreviousNodeIndex = nCurrenNodeIndex;
+ return;
+ }
+
+ // Check if node index changed
+ if (nCurrenNodeIndex == m_nPreviousNodeIndex)
+ return;
+
+ // Check if previous node is valid
+ if (m_nPreviousNodeIndex < SwNodeOffset(0)
+ || m_nPreviousNodeIndex >= pCurrentNode->GetNodes().Count())
+ {
+ m_pPreviousNode = std::move(pCurrentWeak);
+ m_nPreviousNodeIndex = nCurrenNodeIndex;
+ return;
+ }
+
+ // Run the document level Accessibility Check
+ runDocumentLevelAccessibilityCheck();
+
+ // Get the real previous node from index
+ SwNode* pNode = pCurrentNode->GetNodes()[m_nPreviousNodeIndex];
+
+ if (pNode && (pNode->IsContentNode() || pNode->IsTableNode()))
+ {
+ runAccessibilityCheck(pNode);
+ updateNodeStatus(pNode);
+
+ // Assign previous node and index
+ m_pPreviousNode = std::move(pCurrentWeak);
+ m_nPreviousNodeIndex = nCurrenNodeIndex;
+ }
+ else
+ {
+ runAccessibilityCheck(pCurrentNode);
+ updateNodeStatus(pCurrentNode);
+
+ m_pPreviousNode.reset();
+ m_nPreviousNodeIndex = SwNodeOffset(-1);
+ }
+
+ updateStatusbar();
+}
+
+void OnlineAccessibilityCheck::clearAccessibilityIssuesFromAllNodes()
+{
+ auto const& pNodes = m_rDocument.GetNodes();
+ for (SwNodeOffset n(0); n < pNodes.Count(); ++n)
+ {
+ SwNode* pNode = pNodes[n];
+ if (pNode)
+ {
+ pNode->getAccessibilityCheckStatus().reset();
+ }
+ }
+
+ m_aNodes.clear();
+ updateStatusbar();
+}
+
+void OnlineAccessibilityCheck::resetAndQueue(SwNode* pNode, bool bIssueObjectNameChanged)
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return;
+
+ bool bOnlineCheckStatus
+ = officecfg::Office::Common::Accessibility::OnlineAccessibilityCheck::get();
+ if (!bOnlineCheckStatus)
+ return;
+
+ pNode->getAccessibilityCheckStatus().reset();
+ m_aNodes.erase(pNode);
+ if (&pNode->GetNodes() == &m_rDocument.GetNodes()) // don't add undo array
+ {
+ runAccessibilityCheck(pNode);
+ updateNodeStatus(pNode, bIssueObjectNameChanged);
+ }
+ updateStatusbar();
+}
+
+void OnlineAccessibilityCheck::resetAndQueueDocumentLevel()
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return;
+
+ bool bOnlineCheckStatus
+ = officecfg::Office::Common::Accessibility::OnlineAccessibilityCheck::get();
+ if (!bOnlineCheckStatus)
+ return;
+
+ runDocumentLevelAccessibilityCheck();
+ updateStatusbar();
+}
+
+} // end sw
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/atrfld.cxx b/sw/source/core/txtnode/atrfld.cxx
new file mode 100644
index 0000000000..47e78ce2c9
--- /dev/null
+++ b/sw/source/core/txtnode/atrfld.cxx
@@ -0,0 +1,797 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <fmtfld.hxx>
+
+#include <libxml/xmlwriter.h>
+
+#include <fldbas.hxx>
+#include <txtfld.hxx>
+#include <txtannotationfld.hxx>
+#include <docfld.hxx>
+#include <docufld.hxx>
+#include <doc.hxx>
+#include <unofield.hxx>
+
+#include <pam.hxx>
+#include <reffld.hxx>
+#include <ddefld.hxx>
+#include <usrfld.hxx>
+#include <expfld.hxx>
+#include <ndtxt.hxx>
+#include <hints.hxx>
+#include <IDocumentFieldsAccess.hxx>
+#include <IDocumentMarkAccess.hxx>
+#include <IDocumentLayoutAccess.hxx>
+#include <fieldhint.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+
+// constructor for default item in attribute-pool
+SwFormatField::SwFormatField( sal_uInt16 nWhich )
+ : SfxPoolItem( nWhich )
+ , SfxBroadcaster()
+ , mpTextField( nullptr )
+{
+}
+
+SwFormatField::SwFormatField( const SwField &rField )
+ : SfxPoolItem( RES_TXTATR_FIELD )
+ , SfxBroadcaster()
+ , mpField( rField.CopyField() )
+ , mpTextField( nullptr )
+{
+ rField.GetTyp()->Add(this);
+ if ( mpField->GetTyp()->Which() == SwFieldIds::Input )
+ {
+ // input field in-place editing
+ SetWhich( RES_TXTATR_INPUTFIELD );
+ static_cast<SwInputField*>(mpField.get())->SetFormatField( *this );
+ }
+ else if (mpField->GetTyp()->Which() == SwFieldIds::SetExp)
+ {
+ // see SwWrtShell::StartInputFieldDlg
+ SwSetExpField *const pSetField(static_cast<SwSetExpField *>(mpField.get()));
+ if (pSetField->GetInputFlag()
+ // only for string fields for now - inline editing of number fields
+ // tends to produce error messages...
+ && (static_cast<SwSetExpFieldType*>(pSetField->GetTyp())->GetType()
+ & nsSwGetSetExpType::GSE_STRING))
+ {
+ SetWhich( RES_TXTATR_INPUTFIELD );
+ }
+ pSetField->SetFormatField(*this);
+ }
+ else if ( mpField->GetTyp()->Which() == SwFieldIds::Postit )
+ {
+ // text annotation field
+ SetWhich( RES_TXTATR_ANNOTATION );
+ }
+}
+
+// #i24434#
+// Since Items are used in ItemPool and in default constructed ItemSets with
+// full pool range, all items need to be clonable. Thus, this one needed to be
+// corrected
+SwFormatField::SwFormatField( const SwFormatField& rAttr )
+ : SfxPoolItem( rAttr )
+ , SfxBroadcaster()
+ , mpTextField( nullptr )
+{
+ if ( !rAttr.mpField )
+ return;
+
+ rAttr.mpField->GetTyp()->Add(this);
+ mpField = rAttr.mpField->CopyField();
+ if ( mpField->GetTyp()->Which() == SwFieldIds::Input )
+ {
+ // input field in-place editing
+ SetWhich( RES_TXTATR_INPUTFIELD );
+ SwInputField *pField = dynamic_cast<SwInputField*>(mpField.get());
+ assert(pField);
+ if (pField)
+ pField->SetFormatField( *this );
+ }
+ else if (mpField->GetTyp()->Which() == SwFieldIds::SetExp)
+ {
+ SwSetExpField *const pSetField(static_cast<SwSetExpField *>(mpField.get()));
+ if (pSetField->GetInputFlag()
+ && (static_cast<SwSetExpFieldType*>(pSetField->GetTyp())->GetType()
+ & nsSwGetSetExpType::GSE_STRING))
+ {
+ SetWhich( RES_TXTATR_INPUTFIELD );
+ }
+ // see SwWrtShell::StartInputFieldDlg
+ pSetField->SetFormatField(*this);
+ }
+ else if ( mpField->GetTyp()->Which() == SwFieldIds::Postit )
+ {
+ // text annotation field
+ SetWhich( RES_TXTATR_ANNOTATION );
+ }
+}
+
+SwFormatField::~SwFormatField()
+{
+ SwFieldType* pType = mpField ? mpField->GetTyp() : nullptr;
+
+ if (pType && pType->Which() == SwFieldIds::Database)
+ pType = nullptr; // DB field types destroy themselves
+
+ Broadcast( SwFormatFieldHint( this, SwFormatFieldHintWhich::REMOVED ) );
+ mpField.reset();
+
+ // some fields need to delete their field type
+ if( !(pType && pType->HasOnlyOneListener()) )
+ return;
+
+ bool bDel = false;
+ switch( pType->Which() )
+ {
+ case SwFieldIds::User:
+ bDel = static_cast<SwUserFieldType*>(pType)->IsDeleted();
+ break;
+
+ case SwFieldIds::SetExp:
+ bDel = static_cast<SwSetExpFieldType*>(pType)->IsDeleted();
+ break;
+
+ case SwFieldIds::Dde:
+ bDel = static_cast<SwDDEFieldType*>(pType)->IsDeleted();
+ break;
+ default: break;
+ }
+
+ if( bDel )
+ {
+ // unregister before deleting
+ pType->Remove( this );
+ delete pType;
+ }
+}
+
+void SwFormatField::RegisterToFieldType( SwFieldType& rType )
+{
+ rType.Add(this);
+}
+
+void SwFormatField::SetField(std::unique_ptr<SwField> _pField)
+{
+ mpField = std::move(_pField);
+ if ( mpField->GetTyp()->Which() == SwFieldIds::Input )
+ {
+ static_cast<SwInputField* >(mpField.get())->SetFormatField( *this );
+ }
+ else if (mpField->GetTyp()->Which() == SwFieldIds::SetExp)
+ {
+ // see SwWrtShell::StartInputFieldDlg
+ static_cast<SwSetExpField *>(mpField.get())->SetFormatField(*this);
+ }
+ Broadcast( SwFormatFieldHint( this, SwFormatFieldHintWhich::CHANGED ) );
+}
+
+void SwFormatField::SetTextField( SwTextField& rTextField )
+{
+ mpTextField = &rTextField;
+}
+
+void SwFormatField::ClearTextField()
+{
+ mpTextField = nullptr;
+}
+
+bool SwFormatField::operator==( const SfxPoolItem& rAttr ) const
+{
+ assert(SfxPoolItem::operator==(rAttr));
+ return ( mpField
+ && static_cast<const SwFormatField&>(rAttr).mpField
+ && mpField->GetTyp() == static_cast<const SwFormatField&>(rAttr).mpField->GetTyp()
+ && mpField->GetFormat() == static_cast<const SwFormatField&>(rAttr).mpField->GetFormat() )
+ ||
+ ( !mpField && !static_cast<const SwFormatField&>(rAttr).mpField );
+}
+
+SwFormatField* SwFormatField::Clone( SfxItemPool* ) const
+{
+ return new SwFormatField( *this );
+}
+
+void SwFormatField::InvalidateField()
+{
+ const SwPtrMsgPoolItem aItem(RES_REMOVE_UNO_OBJECT, &static_cast<sw::BroadcastingModify&>(*this));
+ CallSwClientNotify(sw::LegacyModifyHint{ &aItem, &aItem });
+}
+
+void SwFormatField::SwClientNotify( const SwModify& rModify, const SfxHint& rHint )
+{
+ SwClient::SwClientNotify(rModify, rHint);
+ if (rHint.GetId() == SfxHintId::SwAutoFormatUsedHint) {
+ if(mpTextField)
+ static_cast<const sw::AutoFormatUsedHint&>(rHint).CheckNode(mpTextField->GetpTextNode());
+ return;
+ }
+ else if (rHint.GetId() == SfxHintId::SwField)
+ {
+ const auto pFieldHint = static_cast<const SwFieldHint*>( &rHint );
+ // replace field content by text
+ SwPaM* pPaM = pFieldHint->m_pPaM;
+ pPaM->DeleteMark(); // TODO: this is really hackish
+
+ if( !mpTextField )
+ return;
+
+ SwDoc& rDoc = pPaM->GetDoc();
+ const SwTextNode& rTextNode = mpTextField->GetTextNode();
+ pPaM->GetPoint()->Assign(rTextNode, mpTextField->GetStart());
+
+ OUString const aEntry(mpField->ExpandField(rDoc.IsClipBoard(), pFieldHint->m_pLayout));
+ pPaM->SetMark();
+ pPaM->Move( fnMoveForward );
+ rDoc.getIDocumentContentOperations().DeleteRange( *pPaM );
+ rDoc.getIDocumentContentOperations().InsertString( *pPaM, aEntry );
+ }
+ else if (rHint.GetId() == SfxHintId::SwLegacyModify)
+ {
+ if(!mpTextField)
+ return;
+ UpdateTextNode(rHint);
+ }
+ else if (rHint.GetId() == SfxHintId::SwFindFormatForField)
+ {
+ const auto pFindForFieldHint = static_cast<const sw::FindFormatForFieldHint*>( &rHint );
+ if(pFindForFieldHint->m_rpFormat == nullptr && pFindForFieldHint->m_pField == GetField())
+ pFindForFieldHint->m_rpFormat = this;
+ }
+ else if (rHint.GetId() == SfxHintId::SwFindFormatForPostItId)
+ {
+ const auto pFindForPostItIdHint = static_cast<const sw::FindFormatForPostItIdHint*>( &rHint );
+ auto pPostItField = dynamic_cast<SwPostItField*>(mpField.get());
+ if(pPostItField && pFindForPostItIdHint->m_rpFormat == nullptr && pFindForPostItIdHint->m_nPostItId == pPostItField->GetPostItId())
+ pFindForPostItIdHint->m_rpFormat = this;
+ }
+ else if (rHint.GetId() == SfxHintId::SwCollectPostIts)
+ {
+ const auto pCollectPostItsHint = static_cast<const sw::CollectPostItsHint*>( &rHint );
+ if(GetTextField() && IsFieldInDoc() && (!pCollectPostItsHint->m_bHideRedlines || !sw::IsFieldDeletedInModel(pCollectPostItsHint->m_rIDRA, *GetTextField())))
+ pCollectPostItsHint->m_rvFormatFields.push_back(this);
+ }
+ else if (rHint.GetId() == SfxHintId::SwHasHiddenInformationNotes)
+ {
+ const auto pHasHiddenInfoHint = static_cast<const sw::HasHiddenInformationNotesHint*>( &rHint );
+ if(!pHasHiddenInfoHint->m_rbHasHiddenInformationNotes && GetTextField() && IsFieldInDoc())
+ pHasHiddenInfoHint->m_rbHasHiddenInformationNotes = true;
+ }
+ else if (rHint.GetId() == SfxHintId::SwGatherNodeIndex)
+ {
+ const auto pGatherNodeIndexHint = static_cast<const sw::GatherNodeIndexHint*>( &rHint );
+ if(auto pTextField = GetTextField())
+ pGatherNodeIndexHint->m_rvNodeIndex.push_back(pTextField->GetTextNode().GetIndex());
+ }
+ else if (rHint.GetId() == SfxHintId::SwGatherRefFields)
+ {
+ const auto pGatherRefFieldsHint = static_cast<const sw::GatherRefFieldsHint*>( &rHint );
+ if(!GetTextField() || pGatherRefFieldsHint->m_nType != GetField()->GetSubType())
+ return;
+ SwTextNode* pNd = GetTextField()->GetpTextNode();
+ if(pNd && pNd->GetNodes().IsDocNodes())
+ pGatherRefFieldsHint->m_rvRFields.push_back(static_cast<SwGetRefField*>(GetField()));
+ }
+ else if (rHint.GetId() == SfxHintId::SwGatherFields)
+ {
+ const auto pGatherFieldsHint = static_cast<const sw::GatherFieldsHint*>( &rHint );
+ if(pGatherFieldsHint->m_bCollectOnlyInDocNodes)
+ {
+ if(!GetTextField())
+ return;
+ SwTextNode* pNd = GetTextField()->GetpTextNode();
+ if(!pNd || !pNd->GetNodes().IsDocNodes())
+ return;
+ }
+ pGatherFieldsHint->m_rvFields.push_back(this);
+ }
+ else if (rHint.GetId() == SfxHintId::SwDocPosUpdate)
+ {
+ UpdateDocPos(static_cast<const sw::DocPosUpdate*>(&rHint)->m_nDocPos);
+ }
+}
+
+namespace
+{
+ bool lcl_ExpandField(const SwFieldIds eId, const bool bHiddenParaPrint)
+ {
+ switch(eId)
+ {
+ case SwFieldIds::HiddenPara:
+ return !bHiddenParaPrint;
+ case SwFieldIds::DbSetNumber:
+ case SwFieldIds::DbNumSet:
+ case SwFieldIds::DbNextSet:
+ case SwFieldIds::DatabaseName:
+ return false;
+ default:
+ return true;
+ }
+
+ };
+ bool lcl_TriggerNode(const SwFieldIds eId)
+ {
+ switch(eId)
+ {
+ case SwFieldIds::HiddenPara:
+ case SwFieldIds::DbSetNumber:
+ case SwFieldIds::DbNumSet:
+ case SwFieldIds::DbNextSet:
+ case SwFieldIds::DatabaseName:
+ return true;
+ default:
+ return false;
+ }
+ }
+ void lcl_EnsureUserFieldValid(SwFieldType& rType)
+ {
+ if(rType.Which() != SwFieldIds::User)
+ return;
+ static_cast<SwUserFieldType*>(&rType)->EnsureValid();
+ }
+ bool lcl_NeedsForcedUpdate(const SwField& rField)
+ {
+ if (rField.GetTyp()->Which() == SwFieldIds::DocInfo)
+ {
+ auto pDocInfoField = static_cast<const SwDocInfoField*>(&rField);
+ sal_uInt16 nSubType = pDocInfoField->GetSubType();
+ // Do not consider extended SubTypes.
+ nSubType &= 0xff;
+ switch (nSubType)
+ {
+ case nsSwDocInfoSubType::DI_TITLE:
+ case nsSwDocInfoSubType::DI_SUBJECT:
+ case nsSwDocInfoSubType::DI_CHANGE:
+ case nsSwDocInfoSubType::DI_CUSTOM:
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+void SwFormatField::ForceUpdateTextNode()
+{
+ if (!IsFieldInDoc())
+ return;
+
+ SwTextNode* pTextNd = &mpTextField->GetTextNode();
+ OSL_ENSURE(pTextNd, "Where is my Node?");
+
+ auto pType = mpField->GetTyp();
+ lcl_EnsureUserFieldValid(*pType);
+ if(lcl_TriggerNode(pType->Which()))
+ pTextNd->TriggerNodeUpdate(sw::LegacyModifyHint(nullptr, nullptr));
+ if(!lcl_ExpandField(pType->Which(), false))
+ return;
+
+ // Force notify was added for conditional text fields,
+ // at least the below fields need no forced notify.
+ bool bNeedForced = lcl_NeedsForcedUpdate(*mpTextField->GetFormatField().GetField());
+ mpTextField->ExpandTextField(bNeedForced);
+}
+void SwFormatField::UpdateDocPos(const SwTwips nDocPos)
+{
+ if (!IsFieldInDoc())
+ return;
+ auto pTextNd = &mpTextField->GetTextNode();
+
+ pTextNd->UpdateDocPos(nDocPos, mpTextField->GetStart());
+}
+void SwFormatField::UpdateTextNode(const SfxHint& rHint)
+{
+ if(SfxHintId::SwLegacyModify != rHint.GetId())
+ return;
+ auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ auto pOld = pLegacy->m_pOld;
+ auto pNew = pLegacy->m_pNew;
+ if (pOld == nullptr && pNew == nullptr)
+ {
+ ForceUpdateTextNode();
+ return;
+ }
+ else if (pOld && (RES_REMOVE_UNO_OBJECT == pOld->Which()))
+ { // invalidate cached UNO object
+ m_wXTextField.clear();
+ // ??? why does this Modify method not already do this?
+ CallSwClientNotify(sw::LegacyModifyHint(pOld, pNew));
+ return;
+ }
+
+ if (!IsFieldInDoc())
+ return;
+
+ // don't do anything, especially not expand!
+ if( pNew && pNew->Which() == RES_OBJECTDYING )
+ return;
+
+ SwTextNode* pTextNd = &mpTextField->GetTextNode();
+ OSL_ENSURE(pTextNd, "Where is my Node?");
+
+ bool bTriggerNode = pNew != nullptr;
+ bool bExpand = false;
+ if(pNew)
+ {
+ switch(pNew->Which())
+ {
+ case RES_ATTRSET_CHG:
+ case RES_FMT_CHG:
+ break;
+ default:
+ {
+ auto pType = mpField->GetTyp();
+ lcl_EnsureUserFieldValid(*pType);
+ bTriggerNode = lcl_TriggerNode(pType->Which());
+ bExpand = lcl_ExpandField(pType->Which(), pOld && pOld->Which() == RES_HIDDENPARA_PRINT);
+ pOld = nullptr;
+ }
+ }
+ }
+ if(bTriggerNode)
+ pTextNd->TriggerNodeUpdate(sw::LegacyModifyHint(pOld, pNew));
+ if(bExpand)
+ mpTextField->ExpandTextField(false);
+}
+
+bool SwFormatField::IsFieldInDoc() const
+{
+ return mpTextField != nullptr
+ && mpTextField->IsFieldInDoc();
+}
+
+bool SwFormatField::IsProtect() const
+{
+ return mpTextField != nullptr
+ && mpTextField->GetpTextNode() != nullptr
+ && mpTextField->GetpTextNode()->IsProtect();
+}
+
+void SwFormatField::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatField"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("mpTextField"), "%p", mpTextField);
+
+ SfxPoolItem::dumpAsXml(pWriter);
+ if (mpField) // pool default doesn't have one
+ {
+ mpField->dumpAsXml(pWriter);
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+// class SwTextField ////////////////////////////////////////////////////
+
+SwTextField::SwTextField(
+ SwFormatField & rAttr,
+ sal_Int32 const nStartPos,
+ bool const bInClipboard)
+ : SwTextAttr( rAttr, nStartPos )
+// fdo#39694 the ExpandField here may not give the correct result in all cases,
+// but is better than nothing
+ , m_aExpand( rAttr.GetField()->ExpandField(bInClipboard, nullptr) )
+ , m_pTextNode( nullptr )
+{
+ rAttr.SetTextField( *this );
+ SetHasDummyChar(true);
+}
+
+SwTextField::~SwTextField( )
+{
+ SwFormatField & rFormatField( static_cast<SwFormatField &>(GetAttr()) );
+ if ( this == rFormatField.GetTextField() )
+ {
+ rFormatField.ClearTextField();
+ }
+}
+
+bool SwTextField::IsFieldInDoc() const
+{
+ return GetpTextNode() != nullptr
+ && GetpTextNode()->GetNodes().IsDocNodes();
+}
+
+void SwTextField::ExpandTextField(const bool bForceNotify) const
+{
+ OSL_ENSURE( m_pTextNode, "SwTextField: where is my TextNode?" );
+
+ const SwField* pField = GetFormatField().GetField();
+ const OUString aNewExpand( pField->ExpandField(m_pTextNode->GetDoc().IsClipBoard(),
+ // can't do any better than this here...
+ m_pTextNode->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout()) );
+
+ const SwFieldIds nWhich = pField->GetTyp()->Which();
+ const bool bSameExpandSimpleNotification
+ = SwFieldIds::Chapter != nWhich && SwFieldIds::PageNumber != nWhich
+ && SwFieldIds::RefPageGet != nWhich
+ // Page count fields to not use aExpand during formatting,
+ // therefore an invalidation of the text frame has to be triggered even if aNewExpand == aExpand:
+ && (SwFieldIds::DocStat != nWhich
+ || DS_PAGE != static_cast<const SwDocStatField*>(pField)->GetSubType())
+ && (SwFieldIds::GetExp != nWhich
+ || static_cast<const SwGetExpField*>(pField)->IsInBodyText());
+
+ bool bHiddenParaChanged = false;
+ if (aNewExpand != m_aExpand || bSameExpandSimpleNotification)
+ bHiddenParaChanged = m_pTextNode->CalcHiddenParaField();
+
+ if (aNewExpand == m_aExpand)
+ {
+ if ( bSameExpandSimpleNotification )
+ {
+ if( bHiddenParaChanged )
+ m_pTextNode->TriggerNodeUpdate(sw::LegacyModifyHint(nullptr, nullptr));
+ if ( !bForceNotify )
+ {
+ // done, if no further notification forced.
+ return;
+ }
+ }
+ }
+ else
+ m_aExpand = aNewExpand;
+
+ const_cast<SwTextField*>(this)->NotifyContentChange( const_cast<SwFormatField&>(GetFormatField()) );
+}
+
+void SwTextField::CopyTextField( SwTextField *pDest ) const
+{
+ OSL_ENSURE( m_pTextNode, "SwTextField: where is my TextNode?" );
+ OSL_ENSURE( pDest->m_pTextNode, "SwTextField: where is pDest's TextNode?" );
+
+ IDocumentFieldsAccess* pIDFA = &m_pTextNode->getIDocumentFieldsAccess();
+ IDocumentFieldsAccess* pDestIDFA = &pDest->m_pTextNode->getIDocumentFieldsAccess();
+
+ SwFormatField& rDestFormatField = const_cast<SwFormatField&>(pDest->GetFormatField());
+ const SwFieldIds nFieldWhich = rDestFormatField.GetField()->GetTyp()->Which();
+
+ if( pIDFA != pDestIDFA )
+ {
+ // different documents, e.g. clipboard:
+ // register field type in target document
+ SwFieldType* pFieldType;
+ if( nFieldWhich != SwFieldIds::Database
+ && nFieldWhich != SwFieldIds::User
+ && nFieldWhich != SwFieldIds::SetExp
+ && nFieldWhich != SwFieldIds::Dde
+ && SwFieldIds::TableOfAuthorities != nFieldWhich )
+ {
+ pFieldType = pDestIDFA->GetSysFieldType( nFieldWhich );
+ }
+ else
+ {
+ pFieldType = pDestIDFA->InsertFieldType( *rDestFormatField.GetField()->GetTyp() );
+ }
+
+ // DDE fields need special treatment
+ if( SwFieldIds::Dde == nFieldWhich )
+ {
+ if( rDestFormatField.GetTextField() )
+ {
+ static_cast<SwDDEFieldType*>(rDestFormatField.GetField()->GetTyp())->DecRefCnt();
+ }
+ static_cast<SwDDEFieldType*>(pFieldType)->IncRefCnt();
+ }
+
+ OSL_ENSURE( pFieldType, "unknown FieldType" );
+ pFieldType->Add( &rDestFormatField ); // register at the field type
+ rDestFormatField.GetField()->ChgTyp( pFieldType );
+ }
+
+ // update expression fields
+ if( nFieldWhich == SwFieldIds::SetExp
+ || nFieldWhich == SwFieldIds::GetExp
+ || nFieldWhich == SwFieldIds::HiddenText )
+ {
+ SwTextField* pField = const_cast<SwTextField*>(this);
+ pDestIDFA->UpdateExpFields( pField, true );
+ }
+ // table fields: external display
+ else if( SwFieldIds::Table == nFieldWhich
+ && static_cast<SwTableField*>(rDestFormatField.GetField())->IsIntrnlName() )
+ {
+ // convert internal (core) to external (UI) formula
+ const SwTableNode* pTableNd = m_pTextNode->FindTableNode();
+ if( pTableNd ) // in a table?
+ static_cast<SwTableField*>(rDestFormatField.GetField())->PtrToBoxNm( &pTableNd->GetTable() );
+ }
+}
+
+void SwTextField::NotifyContentChange(SwFormatField& rFormatField)
+{
+ //if not in undo section notify the change
+ if (m_pTextNode && m_pTextNode->GetNodes().IsDocNodes())
+ m_pTextNode->TriggerNodeUpdate(sw::LegacyModifyHint(nullptr, &rFormatField));
+}
+
+/*static*/
+void SwTextField::GetPamForTextField(
+ const SwTextField& rTextField,
+ std::shared_ptr< SwPaM >& rPamForTextField )
+{
+ if (rTextField.GetpTextNode() == nullptr)
+ {
+ SAL_WARN("sw.core", "<SwTextField::GetPamForField> - missing <SwTextNode>");
+ return;
+ }
+
+ const SwTextNode& rTextNode = rTextField.GetTextNode();
+
+ rPamForTextField = std::make_shared<SwPaM>( rTextNode,
+ (rTextField.End() != nullptr) ? *(rTextField.End()) : ( rTextField.GetStart() + 1 ),
+ rTextNode,
+ rTextField.GetStart() );
+
+}
+
+/*static*/
+void SwTextField::DeleteTextField( const SwTextField& rTextField )
+{
+ if (rTextField.GetpTextNode() != nullptr)
+ {
+ std::shared_ptr< SwPaM > pPamForTextField;
+ GetPamForTextField(rTextField, pPamForTextField);
+ if (pPamForTextField != nullptr)
+ {
+ rTextField.GetTextNode().GetDoc().getIDocumentContentOperations().DeleteAndJoin(*pPamForTextField);
+ }
+ }
+}
+
+// class SwTextInputField ///////////////////////////////////////////////
+
+// input field in-place editing
+SwTextInputField::SwTextInputField(
+ SwFormatField & rAttr,
+ sal_Int32 const nStart,
+ sal_Int32 const nEnd,
+ bool const bInClipboard )
+
+ : SwTextAttr( rAttr, nStart )
+ , SwTextAttrNesting( rAttr, nStart, nEnd )
+ , SwTextField( rAttr, nStart, bInClipboard )
+ , m_bLockNotifyContentChange( false )
+{
+ SetHasDummyChar( false );
+ SetHasContent( true );
+}
+
+SwTextInputField::~SwTextInputField()
+{
+}
+
+bool SwTextInputField::LockNotifyContentChange()
+{
+ if (m_bLockNotifyContentChange)
+ {
+ return false;
+ }
+ m_bLockNotifyContentChange = true;
+ return true;
+}
+
+void SwTextInputField::UnlockNotifyContentChange()
+{
+ m_bLockNotifyContentChange = false;
+}
+
+void SwTextInputField::NotifyContentChange( SwFormatField& rFormatField )
+{
+ if ( !m_bLockNotifyContentChange )
+ {
+ LockNotifyContentChange();
+
+ SwTextField::NotifyContentChange( rFormatField );
+ UpdateTextNodeContent( GetFieldContent() );
+
+ UnlockNotifyContentChange();
+ }
+}
+
+OUString SwTextInputField::GetFieldContent() const
+{
+ return GetFormatField().GetField()->ExpandField(false, nullptr/*ignored anyway*/);
+}
+
+void SwTextInputField::UpdateFieldContent()
+{
+ if ( !(IsFieldInDoc()
+ && GetStart() != (*End())) )
+ return;
+
+ assert( (*End()) - GetStart() >= 2 &&
+ "<SwTextInputField::UpdateFieldContent()> - Are CH_TXT_ATR_INPUTFIELDSTART and/or CH_TXT_ATR_INPUTFIELDEND missing?" );
+ // skip CH_TXT_ATR_INPUTFIELDSTART character
+ const sal_Int32 nIdx = GetStart() + 1;
+ // skip CH_TXT_ATR_INPUTFIELDEND character
+ const sal_Int32 nLen = static_cast<sal_Int32>(std::max<sal_Int32>( 0, ( (*End()) - 1 - nIdx ) ));
+ const OUString aNewFieldContent = GetTextNode().GetExpandText(nullptr, nIdx, nLen);
+
+ const SwField* pField = GetFormatField().GetField();
+ const SwInputField* pInputField = dynamic_cast<const SwInputField*>(pField);
+ if (pInputField)
+ const_cast<SwInputField*>(pInputField)->applyFieldContent( aNewFieldContent );
+
+ const SwSetExpField* pExpField = dynamic_cast<const SwSetExpField*>(pField);
+ if (pExpField)
+ {
+ assert(pExpField->GetInputFlag());
+ const_cast<SwSetExpField*>(pExpField)->SetPar2(aNewFieldContent);
+ }
+ assert(pInputField || pExpField);
+
+ // trigger update of fields for scenarios in which the Input Field's content is part of e.g. a table formula
+ GetTextNode().GetDoc().getIDocumentFieldsAccess().GetUpdateFields().SetFieldsDirty(true);
+}
+
+void SwTextInputField::UpdateTextNodeContent( const OUString& rNewContent )
+{
+ assert(IsFieldInDoc() &&
+ "<SwTextInputField::UpdateTextNodeContent(..)> - misusage as Input Field is not in document content.");
+
+ assert( (*End()) - GetStart() >= 2 &&
+ "<SwTextInputField::UpdateTextNodeContent(..)> - Are CH_TXT_ATR_INPUTFIELDSTART and/or CH_TXT_ATR_INPUTFIELDEND missing?" );
+ // skip CH_TXT_ATR_INPUTFIELDSTART character
+ const sal_Int32 nIdx = GetStart() + 1;
+ // skip CH_TXT_ATR_INPUTFIELDEND character
+ const sal_Int32 nDelLen = std::max<sal_Int32>( 0, ( (*End()) - 1 - nIdx ) );
+ SwContentIndex aIdx( &GetTextNode(), nIdx );
+ GetTextNode().ReplaceText( aIdx, nDelLen, rNewContent );
+}
+
+// class SwTextAnnotationField //////////////////////////////////////////
+
+// text annotation field
+SwTextAnnotationField::SwTextAnnotationField(
+ SwFormatField & rAttr,
+ sal_Int32 const nStart,
+ bool const bInClipboard )
+ : SwTextAttr( rAttr, nStart )
+ , SwTextField( rAttr, nStart, bInClipboard )
+{
+}
+
+SwTextAnnotationField::~SwTextAnnotationField()
+{
+}
+
+::sw::mark::IMark* SwTextAnnotationField::GetAnnotationMark() const
+{
+ auto pPostItField = dynamic_cast<const SwPostItField*>(GetFormatField().GetField());
+ assert(pPostItField);
+
+ SwDoc& rDoc = static_cast<const SwPostItFieldType*>(pPostItField->GetTyp())->GetDoc();
+
+ IDocumentMarkAccess* pMarksAccess = rDoc.getIDocumentMarkAccess();
+ IDocumentMarkAccess::const_iterator_t pMark = pMarksAccess->findAnnotationMark( pPostItField->GetName() );
+ return pMark != pMarksAccess->getAnnotationMarksEnd()
+ ? *pMark
+ : nullptr;
+}
+
+void SwFormatField::SetXTextField(rtl::Reference<SwXTextField> const& xTextField)
+{ m_wXTextField = xTextField.get(); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/atrflyin.cxx b/sw/source/core/txtnode/atrflyin.cxx
new file mode 100644
index 0000000000..28eb7b38e5
--- /dev/null
+++ b/sw/source/core/txtnode/atrflyin.cxx
@@ -0,0 +1,306 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <hintids.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentLayoutAccess.hxx>
+#include <pam.hxx>
+#include <flyfrm.hxx>
+#include <ndtxt.hxx>
+#include <frmfmt.hxx>
+#include <fmtflcnt.hxx>
+#include <txtflcnt.hxx>
+#include <fmtanchr.hxx>
+#include <txtfrm.hxx>
+#include <flyfrms.hxx>
+#include <objectformatter.hxx>
+#include <calbck.hxx>
+#include <dcontact.hxx>
+#include <textboxhelper.hxx>
+#include <osl/diagnose.h>
+
+SwFormatFlyCnt::SwFormatFlyCnt( SwFrameFormat *pFrameFormat )
+ : SfxPoolItem( RES_TXTATR_FLYCNT ),
+ m_pTextAttr( nullptr ),
+ m_pFormat( pFrameFormat )
+{
+}
+
+bool SwFormatFlyCnt::operator==( const SfxPoolItem& rAttr ) const
+{
+ assert(SfxPoolItem::operator==(rAttr));
+ return( m_pTextAttr && static_cast<const SwFormatFlyCnt&>(rAttr).m_pTextAttr &&
+ m_pTextAttr->GetStart() == static_cast<const SwFormatFlyCnt&>(rAttr).m_pTextAttr->GetStart() &&
+ m_pFormat == static_cast<const SwFormatFlyCnt&>(rAttr).GetFrameFormat() );
+}
+
+SwFormatFlyCnt* SwFormatFlyCnt::Clone( SfxItemPool* ) const
+{
+ return new SwFormatFlyCnt( m_pFormat );
+}
+
+void SwFormatFlyCnt::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatFlyCnt"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("text-attr"), "%p", m_pTextAttr);
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("format"), "%p", m_pFormat);
+
+ SfxPoolItem::dumpAsXml(pWriter);
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SwTextFlyCnt::SwTextFlyCnt( SwFormatFlyCnt& rAttr, sal_Int32 nStartPos )
+ : SwTextAttr( rAttr, nStartPos )
+{
+ rAttr.m_pTextAttr = this;
+ SetHasDummyChar(true);
+}
+
+/** An overview of how a new SwTextFlyCnt is created:
+ * MakeTextAttr() is called e.g. by SwTextNode::CopyText().
+ * The following steps are required to clone:
+ * 1) copying the pFormat with content, attributes etc.
+ * 2) setting the anchor
+ * 3) notification
+ * Because not all required information is available at all times,
+ * the steps are distributed variously:
+ * ad 1) MakeTextAttr() calls DocumentLayoutManager::CopyLayoutFormat()
+ * which creates the new SwFlyFrameFormat and copies the content of the
+ * fly frame.
+ * ad 2) SetAnchor() is called by SwTextNode::InsertHint() and sets the anchor
+ * position in the SwFlyFrameFormat to the SwPosition of the dummy
+ * CH_TXTATR_BREAKWORD. This cannot be done in MakeTextAttr() because it
+ * doesn't know the target text node.
+ * ad 3) GetFlyFrame_() is called during text formatting by SwTextFormatter
+ * and searches for the SwFlyFrame for the dummy char of the current
+ * SwTextFrame. If none is found, a new SwFlyInContentFrame is created.
+ * Important: pTextFrame->AppendFly() immediately triggers a reformat
+ * of pTextFrame. However, the recursion is blocked by the lock mechanism
+ * in SwTextFrame::Format().
+ * The advantage of all this is that it's not necessary to explicitly iterate
+ * over all SwTextFrames that depend on the SwTextNode to create the
+ * SwFlyInContentFrame - this is done automatically already.
+ */
+
+void SwTextFlyCnt::CopyFlyFormat( SwDoc& rDoc )
+{
+ SwFrameFormat* pFormat = GetFlyCnt().GetFrameFormat();
+ assert(pFormat);
+ // The FlyFrameFormat must be copied - CopyLayoutFormat
+ // (DocumentLayoutManager.cxx) creates the FlyFrameFormat and copies the
+ // content.
+
+ // disable undo while copying attribute
+ ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
+ SwFormatAnchor aAnchor( pFormat->GetAnchor() );
+ if ((RndStdIds::FLY_AT_PAGE != aAnchor.GetAnchorId()) &&
+ (&rDoc != pFormat->GetDoc())) // different documents?
+ {
+ // JP 03.06.96: ensure that the copied anchor points to valid content!
+ // setting it to the correct position is done later.
+ SwNodeIndex aIdx( rDoc.GetNodes().GetEndOfExtras(), +2 );
+ SwContentNode* pCNd = aIdx.GetNode().GetContentNode();
+ if( !pCNd )
+ pCNd = rDoc.GetNodes().GoNext( &aIdx );
+
+ SwPosition pos(aIdx.GetNode());
+ aAnchor.SetAnchor( &pos );
+ }
+
+ SwFrameFormat* pNew = rDoc.getIDocumentLayoutAccess().CopyLayoutFormat( *pFormat, aAnchor, false, false );
+ const_cast<SwFormatFlyCnt&>(GetFlyCnt()).SetFlyFormat( pNew );
+}
+
+/** SetAnchor() is called by SwTextNode::InsertHint() and sets the anchor
+ * position in the SwFlyFrameFormat to the SwPosition of the dummy
+ * CH_TXTATR_BREAKWORD. This cannot be done in MakeTextAttr() because it
+ * doesn't know the target text node.
+ */
+void SwTextFlyCnt::SetAnchor( const SwTextNode *pNode )
+{
+ // for Undo, the new anchor must be known already!
+
+ SwDoc& rDoc = const_cast<SwDoc&>(pNode->GetDoc());
+
+ SwFrameFormat* pFormat = GetFlyCnt().GetFrameFormat();
+ SwFormatAnchor aAnchor( pFormat->GetAnchor() );
+ SwNode *const pOldNode(aAnchor.GetAnchorNode());
+
+ std::optional<SwPosition> oPos;
+ if (!pOldNode || !pOldNode->GetNodes().IsDocNodes() ||
+ pOldNode != static_cast<SwNode const *>(pNode))
+ {
+ oPos.emplace( *pNode, GetStart() );
+ }
+ else
+ {
+ oPos.emplace( *pOldNode, pOldNode->GetContentNode(), GetStart() );
+ }
+
+ aAnchor.SetType( RndStdIds::FLY_AS_CHAR ); // default!
+ aAnchor.SetAnchor( &*oPos );
+
+ // in case of anchor change, delete all FlyFrames
+ // JP 25.04.95: if the Frames can be moved within SplitNode, they don't
+ // need to be deleted
+ if( ( !pNode->GetpSwpHints() || !pNode->GetpSwpHints()->IsInSplitNode() )
+ && RES_DRAWFRMFMT != pFormat->Which() )
+ pFormat->DelFrames();
+
+ // copy into a different document?
+ if( &rDoc != pFormat->GetDoc() )
+ {
+ // disable undo while copying attribute
+ ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
+ SwFrameFormat* pNew = rDoc.getIDocumentLayoutAccess().CopyLayoutFormat( *pFormat, aAnchor, false, false );
+
+ ::sw::UndoGuard const undoGuardFormat(
+ pFormat->GetDoc()->GetIDocumentUndoRedo());
+ pFormat->GetDoc()->getIDocumentLayoutAccess().DelLayoutFormat( pFormat );
+ const_cast<SwFormatFlyCnt&>(GetFlyCnt()).SetFlyFormat( pNew );
+ }
+ else if( pNode->GetpSwpHints() &&
+ pNode->GetpSwpHints()->IsInSplitNode() &&
+ RES_DRAWFRMFMT != pFormat->Which() )
+ {
+ pFormat->LockModify();
+ pFormat->SetFormatAttr( aAnchor ); // only set the anchor
+ // tdf#91228 must notify the anchor nodes despite LockModify
+ assert(pOldNode);
+ pOldNode->RemoveAnchoredFly(pFormat);
+ oPos->GetNode().AddAnchoredFly(pFormat);
+ pFormat->UnlockModify();
+ }
+ else
+ {
+ assert(!pFormat->IsModifyLocked()); // need to notify anchor node
+ if (RES_DRAWFRMFMT == pFormat->Which())
+ {
+ if (SdrObject const*const pObj = pFormat->FindSdrObject())
+ { // tdf#123259 disconnect with *old* anchor position
+ static_cast<SwDrawContact*>(::GetUserCall(pObj))->DisconnectFromLayout(false);
+ }
+ }
+ pFormat->SetFormatAttr( aAnchor ); // only set the anchor
+
+ // If the draw format has a TextBox, then set its anchor as well.
+ if (SwFrameFormat* pTextBox
+ = SwTextBoxHelper::getOtherTextBoxFormat(pFormat, RES_DRAWFRMFMT))
+ {
+ SwFormatAnchor aTextBoxAnchor(pTextBox->GetAnchor());
+ aTextBoxAnchor.SetAnchor(aAnchor.GetContentAnchor());
+
+ // SwFlyAtContentFrame::SwClientNotify() assumes the anchor has a matching layout frame, which
+ // may not be the case when we're in the process of a node split, so block
+ // notifications.
+ bool bIsInSplitNode = pNode->GetpSwpHints() && pNode->GetpSwpHints()->IsInSplitNode();
+ if (bIsInSplitNode)
+ {
+ pTextBox->LockModify();
+ }
+ else
+ {
+ // Otherwise delete fly frames on anchor change.
+ pTextBox->DelFrames();
+ }
+
+ pTextBox->SetFormatAttr(aTextBoxAnchor);
+
+ if (bIsInSplitNode)
+ {
+ pOldNode->RemoveAnchoredFly(pTextBox);
+ oPos->GetNode().AddAnchoredFly(pTextBox);
+ pTextBox->UnlockModify();
+ }
+ else
+ {
+ pTextBox->MakeFrames();
+ }
+ }
+ }
+
+ // The node may have several SwTextFrames - for every SwTextFrame a
+ // SwFlyInContentFrame is created.
+}
+
+
+/** GetFlyFrame_() is called during text formatting by SwTextFormatter
+ * and searches for the SwFlyFrame for the dummy char of the current
+ * SwTextFrame. If none is found, a new SwFlyInContentFrame is created.
+ */
+SwFlyInContentFrame *SwTextFlyCnt::GetFlyFrame_( const SwFrame *pCurrFrame )
+{
+ SwFrameFormat* pFrameFormat = GetFlyCnt().GetFrameFormat();
+ if( RES_DRAWFRMFMT == pFrameFormat->Which() )
+ {
+ OSL_ENSURE( false, "SwTextFlyCnt::GetFlyFrame_: DrawInCnt-under construction!" );
+ return nullptr;
+ }
+
+ SwIterator<SwFlyFrame,SwFormat> aIter( *GetFlyCnt().m_pFormat );
+ assert(pCurrFrame->IsTextFrame());
+ SwFrame* pFrame = aIter.First();
+ if ( pFrame )
+ {
+ SwTextFrame *pFirst = const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pCurrFrame));
+ while ( pFirst->IsFollow() )
+ pFirst = pFirst->FindMaster();
+ do
+ {
+ SwTextFrame *pTmp = pFirst;
+ do
+ { if( static_cast<SwFlyFrame*>(pFrame)->GetAnchorFrame() == static_cast<SwFrame*>(pTmp) )
+ {
+ if ( pTmp != pCurrFrame )
+ {
+ pTmp->RemoveFly( static_cast<SwFlyFrame*>(pFrame) );
+ const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pCurrFrame))->AppendFly( static_cast<SwFlyFrame*>(pFrame) );
+ }
+ return static_cast<SwFlyInContentFrame*>(pFrame);
+ }
+ pTmp = pTmp->GetFollow();
+ } while ( pTmp );
+
+ pFrame = aIter.Next();
+
+ } while( pFrame );
+ }
+
+ // We did not find a matching FlyFrame, so create a new one.
+ // AppendFly() triggers a reformat of pCurrentFrame. However, the
+ // recursion is blocked by the lock mechanism in SwTextFrame::Format().
+ SwFrame* pCurrentFrame = const_cast<SwFrame*>(pCurrFrame);
+ SwFlyInContentFrame *pFly = new SwFlyInContentFrame(static_cast<SwFlyFrameFormat*>(pFrameFormat), pCurrentFrame, pCurrentFrame);
+ pCurrentFrame->AppendFly(pFly);
+ pFly->RegistFlys();
+
+ // We must ensure that the content of the FlyInCnt is fully formatted
+ // right after construction.
+ // #i26945# - Use new object formatter to format Writer
+ // fly frame and its content.
+ SwObjectFormatter::FormatObj( *pFly, const_cast<SwFrame*>(pCurrFrame),
+ pCurrFrame->FindPageFrame() );
+
+ return pFly;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/atrftn.cxx b/sw/source/core/txtnode/atrftn.cxx
new file mode 100644
index 0000000000..58f6a50016
--- /dev/null
+++ b/sw/source/core/txtnode/atrftn.cxx
@@ -0,0 +1,612 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <libxml/xmlwriter.h>
+#include <fmtftn.hxx>
+#include <doc.hxx>
+#include <DocumentContentOperationsManager.hxx>
+#include <IDocumentStylePoolAccess.hxx>
+#include <cntfrm.hxx>
+#include <rootfrm.hxx>
+#include <pagefrm.hxx>
+#include <txtftn.hxx>
+#include <ftnidx.hxx>
+#include <ftninfo.hxx>
+#include <ndtxt.hxx>
+#include <poolfmt.hxx>
+#include <ftnfrm.hxx>
+#include <ndindex.hxx>
+#include <fmtftntx.hxx>
+#include <section.hxx>
+#include <calbck.hxx>
+#include <hints.hxx>
+#include <pam.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <vcl/svapp.hxx>
+#include <unotextrange.hxx>
+#include <osl/diagnose.h>
+#include <unofootnote.hxx>
+
+namespace {
+ /// Get a sorted list of the used footnote reference numbers.
+ /// @param[in] rDoc The active document.
+ /// @param[in] pExclude A footnote whose reference number should be excluded from the set.
+ /// @param[out] rUsedRef The set of used reference numbers.
+ /// @param[out] rInvalid A returned list of all items that had an invalid reference number.
+ void lcl_FillUsedFootnoteRefNumbers(SwDoc &rDoc,
+ SwTextFootnote const *pExclude,
+ std::set<sal_uInt16> &rUsedRef,
+ std::vector<SwTextFootnote*> &rInvalid)
+ {
+ SwFootnoteIdxs& ftnIdxs = rDoc.GetFootnoteIdxs();
+
+ rInvalid.clear();
+
+ for( size_t n = 0; n < ftnIdxs.size(); ++n )
+ {
+ SwTextFootnote* pTextFootnote = ftnIdxs[ n ];
+ if ( pTextFootnote != pExclude )
+ {
+ if ( USHRT_MAX == pTextFootnote->GetSeqRefNo() )
+ {
+ rInvalid.push_back(pTextFootnote);
+ }
+ else
+ {
+ rUsedRef.insert( pTextFootnote->GetSeqRefNo() );
+ }
+ }
+ }
+ }
+
+ /// Check whether a requested reference number is available.
+ /// @param[in] rUsedNums Set of used reference numbers.
+ /// @param[in] requested The requested reference number.
+ /// @returns true if the number is available, false if not.
+ bool lcl_IsRefNumAvailable(std::set<sal_uInt16> const &rUsedNums,
+ sal_uInt16 requested)
+ {
+ if ( USHRT_MAX == requested )
+ return false; // Invalid sequence number.
+ if ( rUsedNums.count(requested) )
+ return false; // Number already used.
+ return true;
+ }
+
+ /// Get the first few unused sequential reference numbers.
+ /// @param[out] rLowestUnusedNums The lowest unused sequential reference numbers.
+ /// @param[in] rUsedNums The set of used sequential reference numbers.
+ /// @param[in] numRequired The number of reference number required.
+ void lcl_FillUnusedSeqRefNums(std::vector<sal_uInt16> &rLowestUnusedNums,
+ const std::set<sal_uInt16> &rUsedNums,
+ size_t numRequired)
+ {
+ if (!numRequired)
+ return;
+
+ rLowestUnusedNums.reserve(numRequired);
+ sal_uInt16 newNum = 0;
+ //Start by using numbers from gaps in rUsedNums
+ for( const auto& rNum : rUsedNums )
+ {
+ while ( newNum < rNum )
+ {
+ rLowestUnusedNums.push_back( newNum++ );
+ if ( --numRequired == 0)
+ return;
+ }
+ newNum++;
+ }
+ //Filled in all gaps. Fill the rest of the list with new numbers.
+ do
+ {
+ rLowestUnusedNums.push_back( newNum++ );
+ }
+ while ( --numRequired > 0 );
+ }
+
+}
+
+SwFormatFootnote::SwFormatFootnote( bool bEndNote )
+ : SfxPoolItem( RES_TXTATR_FTN )
+ , sw::BroadcastingModify()
+ , m_pTextAttr(nullptr)
+ , m_nNumber(0)
+ , m_nNumberRLHidden(0)
+ , m_bEndNote(bEndNote)
+{
+}
+
+void SwFormatFootnote::SetXFootnote(rtl::Reference<SwXFootnote> const& xNote)
+{ m_wXFootnote = xNote.get(); }
+
+bool SwFormatFootnote::operator==( const SfxPoolItem& rAttr ) const
+{
+ assert(SfxPoolItem::operator==(rAttr));
+ return m_nNumber == static_cast<const SwFormatFootnote&>(rAttr).m_nNumber &&
+ //FIXME?
+ m_aNumber == static_cast<const SwFormatFootnote&>(rAttr).m_aNumber &&
+ m_bEndNote == static_cast<const SwFormatFootnote&>(rAttr).m_bEndNote;
+}
+
+SwFormatFootnote* SwFormatFootnote::Clone( SfxItemPool* ) const
+{
+ SwFormatFootnote* pNew = new SwFormatFootnote;
+ pNew->m_aNumber = m_aNumber;
+ pNew->m_nNumber = m_nNumber;
+ pNew->m_nNumberRLHidden = m_nNumberRLHidden;
+ pNew->m_bEndNote = m_bEndNote;
+ return pNew;
+}
+
+void SwFormatFootnote::SwClientNotify(const SwModify&, const SfxHint& rHint)
+{
+ if (rHint.GetId() != SfxHintId::SwLegacyModify)
+ return;
+ auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ CallSwClientNotify(rHint);
+ if(RES_REMOVE_UNO_OBJECT == pLegacy->GetWhich())
+ SetXFootnote(nullptr);
+}
+
+void SwFormatFootnote::InvalidateFootnote()
+{
+ SwPtrMsgPoolItem const item(RES_REMOVE_UNO_OBJECT,
+ &static_cast<sw::BroadcastingModify&>(*this)); // cast to base class (void*)
+ CallSwClientNotify(sw::LegacyModifyHint(&item, &item));
+}
+
+void SwFormatFootnote::SetEndNote( bool b )
+{
+ if ( b != m_bEndNote )
+ {
+ if ( GetTextFootnote() )
+ {
+ GetTextFootnote()->DelFrames(nullptr);
+ }
+ m_bEndNote = b;
+ }
+}
+
+SwFormatFootnote::~SwFormatFootnote()
+{
+}
+
+OUString SwFormatFootnote::GetFootnoteText(SwRootFrame const& rLayout) const
+{
+ OUStringBuffer buf;
+ if( m_pTextAttr->GetStartNode() )
+ {
+ SwNodeIndex aIdx( *m_pTextAttr->GetStartNode(), 1 );
+ SwContentNode* pCNd = aIdx.GetNode().GetTextNode();
+ if( !pCNd )
+ pCNd = aIdx.GetNodes().GoNext( &aIdx );
+
+ if( pCNd->IsTextNode() ) {
+ buf.append(static_cast<SwTextNode*>(pCNd)->GetExpandText(&rLayout));
+
+ ++aIdx;
+ while ( !aIdx.GetNode().IsEndNode() ) {
+ if ( aIdx.GetNode().IsTextNode() )
+ {
+ buf.append(" " + aIdx.GetNode().GetTextNode()->GetExpandText(&rLayout));
+ }
+ ++aIdx;
+ }
+ }
+ }
+ return buf.makeStringAndClear();
+}
+
+/// return the view string of the foot/endnote
+OUString SwFormatFootnote::GetViewNumStr(const SwDoc& rDoc,
+ SwRootFrame const*const pLayout, bool bInclStrings) const
+{
+ OUString sRet( GetNumStr() );
+ if( sRet.isEmpty() )
+ {
+ // in this case the number is needed, get it via SwDoc's FootnoteInfo
+ bool bMakeNum = true;
+ const SwSectionNode* pSectNd = m_pTextAttr
+ ? SwUpdFootnoteEndNtAtEnd::FindSectNdWithEndAttr( *m_pTextAttr )
+ : nullptr;
+ sal_uInt16 const nNumber(pLayout && pLayout->IsHideRedlines()
+ ? GetNumberRLHidden()
+ : GetNumber());
+
+ if( pSectNd )
+ {
+ const SwFormatFootnoteEndAtTextEnd& rFootnoteEnd = static_cast<const SwFormatFootnoteEndAtTextEnd&>(
+ pSectNd->GetSection().GetFormat()->GetFormatAttr(
+ IsEndNote() ?
+ o3tl::narrowing<sal_uInt16>(RES_END_AT_TXTEND) :
+ o3tl::narrowing<sal_uInt16>(RES_FTN_AT_TXTEND) ) );
+
+ if( FTNEND_ATTXTEND_OWNNUMANDFMT == rFootnoteEnd.GetValue() )
+ {
+ bMakeNum = false;
+ sRet = rFootnoteEnd.GetSwNumType().GetNumStr( nNumber );
+ if( bInclStrings )
+ {
+ sRet = rFootnoteEnd.GetPrefix() + sRet + rFootnoteEnd.GetSuffix();
+ }
+ }
+ }
+
+ if( bMakeNum )
+ {
+ const SwEndNoteInfo* pInfo;
+ if( IsEndNote() )
+ pInfo = &rDoc.GetEndNoteInfo();
+ else
+ pInfo = &rDoc.GetFootnoteInfo();
+ sRet = pInfo->m_aFormat.GetNumStr( nNumber );
+ if( bInclStrings )
+ {
+ sRet = pInfo->GetPrefix() + sRet + pInfo->GetSuffix();
+ }
+ }
+ }
+ return sRet;
+}
+
+rtl::Reference<SwXTextRange> SwFormatFootnote::getAnchor(SwDoc& rDoc) const
+{
+ SolarMutexGuard aGuard;
+ if (!m_pTextAttr)
+ return {};
+ SwPaM aPam(m_pTextAttr->GetTextNode(), m_pTextAttr->GetStart());
+ aPam.SetMark();
+ aPam.GetMark()->AdjustContent(+1);
+ rtl::Reference<SwXTextRange> xRet =
+ SwXTextRange::CreateXTextRange(rDoc, *aPam.Start(), aPam.End());
+ return xRet;
+}
+
+void SwFormatFootnote::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatFootnote"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("text-attr"), "%p", m_pTextAttr);
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("endnote"),
+ BAD_CAST(OString::boolean(m_bEndNote).getStr()));
+
+ SfxPoolItem::dumpAsXml(pWriter);
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SwTextFootnote::SwTextFootnote( SwFormatFootnote& rAttr, sal_Int32 nStartPos )
+ : SwTextAttr( rAttr, nStartPos )
+ , m_pTextNode( nullptr )
+ , m_nSeqNo( USHRT_MAX )
+{
+ rAttr.m_pTextAttr = this;
+ SetHasDummyChar(true);
+}
+
+SwTextFootnote::~SwTextFootnote()
+{
+ SetStartNode( nullptr );
+}
+
+void SwTextFootnote::SetStartNode( const SwNodeIndex *pNewNode, bool bDelNode )
+{
+ if( pNewNode )
+ {
+ m_oStartNode = *pNewNode;
+ }
+ else if ( m_oStartNode )
+ {
+ // need to do 2 things:
+ // 1) unregister footnotes at their pages
+ // 2) delete the footnote section in the Inserts of the nodes-array
+ SwDoc* pDoc;
+ if ( m_pTextNode )
+ {
+ pDoc = &m_pTextNode->GetDoc();
+ }
+ else
+ {
+ //JP 27.01.97: the sw3-Reader creates a StartNode but the
+ // attribute isn't anchored in the TextNode yet.
+ // If it is deleted (e.g. Insert File with footnote
+ // inside fly frame), the content must also be deleted.
+ pDoc = &m_oStartNode->GetNodes().GetDoc();
+ }
+
+ // If called from ~SwDoc(), must not delete the footnote nodes,
+ // and not necessary to delete the footnote frames.
+ if( !pDoc->IsInDtor() )
+ {
+ if( bDelNode )
+ {
+ // 2) delete the section for the footnote nodes
+ // it's possible that the Inserts have already been deleted (how???)
+ pDoc->getIDocumentContentOperations().DeleteSection( &m_oStartNode->GetNode() );
+ }
+ else
+ // If the nodes are not deleted, their frames must be removed
+ // from the page (deleted), there is nothing else that deletes
+ // them (particularly not Undo)
+ DelFrames( nullptr );
+ }
+ m_oStartNode.reset();
+
+ // remove the footnote from the SwDoc's array
+ for( size_t n = 0; n < pDoc->GetFootnoteIdxs().size(); ++n )
+ if( this == pDoc->GetFootnoteIdxs()[n] )
+ {
+ pDoc->GetFootnoteIdxs().erase( pDoc->GetFootnoteIdxs().begin() + n );
+ // if necessary, update following footnotes
+ if( !pDoc->IsInDtor() && n < pDoc->GetFootnoteIdxs().size() )
+ {
+ pDoc->GetFootnoteIdxs().UpdateFootnote( pDoc->GetFootnoteIdxs()[n]->GetTextNode() );
+ }
+ break;
+ }
+ }
+}
+
+void SwTextFootnote::SetNumber(const sal_uInt16 nNewNum,
+ sal_uInt16 const nNumberRLHidden, const OUString &sNumStr)
+{
+ SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(GetFootnote());
+
+ rFootnote.m_aNumber = sNumStr;
+ if ( sNumStr.isEmpty() )
+ {
+ rFootnote.m_nNumber = nNewNum;
+ rFootnote.m_nNumberRLHidden = nNumberRLHidden;
+ }
+ InvalidateNumberInLayout();
+}
+
+void SwTextFootnote::InvalidateNumberInLayout()
+{
+ assert(m_pTextNode);
+ SwNodes &rNodes = m_pTextNode->GetDoc().GetNodes();
+ const sw::LegacyModifyHint aHint(nullptr, &GetFootnote());
+ m_pTextNode->TriggerNodeUpdate(aHint);
+ if ( m_oStartNode )
+ {
+ // must iterate over all TextNodes because of footnotes on other pages
+ SwNodeOffset nSttIdx = m_oStartNode->GetIndex() + 1;
+ SwNodeOffset nEndIdx = m_oStartNode->GetNode().EndOfSectionIndex();
+ for( ; nSttIdx < nEndIdx; ++nSttIdx )
+ {
+ SwNode* pNd;
+ if( ( pNd = rNodes[ nSttIdx ] )->IsTextNode() )
+ static_cast<SwTextNode*>(pNd)->TriggerNodeUpdate(aHint);
+ }
+ }
+}
+
+void SwTextFootnote::CopyFootnote(
+ SwTextFootnote & rDest,
+ SwTextNode & rDestNode ) const
+{
+ if (m_oStartNode && !rDest.GetStartNode())
+ {
+ // dest missing node section? create it here!
+ // (happens in SwTextNode::CopyText if pDest == this)
+ rDest.MakeNewTextSection( rDestNode.GetNodes() );
+ }
+ if (m_oStartNode && rDest.GetStartNode())
+ {
+ // footnotes not necessarily in same document!
+ SwDoc& rDstDoc = rDestNode.GetDoc();
+ SwNodes &rDstNodes = rDstDoc.GetNodes();
+
+ // copy only the content of the section
+ SwNodeRange aRg( m_oStartNode->GetNode(), SwNodeOffset(1),
+ *m_oStartNode->GetNode().EndOfSectionNode() );
+
+ // insert at the end of rDest, i.e., the nodes are appended.
+ // nDestLen contains number of ContentNodes in rDest _before_ copy.
+ SwNodeIndex aStart( *(rDest.GetStartNode()) );
+ SwNodeIndex aEnd( *aStart.GetNode().EndOfSectionNode() );
+ SwNodeOffset nDestLen = aEnd.GetIndex() - aStart.GetIndex() - 1;
+
+ m_pTextNode->GetDoc().GetDocumentContentOperationsManager().CopyWithFlyInFly(aRg, aEnd.GetNode());
+
+ // in case the destination section was not empty, delete the old nodes
+ // before: Src: SxxxE, Dst: SnE
+ // now: Src: SxxxE, Dst: SnxxxE
+ // after: Src: SxxxE, Dst: SxxxE
+ ++aStart;
+ rDstNodes.Delete( aStart, nDestLen );
+ }
+
+ // also copy user defined number string
+ if( !GetFootnote().m_aNumber.isEmpty() )
+ {
+ const_cast<SwFormatFootnote &>(rDest.GetFootnote()).m_aNumber = GetFootnote().m_aNumber;
+ }
+}
+
+/// create a new nodes-array section for the footnote
+void SwTextFootnote::MakeNewTextSection( SwNodes& rNodes )
+{
+ if ( m_oStartNode )
+ return;
+
+ // set the footnote style on the SwTextNode
+ SwTextFormatColl *pFormatColl;
+ const SwEndNoteInfo* pInfo;
+ sal_uInt16 nPoolId;
+
+ if( GetFootnote().IsEndNote() )
+ {
+ pInfo = &rNodes.GetDoc().GetEndNoteInfo();
+ nPoolId = RES_POOLCOLL_ENDNOTE;
+ }
+ else
+ {
+ pInfo = &rNodes.GetDoc().GetFootnoteInfo();
+ nPoolId = RES_POOLCOLL_FOOTNOTE;
+ }
+
+ pFormatColl = pInfo->GetFootnoteTextColl();
+ if( nullptr == pFormatColl )
+ pFormatColl = rNodes.GetDoc().getIDocumentStylePoolAccess().GetTextCollFromPool( nPoolId );
+
+ SwStartNode* pSttNd = rNodes.MakeTextSection( rNodes.GetEndOfInserts(),
+ SwFootnoteStartNode, pFormatColl );
+ m_oStartNode = *pSttNd;
+}
+
+void SwTextFootnote::DelFrames(SwRootFrame const*const pRoot)
+{
+ // delete the FootnoteFrames from the pages
+ OSL_ENSURE( m_pTextNode, "SwTextFootnote: where is my TextNode?" );
+ if ( !m_pTextNode )
+ return;
+
+ bool bFrameFnd = false;
+ {
+ SwIterator<SwContentFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*m_pTextNode);
+ for( SwContentFrame* pFnd = aIter.First(); pFnd; pFnd = aIter.Next() )
+ {
+ if( pRoot != pFnd->getRootFrame() && pRoot )
+ continue;
+ SwPageFrame* pPage = pFnd->FindPageFrame();
+ if( pPage )
+ {
+ // note: we have found the correct frame only if the footnote
+ // was actually removed; in case this is called from
+ // SwTextFrame::DestroyImpl(), then that frame isn't connected
+ // to SwPageFrame any more, and RemoveFootnote on any follow
+ // must not prevent the fall-back to the !bFrameFnd code.
+ bFrameFnd = pPage->RemoveFootnote(pFnd, this);
+ }
+ }
+ }
+ //JP 13.05.97: if the layout is deleted before the footnotes are deleted,
+ // try to delete the footnote's frames by another way
+ if ( bFrameFnd || !m_oStartNode )
+ return;
+
+ SwNodeIndex aIdx( *m_oStartNode );
+ SwContentNode* pCNd = m_pTextNode->GetNodes().GoNext( &aIdx );
+ if( !pCNd )
+ return;
+
+ SwIterator<SwContentFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*pCNd);
+ for( SwContentFrame* pFnd = aIter.First(); pFnd; pFnd = aIter.Next() )
+ {
+ if( pRoot != pFnd->getRootFrame() && pRoot )
+ continue;
+ SwPageFrame* pPage = pFnd->FindPageFrame();
+
+ SwFrame *pFrame = pFnd->GetUpper();
+ while ( pFrame && !pFrame->IsFootnoteFrame() )
+ pFrame = pFrame->GetUpper();
+
+ SwFootnoteFrame *pFootnote = static_cast<SwFootnoteFrame*>(pFrame);
+ while ( pFootnote && pFootnote->GetMaster() )
+ pFootnote = pFootnote->GetMaster();
+ OSL_ENSURE( pFootnote->GetAttr() == this, "Footnote mismatch error." );
+
+ while ( pFootnote )
+ {
+ SwFootnoteFrame *pFoll = pFootnote->GetFollow();
+ pFootnote->Cut();
+ SwFrame::DestroyFrame(pFootnote);
+ pFootnote = pFoll;
+ }
+
+ // #i20556# During hiding of a section, the connection
+ // to the layout is already lost. pPage may be 0:
+ if ( pPage )
+ pPage->UpdateFootnoteNum();
+ }
+}
+
+/// Set the sequence number for the current footnote.
+/// @returns The new sequence number or USHRT_MAX if invalid.
+void SwTextFootnote::SetSeqRefNo()
+{
+ if( !m_pTextNode )
+ return;
+
+ SwDoc& rDoc = m_pTextNode->GetDoc();
+ if( rDoc.IsInReading() )
+ return;
+
+ std::set<sal_uInt16> aUsedNums;
+ std::vector<SwTextFootnote*> badRefNums;
+ ::lcl_FillUsedFootnoteRefNumbers(rDoc, this, aUsedNums, badRefNums);
+ if ( ::lcl_IsRefNumAvailable(aUsedNums, m_nSeqNo) )
+ return;
+ std::vector<sal_uInt16> unused;
+ ::lcl_FillUnusedSeqRefNums(unused, aUsedNums, 1);
+ m_nSeqNo = unused[0];
+}
+
+/// Set a unique sequential reference number for every footnote in the document.
+/// @param[in] rDoc The document to be processed.
+void SwTextFootnote::SetUniqueSeqRefNo( SwDoc& rDoc )
+{
+ std::set<sal_uInt16> aUsedNums;
+ std::vector<SwTextFootnote*> badRefNums;
+ ::lcl_FillUsedFootnoteRefNumbers(rDoc, nullptr, aUsedNums, badRefNums);
+ std::vector<sal_uInt16> aUnused;
+ ::lcl_FillUnusedSeqRefNums(aUnused, aUsedNums, badRefNums.size());
+
+ for (size_t i = 0; i < badRefNums.size(); ++i)
+ {
+ badRefNums[i]->m_nSeqNo = aUnused[i];
+ }
+}
+
+void SwTextFootnote::CheckCondColl()
+{
+ if( GetStartNode() )
+ static_cast<SwStartNode&>(GetStartNode()->GetNode()).CheckSectionCondColl();
+}
+
+void SwTextFootnote::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextFootnote"));
+ SwTextAttr::dumpAsXml(pWriter);
+
+ if (m_oStartNode)
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_oStartNode"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"),
+ BAD_CAST(OString::number(sal_Int32(m_oStartNode->GetIndex())).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+ if (m_pTextNode)
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pTextNode"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"),
+ BAD_CAST(OString::number(sal_Int32(m_pTextNode->GetIndex())).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_nSeqNo"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"),
+ BAD_CAST(OString::number(m_nSeqNo).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/atrref.cxx b/sw/source/core/txtnode/atrref.cxx
new file mode 100644
index 0000000000..1684e00330
--- /dev/null
+++ b/sw/source/core/txtnode/atrref.cxx
@@ -0,0 +1,225 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <fmtrfmrk.hxx>
+
+#include <libxml/xmlwriter.h>
+
+#include <hintids.hxx>
+#include <hints.hxx>
+#include <txtrfmrk.hxx>
+#include <unorefmark.hxx>
+#include <utility>
+#include <sfx2/viewsh.hxx>
+#include <tools/json_writer.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <comphelper/lok.hxx>
+#include <doc.hxx>
+#include <ndtxt.hxx>
+#include <pam.hxx>
+#include <translatehelper.hxx>
+#include <wrtsh.hxx>
+
+SwFormatRefMark::~SwFormatRefMark( )
+{
+}
+
+SwFormatRefMark::SwFormatRefMark( OUString aName )
+ : SfxPoolItem(RES_TXTATR_REFMARK)
+ , sw::BroadcastingModify()
+ , m_pTextAttr(nullptr)
+ , m_aRefName(std::move(aName))
+{
+}
+
+SwFormatRefMark::SwFormatRefMark( const SwFormatRefMark& rAttr )
+ : SfxPoolItem(RES_TXTATR_REFMARK)
+ , sw::BroadcastingModify()
+ , m_pTextAttr(nullptr)
+ , m_aRefName(rAttr.m_aRefName)
+{
+}
+
+void SwFormatRefMark::SetXRefMark(rtl::Reference<SwXReferenceMark> const& xMark)
+{ m_wXReferenceMark = xMark.get(); }
+
+bool SwFormatRefMark::operator==( const SfxPoolItem& rAttr ) const
+{
+ assert(SfxPoolItem::operator==(rAttr));
+ return m_aRefName == static_cast<const SwFormatRefMark&>(rAttr).m_aRefName;
+}
+
+SwFormatRefMark* SwFormatRefMark::Clone( SfxItemPool* ) const
+{
+ return new SwFormatRefMark( *this );
+}
+
+void SwFormatRefMark::SwClientNotify(const SwModify&, const SfxHint& rHint)
+{
+ if (rHint.GetId() != SfxHintId::SwLegacyModify)
+ return;
+ auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ CallSwClientNotify(rHint);
+ if(RES_REMOVE_UNO_OBJECT == pLegacy->GetWhich())
+ SetXRefMark(nullptr);
+}
+
+void SwFormatRefMark::InvalidateRefMark()
+{
+ SwPtrMsgPoolItem const item(RES_REMOVE_UNO_OBJECT,
+ &static_cast<sw::BroadcastingModify&>(*this)); // cast to base class (void*)
+ CallSwClientNotify(sw::LegacyModifyHint(&item, &item));
+}
+
+void SwFormatRefMark::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatRefMark"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("m_pTextAttr"), "%p", m_pTextAttr);
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("ref-name"),
+ BAD_CAST(m_aRefName.toUtf8().getStr()));
+ SfxPoolItem::dumpAsXml(pWriter);
+
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+// attribute for content references in the text
+
+SwTextRefMark::SwTextRefMark( SwFormatRefMark& rAttr,
+ sal_Int32 const nStartPos, sal_Int32 const*const pEnd)
+ : SwTextAttr(rAttr, nStartPos)
+ , SwTextAttrEnd( rAttr, nStartPos, nStartPos )
+ , m_pTextNode( nullptr )
+ , m_pEnd( nullptr )
+{
+ rAttr.m_pTextAttr = this;
+ if ( pEnd )
+ {
+ m_nEnd = *pEnd;
+ m_pEnd = & m_nEnd;
+ }
+ else
+ {
+ SetHasDummyChar(true);
+ }
+ SetDontMoveAttr( true );
+ SetOverlapAllowedAttr( true );
+ /* FIXME: Setting the DontExpand flag would solve tdf#81720,
+ * but old behavior was restored due to regressions; see tdf#157287.
+ * After applying a proper fix, remember to restore testDontExpandRefmark!
+ *SetDontExpand( true ); // like hyperlinks, reference markers shouldn't expand
+ *SetLockExpandFlag( true ); // protect the flag
+ */
+}
+
+SwTextRefMark::~SwTextRefMark()
+{
+ if (!comphelper::LibreOfficeKit::isActive() || GetTextNode().GetDoc().IsClipBoard())
+ return;
+
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ if (!pViewShell)
+ return;
+
+ OUString fieldCommand = GetRefMark().GetRefName();
+ tools::JsonWriter aJson;
+ aJson.put("commandName", ".uno:DeleteField");
+ aJson.put("success", true);
+ {
+ auto result = aJson.startNode("result");
+ aJson.put("DeleteField", fieldCommand);
+ }
+
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.finishAndGetAsOString());
+}
+
+const sal_Int32* SwTextRefMark::GetEnd() const
+{
+ return m_pEnd;
+}
+
+void SwTextRefMark::SetEnd(sal_Int32 n)
+{
+ *m_pEnd = n;
+ if (m_pHints)
+ m_pHints->EndPosChanged();
+}
+
+void SwTextRefMark::UpdateFieldContent(SwDoc* pDoc, SwWrtShell& rWrtSh, OUString aContent)
+{
+ if (!this->End())
+ {
+ return;
+ }
+
+ // Insert markers to remember where the paste positions are.
+ const SwTextNode& rTextNode = this->GetTextNode();
+ SwPaM aMarkers(SwPosition(rTextNode, *this->End()));
+ IDocumentContentOperations& rIDCO = pDoc->getIDocumentContentOperations();
+ /* FIXME: see above re: expanding behavior
+ *this->SetLockExpandFlag(false);
+ *this->SetDontExpand(false);
+ */
+ if (rIDCO.InsertString(aMarkers, "XY"))
+ {
+ SwPaM aPasteEnd(SwPosition(rTextNode, *this->End()));
+ aPasteEnd.Move(fnMoveBackward, GoInContent);
+
+ // Paste HTML content.
+ SwPaM* pCursorPos = rWrtSh.GetCursor();
+ *pCursorPos = aPasteEnd;
+ SwTranslateHelper::PasteHTMLToPaM(rWrtSh, pCursorPos, aContent.toUtf8());
+
+ // Update the refmark to point to the new content.
+ sal_Int32 nOldStart = this->GetStart();
+ sal_Int32 nNewStart = *this->End();
+ // First grow it to include text till the end of the paste position.
+ this->SetEnd(aPasteEnd.GetPoint()->GetContentIndex());
+ // Then shrink it to only start at the paste start: we know that the refmark was
+ // truncated to the paste start, as the refmark has to stay inside a single text node
+ this->SetStart(nNewStart);
+ rTextNode.GetSwpHints().SortIfNeedBe();
+ SwPaM aEndMarker(*aPasteEnd.GetPoint());
+ aEndMarker.SetMark();
+ aEndMarker.GetMark()->AdjustContent(1);
+ SwPaM aStartMarker(SwPosition(rTextNode, nOldStart), SwPosition(rTextNode, nNewStart));
+
+ // Remove markers. The start marker includes the old content as well.
+ rIDCO.DeleteAndJoin(aStartMarker);
+ rIDCO.DeleteAndJoin(aEndMarker);
+ }
+ // Restore flags.
+ /* FIXME: see above re: expanding behavior
+ *this->SetDontExpand(true);
+ *this->SetLockExpandFlag(true);
+ */
+}
+
+
+void SwTextRefMark::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextRefMark"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ SwTextAttr::dumpAsXml(pWriter);
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/atrtox.cxx b/sw/source/core/txtnode/atrtox.cxx
new file mode 100644
index 0000000000..664ce55968
--- /dev/null
+++ b/sw/source/core/txtnode/atrtox.cxx
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <doc.hxx>
+#include <txttxmrk.hxx>
+#include <tox.hxx>
+
+SwTextTOXMark::SwTextTOXMark( SwTOXMark& rAttr,
+ sal_Int32 const nStartPos, sal_Int32 const*const pEnd)
+ : SwTextAttr( rAttr, nStartPos )
+ , SwTextAttrEnd( rAttr, nStartPos, nStartPos )
+ , m_pTextNode( nullptr )
+ , m_pEnd( nullptr )
+{
+ rAttr.m_pTextAttr = this;
+ if ( rAttr.GetAlternativeText().isEmpty() )
+ {
+ m_nEnd = *pEnd;
+ m_pEnd = & m_nEnd;
+ }
+ else
+ {
+ SetHasDummyChar(true);
+ }
+ SetDontMoveAttr( true );
+ SetOverlapAllowedAttr( true );
+}
+
+SwTextTOXMark::~SwTextTOXMark()
+{
+}
+
+const sal_Int32* SwTextTOXMark::GetEnd() const
+{
+ return m_pEnd;
+}
+
+void SwTextTOXMark::SetEnd(sal_Int32 n)
+{
+ *m_pEnd = n;
+ if (m_pHints)
+ m_pHints->EndPosChanged();
+}
+
+void SwTextTOXMark::CopyTOXMark( SwDoc& rDoc )
+{
+ SwTOXMark& rTOX = const_cast<SwTOXMark&>(GetTOXMark());
+ TOXTypes eType = rTOX.GetTOXType()->GetType();
+ const sal_uInt16 nCount = rDoc.GetTOXTypeCount( eType );
+ const SwTOXType* pType = nullptr;
+ const OUString rNm = rTOX.GetTOXType()->GetTypeName();
+
+ for(sal_uInt16 i=0; i < nCount; ++i)
+ {
+ const SwTOXType* pSrcType = rDoc.GetTOXType(eType, i);
+ if(pSrcType->GetTypeName() == rNm )
+ {
+ pType = pSrcType;
+ break;
+ }
+ }
+
+ // if the requested tox type does not exist, create it
+ if(!pType)
+ {
+ rDoc.InsertTOXType( SwTOXType( rDoc, eType, rNm ) );
+ pType = rDoc.GetTOXType(eType, 0);
+ }
+
+ // register at target tox type
+ const_cast<SwTOXType*>(pType)->Add( &rTOX );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/attrcontentcontrol.cxx b/sw/source/core/txtnode/attrcontentcontrol.cxx
new file mode 100644
index 0000000000..c894615fa4
--- /dev/null
+++ b/sw/source/core/txtnode/attrcontentcontrol.cxx
@@ -0,0 +1,875 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <formatcontentcontrol.hxx>
+
+#include <libxml/xmlwriter.h>
+
+#include <sal/log.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+#include <svl/numformat.hxx>
+#include <vcl/keycod.hxx>
+
+#include <ndtxt.hxx>
+#include <textcontentcontrol.hxx>
+#include <doc.hxx>
+#include <unocontentcontrol.hxx>
+#include <unoport.hxx>
+#include <wrtsh.hxx>
+
+using namespace com::sun::star;
+
+namespace
+{
+inline constexpr OUString CURRENT_DATE_FORMAT = u"YYYY-MM-DD"_ustr;
+}
+
+SwFormatContentControl* SwFormatContentControl::CreatePoolDefault(sal_uInt16 nWhich)
+{
+ return new SwFormatContentControl(nWhich);
+}
+
+SwFormatContentControl::SwFormatContentControl(sal_uInt16 nWhich)
+ : SfxPoolItem(nWhich)
+ , m_pTextAttr(nullptr)
+{
+}
+
+SwFormatContentControl::SwFormatContentControl(
+ const std::shared_ptr<SwContentControl>& pContentControl, sal_uInt16 nWhich)
+ : SfxPoolItem(nWhich)
+ , m_pContentControl(pContentControl)
+ , m_pTextAttr(nullptr)
+{
+ if (!pContentControl)
+ {
+ SAL_WARN("sw.core", "SwFormatContentControl ctor: no pContentControl?");
+ }
+ // Not calling m_pContentControl->SetFormatContentControl(this) here; only from SetTextAttr.
+}
+
+SwFormatContentControl::~SwFormatContentControl()
+{
+ if (m_pContentControl
+ // SwFormatContentControl is not shareable, so ptr compare is OK
+ && areSfxPoolItemPtrsEqual(m_pContentControl->GetFormatContentControl(), this))
+ {
+ NotifyChangeTextNode(nullptr);
+ m_pContentControl->SetFormatContentControl(nullptr);
+ }
+}
+
+bool SwFormatContentControl::operator==(const SfxPoolItem& rOther) const
+{
+ return SfxPoolItem::operator==(rOther)
+ && m_pContentControl
+ == static_cast<const SwFormatContentControl&>(rOther).m_pContentControl;
+}
+
+SwFormatContentControl* SwFormatContentControl::Clone(SfxItemPool* /*pPool*/) const
+{
+ // If this is indeed a copy, then DoCopy will be called later.
+ if (m_pContentControl)
+ {
+ return new SwFormatContentControl(m_pContentControl, Which());
+ }
+ else
+ {
+ return new SwFormatContentControl(Which());
+ }
+}
+
+void SwFormatContentControl::SetTextAttr(SwTextContentControl* pTextAttr)
+{
+ if (m_pTextAttr && pTextAttr)
+ {
+ SAL_WARN("sw.core", "SwFormatContentControl::SetTextAttr: already has a text attribute");
+ }
+ if (!m_pTextAttr && !pTextAttr)
+ {
+ SAL_WARN("sw.core", "SwFormatContentControl::SetTextAttr: no attribute to remove");
+ }
+ m_pTextAttr = pTextAttr;
+ if (!m_pContentControl)
+ {
+ SAL_WARN("sw.core", "inserted SwFormatContentControl has no SwContentControl");
+ }
+ // The SwContentControl should be able to find the current text attribute.
+ if (m_pContentControl)
+ {
+ if (pTextAttr)
+ {
+ m_pContentControl->SetFormatContentControl(this);
+ }
+ // SwFormatContentControl is not shareable, so ptr compare is OK
+ else if (areSfxPoolItemPtrsEqual(m_pContentControl->GetFormatContentControl(), this))
+ {
+ // The text attribute is gone, so de-register from text node.
+ NotifyChangeTextNode(nullptr);
+ m_pContentControl->SetFormatContentControl(nullptr);
+ }
+ }
+}
+
+void SwFormatContentControl::NotifyChangeTextNode(SwTextNode* pTextNode)
+{
+ // Not deleting m_pTextAttr here, SwNodes::ChgNode() doesn't do that, either.
+ if (!m_pContentControl)
+ {
+ SAL_WARN("sw.core", "SwFormatContentControl::NotifyChangeTextNode: no content control?");
+ }
+ if (m_pContentControl
+ // SwFormatContentControl is not shareable, so ptr compare is OK
+ && areSfxPoolItemPtrsEqual(m_pContentControl->GetFormatContentControl(), this))
+ {
+ // Not calling Modify, that would call SwXContentControl::SwClientNotify.
+ m_pContentControl->NotifyChangeTextNode(pTextNode);
+ }
+}
+
+SwTextNode* SwFormatContentControl::GetTextNode() const
+{
+ if (!m_pContentControl)
+ {
+ return nullptr;
+ }
+
+ return m_pContentControl->GetTextNode();
+}
+
+// This SwFormatContentControl has been cloned and points at the same SwContentControl as the
+// source: this function copies the SwContentControl.
+void SwFormatContentControl::DoCopy(SwTextNode& rTargetTextNode)
+{
+ if (!m_pContentControl)
+ {
+ SAL_WARN("sw.core", "SwFormatContentControl::DoCopy: called for SwFormatContentControl "
+ "with no SwContentControl.");
+ return;
+ }
+
+ m_pContentControl = std::make_shared<SwContentControl>(this);
+ m_pContentControl->NotifyChangeTextNode(&rTargetTextNode);
+}
+
+void SwFormatContentControl::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatContentControl"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("m_pTextAttr"), "%p", m_pTextAttr);
+ SfxPoolItem::dumpAsXml(pWriter);
+
+ if (m_pContentControl)
+ {
+ m_pContentControl->dumpAsXml(pWriter);
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SwContentControl::SwContentControl(SwFormatContentControl* pFormat)
+ : sw::BroadcastingModify()
+ , m_pFormat(pFormat)
+ , m_pTextNode(nullptr)
+{
+ if (!pFormat)
+ {
+ return;
+ }
+
+ const std::shared_ptr<SwContentControl>& pOther = pFormat->GetContentControl();
+ if (!pOther)
+ {
+ return;
+ }
+
+ SetShowingPlaceHolder(pOther->m_bShowingPlaceHolder);
+ SetCheckbox(pOther->m_bCheckbox);
+ SetChecked(pOther->m_bChecked);
+ SetCheckedState(pOther->m_aCheckedState);
+ SetUncheckedState(pOther->m_aUncheckedState);
+ SetListItems(pOther->m_aListItems);
+ SetPicture(pOther->m_bPicture);
+ SetDate(pOther->m_bDate);
+ SetDateFormat(pOther->m_aDateFormat);
+ SetDateLanguage(pOther->m_aDateLanguage);
+ SetCurrentDate(pOther->m_aCurrentDate);
+ SetPlainText(pOther->m_bPlainText);
+ SetComboBox(pOther->m_bComboBox);
+ SetDropDown(pOther->m_bDropDown);
+ SetPlaceholderDocPart(pOther->m_aPlaceholderDocPart);
+ SetDataBindingPrefixMappings(pOther->m_aDataBindingPrefixMappings);
+ SetDataBindingXpath(pOther->m_aDataBindingXpath);
+ SetDataBindingStoreItemID(pOther->m_aDataBindingStoreItemID);
+ SetColor(pOther->m_aColor);
+ SetAppearance(pOther->m_aAppearance);
+ SetAlias(pOther->m_aAlias);
+ SetTag(pOther->m_aTag);
+ SetId(pOther->m_nId);
+ SetTabIndex(pOther->m_nTabIndex);
+ SetLock(pOther->m_aLock);
+ SetMultiLine(pOther->m_aMultiLine);
+}
+
+SwContentControl::~SwContentControl() {}
+
+void SwContentControl::SetXContentControl(const rtl::Reference<SwXContentControl>& xContentControl)
+{
+ m_wXContentControl = xContentControl.get();
+}
+
+SwTextContentControl* SwContentControl::GetTextAttr() const
+{
+ return m_pFormat ? m_pFormat->GetTextAttr() : nullptr;
+}
+
+void SwContentControl::NotifyChangeTextNode(SwTextNode* pTextNode)
+{
+ m_pTextNode = pTextNode;
+ if (m_pTextNode && (GetRegisteredIn() != m_pTextNode))
+ {
+ m_pTextNode->Add(this);
+ }
+ else if (!m_pTextNode)
+ {
+ EndListeningAll();
+ }
+ if (!pTextNode)
+ {
+ // If the text node is gone, then invalidate clients (e.g. UNO object).
+ GetNotifier().Broadcast(SfxHint(SfxHintId::Deinitializing));
+ }
+}
+
+void SwContentControl::SwClientNotify(const SwModify&, const SfxHint& rHint)
+{
+ if (rHint.GetId() != SfxHintId::SwLegacyModify)
+ return;
+
+ auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ CallSwClientNotify(rHint);
+ GetNotifier().Broadcast(SfxHint(SfxHintId::DataChanged));
+
+ if (pLegacy->GetWhich() == RES_REMOVE_UNO_OBJECT)
+ {
+ // Invalidate cached uno object.
+ SetXContentControl(nullptr);
+ GetNotifier().Broadcast(SfxHint(SfxHintId::Deinitializing));
+ }
+}
+
+std::optional<size_t> SwContentControl::GetSelectedListItem(bool bCheckDocModel) const
+{
+ if (!bCheckDocModel || m_oSelectedListItem)
+ return m_oSelectedListItem;
+
+ const size_t nLen = GetListItems().size();
+ if (GetShowingPlaceHolder() || !nLen || !GetTextAttr())
+ return std::nullopt;
+
+ const OUString& rText = GetTextAttr()->ToString();
+ for (size_t i = 0; i < nLen; ++i)
+ {
+ if (GetTextAttr()[i].ToString() == rText)
+ return i;
+ }
+ assert(!GetDropDown() && "DropDowns must always have an associated list item");
+ return std::nullopt;
+}
+
+bool SwContentControl::AddListItem(size_t nZIndex, const OUString& rDisplayText,
+ const OUString& rValue)
+{
+ SwContentControlListItem aListItem;
+ if (rValue.isEmpty())
+ {
+ if (rDisplayText.isEmpty())
+ return false;
+ aListItem.m_aValue = rDisplayText;
+ }
+ else
+ {
+ aListItem.m_aValue = rValue;
+ aListItem.m_aDisplayText = rDisplayText;
+ }
+
+ // Avoid adding duplicates
+ for (auto& rListItem : GetListItems())
+ {
+ if (rListItem == aListItem)
+ return false;
+ }
+
+ const size_t nLen = GetListItems().size();
+ nZIndex = std::min(nZIndex, nLen);
+ const std::optional<size_t> oSelected = GetSelectedListItem();
+ if (oSelected && *oSelected >= nZIndex)
+ {
+ if (*oSelected < nLen)
+ SetSelectedListItem(*oSelected + 1);
+ }
+ std::vector<SwContentControlListItem> vListItems = GetListItems();
+ vListItems.insert(vListItems.begin() + nZIndex, aListItem);
+ SetListItems(vListItems);
+ return true;
+}
+
+void SwContentControl::DeleteListItem(size_t nZIndex)
+{
+ if (nZIndex >= GetListItems().size())
+ return;
+
+ const std::optional<size_t> oSelected = GetSelectedListItem();
+ if (oSelected)
+ {
+ if (*oSelected == nZIndex)
+ {
+ SetSelectedListItem(std::nullopt);
+ if (m_bDropDown && GetTextAttr())
+ GetTextAttr()->Invalidate();
+ }
+ else if (*oSelected < nZIndex)
+ SetSelectedListItem(*oSelected - 1);
+ }
+
+ std::vector<SwContentControlListItem> vListItems = GetListItems();
+ vListItems.erase(vListItems.begin() + nZIndex);
+ SetListItems(vListItems);
+ return;
+}
+
+void SwContentControl::ClearListItems()
+{
+ SetSelectedListItem(std::nullopt);
+ SetListItems(std::vector<SwContentControlListItem>());
+ if (m_bDropDown && GetTextAttr())
+ GetTextAttr()->Invalidate();
+}
+
+OUString SwContentControl::GetDateString() const
+{
+ SwDoc& rDoc = m_pTextNode->GetDoc();
+ SvNumberFormatter* pNumberFormatter = rDoc.GetNumberFormatter();
+ sal_uInt32 nFormat = pNumberFormatter->GetEntryKey(
+ m_aDateFormat, LanguageTag(m_aDateLanguage).getLanguageType());
+
+ if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ // If not found, then create it.
+ sal_Int32 nCheckPos = 0;
+ SvNumFormatType nType;
+ OUString aFormat = m_aDateFormat;
+ pNumberFormatter->PutEntry(aFormat, nCheckPos, nType, nFormat,
+ LanguageTag(m_aDateLanguage).getLanguageType());
+ }
+
+ const Color* pColor = nullptr;
+ OUString aFormatted;
+ double fSelectedDate = 0;
+ if (m_oSelectedDate)
+ {
+ fSelectedDate = *m_oSelectedDate;
+ }
+ else
+ {
+ fSelectedDate = GetCurrentDateValue();
+ }
+
+ if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ return OUString();
+ }
+
+ pNumberFormatter->GetOutputString(fSelectedDate, nFormat, aFormatted, &pColor, false);
+ return aFormatted;
+}
+
+void SwContentControl::SetCurrentDateValue(double fCurrentDate)
+{
+ SwDoc& rDoc = m_pTextNode->GetDoc();
+ SvNumberFormatter* pNumberFormatter = rDoc.GetNumberFormatter();
+ OUString aFormatted;
+ sal_uInt32 nFormat = pNumberFormatter->GetEntryKey(CURRENT_DATE_FORMAT, LANGUAGE_ENGLISH_US);
+ if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ // If not found, then create it.
+ sal_Int32 nCheckPos = 0;
+ SvNumFormatType nType;
+ OUString sFormat = CURRENT_DATE_FORMAT;
+ pNumberFormatter->PutEntry(sFormat, nCheckPos, nType, nFormat, LANGUAGE_ENGLISH_US);
+ }
+
+ if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ return;
+ }
+
+ const Color* pColor = nullptr;
+ pNumberFormatter->GetOutputString(fCurrentDate, nFormat, aFormatted, &pColor, false);
+ m_aCurrentDate = aFormatted + "T00:00:00Z";
+}
+
+double SwContentControl::GetCurrentDateValue() const
+{
+ if (m_aCurrentDate.isEmpty())
+ {
+ return 0;
+ }
+
+ SwDoc& rDoc = m_pTextNode->GetDoc();
+ SvNumberFormatter* pNumberFormatter = rDoc.GetNumberFormatter();
+ sal_uInt32 nFormat = pNumberFormatter->GetEntryKey(CURRENT_DATE_FORMAT, LANGUAGE_ENGLISH_US);
+ if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ sal_Int32 nCheckPos = 0;
+ SvNumFormatType nType;
+ OUString sFormat = CURRENT_DATE_FORMAT;
+ pNumberFormatter->PutEntry(sFormat, nCheckPos, nType, nFormat, LANGUAGE_ENGLISH_US);
+ }
+
+ if (nFormat == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ return 0;
+ }
+
+ double dCurrentDate = 0;
+ OUString aCurrentDate = m_aCurrentDate.replaceAll("T00:00:00Z", "");
+ (void)pNumberFormatter->IsNumberFormat(aCurrentDate, nFormat, dCurrentDate);
+ return dCurrentDate;
+}
+
+bool SwContentControl::IsInteractingCharacter(sal_Unicode cCh)
+{
+ if (GetCheckbox())
+ {
+ return cCh == ' ';
+ }
+
+ if (GetPicture())
+ {
+ return cCh == '\r';
+ }
+
+ return false;
+}
+
+bool SwContentControl::ShouldOpenPopup(const vcl::KeyCode& rKeyCode)
+{
+ switch (GetType())
+ {
+ case SwContentControlType::DROP_DOWN_LIST:
+ case SwContentControlType::COMBO_BOX:
+ case SwContentControlType::DATE:
+ {
+ // Alt-down opens the popup.
+ return rKeyCode.IsMod2() && rKeyCode.GetCode() == KEY_DOWN;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+// NOTE: call SetReadWrite separately to implement true (un)locking.
+// This is mostly a theoretical function; the lock state is mainly kept for round-tripping purposes.
+// It is implemented here primarily for pointless VBA control, but with the intention that it
+// could be made functionally useful as well for checkboxes/dropdowns/pictures.
+// Returns whether the content (bControl=false) cannot be modified,
+// or if the control cannot be deleted.
+std::optional<bool> SwContentControl::GetLock(bool bControl) const
+{
+ std::optional<bool> oLock;
+ if (m_aLock.isEmpty())
+ return oLock;
+ else if (m_aLock.equalsIgnoreAsciiCase("sdtContentLocked"))
+ oLock = true;
+ else if (m_aLock.equalsIgnoreAsciiCase("unlocked"))
+ oLock = false;
+ else if (m_aLock.equalsIgnoreAsciiCase("sdtLocked"))
+ oLock = bControl;
+ else if (m_aLock.equalsIgnoreAsciiCase("contentLocked"))
+ oLock = !bControl;
+
+ assert(oLock.has_value() && "invalid or unknown lock state");
+ return oLock;
+}
+
+void SwContentControl::SetLock(bool bLockContent, bool bLockControl)
+{
+ if (!bLockContent && !bLockControl)
+ m_aLock = "unlocked";
+ else if (bLockContent && bLockControl)
+ m_aLock = "sdtContentLocked";
+ else if (bLockContent)
+ m_aLock = "contentLocked";
+ else
+ m_aLock = "sdtLocked";
+}
+
+SwContentControlType SwContentControl::GetType() const
+{
+ if (m_bCheckbox)
+ {
+ return SwContentControlType::CHECKBOX;
+ }
+
+ if (m_bComboBox)
+ {
+ return SwContentControlType::COMBO_BOX;
+ }
+
+ if (m_bDropDown)
+ {
+ return SwContentControlType::DROP_DOWN_LIST;
+ }
+
+ if (m_bPicture)
+ {
+ return SwContentControlType::PICTURE;
+ }
+
+ if (m_bDate)
+ {
+ return SwContentControlType::DATE;
+ }
+
+ if (m_bPlainText)
+ {
+ return SwContentControlType::PLAIN_TEXT;
+ }
+
+ return SwContentControlType::RICH_TEXT;
+}
+
+void SwContentControl::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwContentControl"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteFormatAttribute(
+ pWriter, BAD_CAST("showing-place-holder"), "%s",
+ BAD_CAST(OString::boolean(m_bShowingPlaceHolder).getStr()));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("checkbox"), "%s",
+ BAD_CAST(OString::boolean(m_bCheckbox).getStr()));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("checked"), "%s",
+ BAD_CAST(OString::boolean(m_bChecked).getStr()));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("checked-state"), "%s",
+ BAD_CAST(m_aCheckedState.toUtf8().getStr()));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("unchecked-state"), "%s",
+ BAD_CAST(m_aUncheckedState.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("picture"),
+ BAD_CAST(OString::boolean(m_bPicture).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("date"),
+ BAD_CAST(OString::boolean(m_bDate).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("date-format"),
+ BAD_CAST(m_aDateFormat.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("date-language"),
+ BAD_CAST(m_aDateLanguage.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("current-date"),
+ BAD_CAST(m_aCurrentDate.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("plain-text"),
+ BAD_CAST(OString::boolean(m_bPlainText).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("combo-box"),
+ BAD_CAST(OString::boolean(m_bComboBox).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("drop-down"),
+ BAD_CAST(OString::boolean(m_bDropDown).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("placeholder-doc-part"),
+ BAD_CAST(m_aPlaceholderDocPart.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("data-binding-prefix-mappings"),
+ BAD_CAST(m_aDataBindingPrefixMappings.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("data-binding-xpath"),
+ BAD_CAST(m_aDataBindingXpath.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("data-binding-store-item-id"),
+ BAD_CAST(m_aDataBindingStoreItemID.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("color"),
+ BAD_CAST(m_aColor.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("appearance"),
+ BAD_CAST(m_aAppearance.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("alias"),
+ BAD_CAST(m_aAlias.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("tag"), BAD_CAST(m_aTag.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("id"),
+ BAD_CAST(OString::number(m_nId).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("tab-index"),
+ BAD_CAST(OString::number(m_nTabIndex).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("lock"),
+ BAD_CAST(m_aLock.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("multiline"),
+ BAD_CAST(m_aMultiLine.toUtf8().getStr()));
+
+ if (!m_aListItems.empty())
+ {
+ for (const auto& rListItem : m_aListItems)
+ {
+ rListItem.dumpAsXml(pWriter);
+ }
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+void SwContentControlListItem::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwContentControlListItem"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("display-text"),
+ BAD_CAST(m_aDisplayText.toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"),
+ BAD_CAST(m_aValue.toUtf8().getStr()));
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+const OUString& SwContentControlListItem::ToString() const
+{
+ if (!m_aDisplayText.isEmpty())
+ {
+ return m_aDisplayText;
+ }
+
+ return m_aValue;
+}
+
+bool SwContentControlListItem::operator==(const SwContentControlListItem& rOther) const
+{
+ return m_aDisplayText == rOther.m_aDisplayText && m_aValue == rOther.m_aValue;
+}
+
+void SwContentControlListItem::ItemsToAny(const std::vector<SwContentControlListItem>& rItems,
+ uno::Any& rVal)
+{
+ uno::Sequence<uno::Sequence<beans::PropertyValue>> aRet(rItems.size());
+
+ uno::Sequence<beans::PropertyValue>* pRet = aRet.getArray();
+ for (size_t i = 0; i < rItems.size(); ++i)
+ {
+ const SwContentControlListItem& rItem = rItems[i];
+ uno::Sequence<beans::PropertyValue> aItem = {
+ comphelper::makePropertyValue("DisplayText", rItem.m_aDisplayText),
+ comphelper::makePropertyValue("Value", rItem.m_aValue),
+ };
+ pRet[i] = aItem;
+ }
+
+ rVal <<= aRet;
+}
+
+std::vector<SwContentControlListItem>
+SwContentControlListItem::ItemsFromAny(const css::uno::Any& rVal)
+{
+ std::vector<SwContentControlListItem> aRet;
+
+ uno::Sequence<uno::Sequence<beans::PropertyValue>> aSequence;
+ rVal >>= aSequence;
+ for (const auto& rItem : aSequence)
+ {
+ comphelper::SequenceAsHashMap aMap(rItem);
+ SwContentControlListItem aItem;
+ auto it = aMap.find("DisplayText");
+ if (it != aMap.end())
+ {
+ it->second >>= aItem.m_aDisplayText;
+ }
+ it = aMap.find("Value");
+ if (it != aMap.end())
+ {
+ it->second >>= aItem.m_aValue;
+ }
+ aRet.push_back(aItem);
+ }
+
+ return aRet;
+}
+
+SwTextContentControl* SwTextContentControl::CreateTextContentControl(SwDoc& rDoc,
+ SwTextNode* pTargetTextNode,
+ SwFormatContentControl& rAttr,
+ sal_Int32 nStart,
+ sal_Int32 nEnd, bool bIsCopy)
+{
+ if (bIsCopy)
+ {
+ // rAttr is already cloned, now call DoCopy to copy the SwContentControl
+ if (!pTargetTextNode)
+ {
+ SAL_WARN("sw.core",
+ "SwTextContentControl ctor: cannot copy content control without target node");
+ }
+ rAttr.DoCopy(*pTargetTextNode);
+ }
+ SwContentControlManager* pManager = &rDoc.GetContentControlManager();
+ auto pTextContentControl(new SwTextContentControl(pManager, rAttr, nStart, nEnd));
+ return pTextContentControl;
+}
+
+SwTextContentControl::SwTextContentControl(SwContentControlManager* pManager,
+ SwFormatContentControl& rAttr, sal_Int32 nStart,
+ sal_Int32 nEnd)
+ : SwTextAttr(rAttr, nStart)
+ , SwTextAttrNesting(rAttr, nStart, nEnd)
+ , m_pManager(pManager)
+{
+ rAttr.SetTextAttr(this);
+ SetHasDummyChar(true);
+ m_pManager->Insert(this);
+}
+
+SwTextContentControl::~SwTextContentControl()
+{
+ auto& rFormatContentControl = static_cast<SwFormatContentControl&>(GetAttr());
+ if (rFormatContentControl.GetTextAttr() == this)
+ {
+ rFormatContentControl.SetTextAttr(nullptr);
+ }
+}
+
+void SwTextContentControl::ChgTextNode(SwTextNode* pNode)
+{
+ auto& rFormatContentControl = static_cast<SwFormatContentControl&>(GetAttr());
+ if (rFormatContentControl.GetTextAttr() == this)
+ {
+ rFormatContentControl.NotifyChangeTextNode(pNode);
+
+ if (pNode)
+ {
+ m_pManager = &pNode->GetDoc().GetContentControlManager();
+ }
+ else
+ {
+ if (m_pManager)
+ {
+ m_pManager->Erase(this);
+ }
+ m_pManager = nullptr;
+ }
+ }
+}
+
+void SwTextContentControl::Delete(bool bSaveContents)
+{
+ if (!GetTextNode())
+ return;
+
+ SwPaM aPaM(*GetTextNode(), GetStart(), *GetTextNode(), *End());
+ if (bSaveContents)
+ GetTextNode()->GetDoc().ResetAttrs(aPaM, /*bTextAttr=*/true, { RES_TXTATR_CONTENTCONTROL });
+ else
+ GetTextNode()->GetDoc().getIDocumentContentOperations().DeleteAndJoin(aPaM);
+}
+
+SwTextNode* SwTextContentControl::GetTextNode() const
+{
+ auto& rFormatContentControl = static_cast<const SwFormatContentControl&>(GetAttr());
+ return rFormatContentControl.GetTextNode();
+}
+
+OUString SwTextContentControl::ToString() const
+{
+ if (!GetTextNode())
+ return OUString();
+
+ // Don't select the text attribute itself at the start.
+ sal_Int32 nStart = GetStart() + 1;
+ // Don't select the CH_TXTATR_BREAKWORD itself at the end.
+ sal_Int32 nEnd = *End() - 1;
+
+ SwPaM aPaM(*GetTextNode(), nStart, *GetTextNode(), nEnd);
+ return aPaM.GetText();
+}
+
+void SwTextContentControl::Invalidate()
+{
+ SwDocShell* pDocShell = GetTextNode() ? GetTextNode()->GetDoc().GetDocShell() : nullptr;
+ if (!pDocShell || !pDocShell->GetWrtShell())
+ return;
+
+ // save the cursor
+ // NOTE: needs further testing to see if this is adequate (i.e. in auto-run macros...)
+ pDocShell->GetWrtShell()->Push();
+
+ // visit the control in the text (which makes any necessary visual changes)
+ // NOTE: simply going to a checkbox causes a toggle, unless bOnlyRefresh
+ auto& rFormatContentControl = static_cast<SwFormatContentControl&>(GetAttr());
+ pDocShell->GetWrtShell()->GotoContentControl(rFormatContentControl, /*bOnlyRefresh=*/true);
+
+ pDocShell->GetWrtShell()->Pop(SwCursorShell::PopMode::DeleteCurrent);
+}
+
+void SwTextContentControl::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextContentControl"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ SwTextAttr::dumpAsXml(pWriter);
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SwContentControlManager::SwContentControlManager() {}
+
+void SwContentControlManager::Insert(SwTextContentControl* pTextContentControl)
+{
+ m_aContentControls.push_back(pTextContentControl);
+}
+
+void SwContentControlManager::Erase(SwTextContentControl* pTextContentControl)
+{
+ std::erase(m_aContentControls, pTextContentControl);
+}
+
+SwTextContentControl* SwContentControlManager::Get(size_t nIndex)
+{
+ // Only sort now: the items may not have an associated text node by the time they are inserted
+ // into the container.
+ std::sort(m_aContentControls.begin(), m_aContentControls.end(),
+ [](SwTextContentControl*& pLhs, SwTextContentControl*& pRhs) -> bool {
+ SwNodeOffset nIdxLHS = pLhs->GetTextNode()->GetIndex();
+ SwNodeOffset nIdxRHS = pRhs->GetTextNode()->GetIndex();
+ if (nIdxLHS == nIdxRHS)
+ {
+ return pLhs->GetStart() < pRhs->GetStart();
+ }
+
+ return nIdxLHS < nIdxRHS;
+ });
+
+ return m_aContentControls[nIndex];
+}
+
+SwTextContentControl* SwContentControlManager::UnsortedGet(size_t nIndex)
+{
+ assert(nIndex < m_aContentControls.size());
+ return m_aContentControls[nIndex];
+}
+
+void SwContentControlManager::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwContentControlManager"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ for (const auto& pContentControl : m_aContentControls)
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextContentControl"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", pContentControl);
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/attrlinebreak.cxx b/sw/source/core/txtnode/attrlinebreak.cxx
new file mode 100644
index 0000000000..5ce9301820
--- /dev/null
+++ b/sw/source/core/txtnode/attrlinebreak.cxx
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <formatlinebreak.hxx>
+
+#include <libxml/xmlwriter.h>
+
+#include <vcl/svapp.hxx>
+
+#include <hintids.hxx>
+#include <pam.hxx>
+#include <textlinebreak.hxx>
+#include <ndtxt.hxx>
+#include <unotextrange.hxx>
+#include <unolinebreak.hxx>
+
+using namespace com::sun::star;
+
+SwFormatLineBreak::SwFormatLineBreak(SwLineBreakClear eClear)
+ : SfxEnumItem(RES_TXTATR_LINEBREAK, eClear)
+ , sw::BroadcastingModify()
+ , m_pTextAttr(nullptr)
+{
+}
+
+SwFormatLineBreak::~SwFormatLineBreak() {}
+
+void SwFormatLineBreak::SetXLineBreak(rtl::Reference<SwXLineBreak> const& xLineBreak)
+{
+ m_wXLineBreak = xLineBreak.get();
+}
+
+bool SwFormatLineBreak::operator==(const SfxPoolItem& rAttr) const
+{
+ assert(SfxPoolItem::operator==(rAttr));
+ return GetValue() == static_cast<const SwFormatLineBreak&>(rAttr).GetValue();
+}
+
+SwFormatLineBreak* SwFormatLineBreak::Clone(SfxItemPool*) const
+{
+ return new SwFormatLineBreak(GetValue());
+}
+
+void SwFormatLineBreak::SwClientNotify(const SwModify&, const SfxHint& rHint)
+{
+ if (rHint.GetId() != SfxHintId::SwLegacyModify)
+ return;
+ auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ CallSwClientNotify(rHint);
+ if (RES_REMOVE_UNO_OBJECT == pLegacy->GetWhich())
+ {
+ SetXLineBreak(nullptr);
+ }
+}
+
+sal_uInt16 SwFormatLineBreak::GetValueCount() const
+{
+ return static_cast<sal_uInt16>(SwLineBreakClear::LAST) + 1;
+}
+
+rtl::Reference<SwXTextRange> SwFormatLineBreak::GetAnchor() const
+{
+ SolarMutexGuard aGuard;
+
+ if (!m_pTextAttr)
+ return {};
+
+ SwPaM aPam(m_pTextAttr->GetTextNode(), m_pTextAttr->GetStart());
+ aPam.SetMark();
+ aPam.GetMark()->AdjustContent(+1);
+ rtl::Reference<SwXTextRange> xRet
+ = SwXTextRange::CreateXTextRange(aPam.GetDoc(), *aPam.Start(), aPam.End());
+ return xRet;
+}
+
+void SwFormatLineBreak::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatLineBreak"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"),
+ BAD_CAST(OString::number(GetEnumValue()).getStr()));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("m_pTextAttr"), "%p", m_pTextAttr);
+
+ SfxPoolItem::dumpAsXml(pWriter);
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SwTextLineBreak::SwTextLineBreak(SwFormatLineBreak& rAttr, sal_Int32 nStartPos)
+ : SwTextAttr(rAttr, nStartPos)
+ , m_pTextNode(nullptr)
+{
+ rAttr.SetTextLineBreak(this);
+ SetHasDummyChar(true);
+}
+
+SwTextLineBreak::~SwTextLineBreak() {}
+
+void SwTextLineBreak::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextLineBreak"));
+ if (m_pTextNode)
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pTextNode"));
+ (void)xmlTextWriterWriteAttribute(
+ pWriter, BAD_CAST("index"),
+ BAD_CAST(OString::number(sal_Int32(m_pTextNode->GetIndex())).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+
+ SwTextAttr::dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+void SwTextLineBreak::SetTextNode(SwTextNode* pNew) { m_pTextNode = pNew; }
+
+const SwTextNode& SwTextLineBreak::GetTextNode() const
+{
+ assert(m_pTextNode);
+ return *m_pTextNode;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/chrfmt.cxx b/sw/source/core/txtnode/chrfmt.cxx
new file mode 100644
index 0000000000..e22b6bab04
--- /dev/null
+++ b/sw/source/core/txtnode/chrfmt.cxx
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <libxml/xmlwriter.h>
+
+#include <charfmt.hxx>
+#include <charformats.hxx>
+#include <doc.hxx>
+
+void SwCharFormat::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwCharFormat"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"),
+ BAD_CAST(GetName().toUtf8().getStr()));
+
+ if (mpLinkedParaFormat)
+ {
+ (void)xmlTextWriterWriteAttribute(
+ pWriter, BAD_CAST("linked"), BAD_CAST(mpLinkedParaFormat->GetName().toUtf8().getStr()));
+ }
+
+ GetAttrSet().dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+void SwCharFormat::SetLinkedParaFormat(SwTextFormatColl* pLink) { mpLinkedParaFormat = pLink; }
+
+const SwTextFormatColl* SwCharFormat::GetLinkedParaFormat() const { return mpLinkedParaFormat; }
+
+SwCharFormat::~SwCharFormat()
+{
+ if (GetDoc()->IsInDtor())
+ {
+ return;
+ }
+
+ for (const auto& pTextFormat : *GetDoc()->GetTextFormatColls())
+ {
+ if (pTextFormat->GetLinkedCharFormat() == this)
+ {
+ pTextFormat->SetLinkedCharFormat(nullptr);
+ }
+ }
+}
+
+void SwCharFormats::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwCharFormats"));
+ for (size_t i = 0; i < size(); ++i)
+ GetFormat(i)->dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SwCharFormats::SwCharFormats()
+ : m_PosIndex(m_Array.get<0>())
+ , m_NameIndex(m_Array.get<1>())
+{
+}
+
+SwCharFormats::~SwCharFormats()
+{
+ // default char format is owned by SwDoc
+ DeleteAndDestroyAll(true);
+}
+
+SwCharFormats::const_iterator SwCharFormats::find(const SwCharFormat* x) const
+{
+ ByName::iterator it
+ = m_NameIndex.find(std::make_tuple(x->GetName(), const_cast<SwCharFormat*>(x)));
+ return m_Array.project<0>(it);
+}
+
+SwCharFormats::ByName::const_iterator SwCharFormats::findByName(const OUString& name) const
+{
+ return m_NameIndex.find(std::make_tuple(name));
+}
+
+SwCharFormat* SwCharFormats::FindFormatByName(const OUString& rName) const
+{
+ auto it = findByName(rName);
+ if (it != m_NameIndex.end())
+ return *it;
+ return nullptr;
+}
+
+void SwCharFormats::DeleteAndDestroyAll(bool keepDefault)
+{
+ if (empty())
+ return;
+ const int _offset = keepDefault ? 1 : 0;
+ for (const_iterator it = begin() + _offset; it != end(); ++it)
+ {
+ assert(!(*it)->HasName(u"Character style"));
+ delete *it;
+ }
+ if (_offset)
+ m_PosIndex.erase(begin() + _offset, end());
+ else
+ m_Array.clear();
+}
+
+void SwCharFormats::insert(SwCharFormat* x)
+{
+ assert(!ContainsFormat(x));
+ m_PosIndex.push_back(x);
+}
+
+void SwCharFormats::erase(const_iterator const& position) { m_PosIndex.erase(position); }
+
+bool SwCharFormats::ContainsFormat(const SwCharFormat* x) const { return find(x) != end(); }
+
+/** Need to call this when the format name changes */
+void SwCharFormats::SetFormatNameAndReindex(SwCharFormat* v, const OUString& sNewName)
+{
+ auto it = find(v);
+ erase(it);
+ v->SetFormatName(sNewName);
+ insert(v);
+}
+
+size_t SwCharFormats::GetPos(const SwCharFormat* p) const
+{
+ auto it = find(p);
+ return it == end() ? SIZE_MAX : it - begin();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/fmtatr2.cxx b/sw/source/core/txtnode/fmtatr2.cxx
new file mode 100644
index 0000000000..3e888b660f
--- /dev/null
+++ b/sw/source/core/txtnode/fmtatr2.cxx
@@ -0,0 +1,867 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <libxml/xmlwriter.h>
+#include <hintids.hxx>
+#include <poolfmt.hxx>
+#include <unomid.h>
+
+#include <o3tl/any.hxx>
+#include <svl/macitem.hxx>
+#include <svl/stylepool.hxx>
+#include <fmtautofmt.hxx>
+#include <fchrfmt.hxx>
+#include <fmtinfmt.hxx>
+#include <txtatr.hxx>
+#include <fmtruby.hxx>
+#include <charfmt.hxx>
+#include <unoevent.hxx>
+#include <unoport.hxx>
+#include <com/sun/star/text/RubyAdjust.hpp>
+#include <com/sun/star/text/RubyPosition.hpp>
+#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
+#include <com/sun/star/util/XCloneable.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+
+#include <com/sun/star/uno/Any.h>
+#include <SwStyleNameMapper.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <fmtmeta.hxx>
+#include <ndtxt.hxx>
+#include <doc.hxx>
+#include <unometa.hxx>
+#include <unotext.hxx>
+#include <docsh.hxx>
+#include <osl/diagnose.h>
+
+#include <algorithm>
+#include <utility>
+
+using namespace ::com::sun::star;
+
+
+SfxPoolItem* SwFormatINetFormat::CreateDefault() { return new SwFormatINetFormat; }
+
+SwFormatCharFormat::SwFormatCharFormat( SwCharFormat *pFormat )
+ : SfxPoolItem( RES_TXTATR_CHARFMT ),
+ SwClient(pFormat),
+ m_pTextAttribute( nullptr )
+{
+}
+
+SwFormatCharFormat::SwFormatCharFormat( const SwFormatCharFormat& rAttr )
+ : SfxPoolItem( RES_TXTATR_CHARFMT ),
+ SwClient( rAttr.GetCharFormat() ),
+ m_pTextAttribute( nullptr )
+{
+}
+
+SwFormatCharFormat::~SwFormatCharFormat() {}
+
+bool SwFormatCharFormat::operator==( const SfxPoolItem& rAttr ) const
+{
+ assert(SfxPoolItem::operator==(rAttr));
+ return GetCharFormat() == static_cast<const SwFormatCharFormat&>(rAttr).GetCharFormat();
+}
+
+SwFormatCharFormat* SwFormatCharFormat::Clone( SfxItemPool* ) const
+{
+ return new SwFormatCharFormat( *this );
+}
+
+// forward to the TextAttribute
+void SwFormatCharFormat::SwClientNotify(const SwModify&, const SfxHint& rHint)
+{
+ if(rHint.GetId() == SfxHintId::SwAutoFormatUsedHint)
+ {
+ if(m_pTextAttribute)
+ m_pTextAttribute->HandleAutoFormatUsedHint(static_cast<const sw::AutoFormatUsedHint&>(rHint));
+ return;
+ }
+ if (rHint.GetId() != SfxHintId::SwLegacyModify)
+ return;
+ auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ if(m_pTextAttribute)
+ m_pTextAttribute->TriggerNodeUpdate(*pLegacy);
+}
+
+bool SwFormatCharFormat::QueryValue( uno::Any& rVal, sal_uInt8 ) const
+{
+ OUString sCharFormatName;
+ if(GetCharFormat())
+ SwStyleNameMapper::FillProgName(GetCharFormat()->GetName(), sCharFormatName, SwGetPoolIdFromName::ChrFmt );
+ rVal <<= sCharFormatName;
+ return true;
+}
+bool SwFormatCharFormat::PutValue( const uno::Any& , sal_uInt8 )
+{
+ OSL_FAIL("format cannot be set with PutValue!");
+ return false;
+}
+
+void SwFormatCharFormat::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatCharFormat"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("text-attribute"), "%p", m_pTextAttribute);
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("char-format"), "%p", GetCharFormat());
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("char-format-name"),
+ BAD_CAST(GetCharFormat()->GetName().toUtf8().getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SwFormatAutoFormat::SwFormatAutoFormat( sal_uInt16 nInitWhich )
+ : SfxPoolItem( nInitWhich )
+{
+}
+
+bool SwFormatAutoFormat::operator==( const SfxPoolItem& rAttr ) const
+{
+ assert(SfxPoolItem::operator==(rAttr));
+ return mpHandle == static_cast<const SwFormatAutoFormat&>(rAttr).mpHandle;
+}
+
+SwFormatAutoFormat* SwFormatAutoFormat::Clone( SfxItemPool* ) const
+{
+ return new SwFormatAutoFormat( *this );
+}
+
+bool SwFormatAutoFormat::QueryValue( uno::Any& rVal, sal_uInt8 ) const
+{
+ rVal <<= StylePool::nameOf( mpHandle );
+ return true;
+}
+
+bool SwFormatAutoFormat::PutValue( const uno::Any& , sal_uInt8 )
+{
+ //the format is not renameable via API
+ return false;
+}
+
+void SwFormatAutoFormat::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFormatAutoFormat"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr()));
+ if (mpHandle) // pool default doesn't have one
+ {
+ mpHandle->dumpAsXml(pWriter);
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SwFormatINetFormat::SwFormatINetFormat()
+ : SfxPoolItem( RES_TXTATR_INETFMT )
+ , msURL()
+ , msTargetFrame()
+ , msINetFormatName()
+ , msVisitedFormatName()
+ , msHyperlinkName()
+ , mpTextAttr( nullptr )
+ , mnINetFormatId( 0 )
+ , mnVisitedFormatId( 0 )
+{}
+
+SwFormatINetFormat::SwFormatINetFormat( OUString aURL, OUString aTarget )
+ : SfxPoolItem( RES_TXTATR_INETFMT )
+ , msURL( std::move(aURL) )
+ , msTargetFrame( std::move(aTarget) )
+ , msINetFormatName()
+ , msVisitedFormatName()
+ , msHyperlinkName()
+ , mpTextAttr( nullptr )
+ , mnINetFormatId( RES_POOLCHR_INET_NORMAL )
+ , mnVisitedFormatId( RES_POOLCHR_INET_VISIT )
+{
+ SwStyleNameMapper::FillUIName( mnINetFormatId, msINetFormatName );
+ SwStyleNameMapper::FillUIName( mnVisitedFormatId, msVisitedFormatName );
+}
+
+SwFormatINetFormat::SwFormatINetFormat( const SwFormatINetFormat& rAttr )
+ : SfxPoolItem( RES_TXTATR_INETFMT )
+ , sw::BroadcasterMixin()
+ , msURL( rAttr.GetValue() )
+ , msTargetFrame( rAttr.msTargetFrame )
+ , msINetFormatName( rAttr.msINetFormatName )
+ , msVisitedFormatName( rAttr.msVisitedFormatName )
+ , msHyperlinkName( rAttr.msHyperlinkName )
+ , mpTextAttr( nullptr )
+ , mnINetFormatId( rAttr.mnINetFormatId )
+ , mnVisitedFormatId( rAttr.mnVisitedFormatId )
+{
+ if ( rAttr.GetMacroTable() )
+ mpMacroTable.reset( new SvxMacroTableDtor( *rAttr.GetMacroTable() ) );
+}
+
+SwFormatINetFormat::~SwFormatINetFormat()
+{
+}
+
+bool SwFormatINetFormat::operator==( const SfxPoolItem& rAttr ) const
+{
+ assert(SfxPoolItem::operator==(rAttr));
+ bool bRet = SfxPoolItem::operator==( rAttr )
+ && msURL == static_cast<const SwFormatINetFormat&>(rAttr).msURL
+ && msHyperlinkName == static_cast<const SwFormatINetFormat&>(rAttr).msHyperlinkName
+ && msTargetFrame == static_cast<const SwFormatINetFormat&>(rAttr).msTargetFrame
+ && msINetFormatName == static_cast<const SwFormatINetFormat&>(rAttr).msINetFormatName
+ && msVisitedFormatName == static_cast<const SwFormatINetFormat&>(rAttr).msVisitedFormatName
+ && mnINetFormatId == static_cast<const SwFormatINetFormat&>(rAttr).mnINetFormatId
+ && mnVisitedFormatId == static_cast<const SwFormatINetFormat&>(rAttr).mnVisitedFormatId;
+
+ if( !bRet )
+ return false;
+
+ const SvxMacroTableDtor* pOther = static_cast<const SwFormatINetFormat&>(rAttr).mpMacroTable.get();
+ if( !mpMacroTable )
+ return ( !pOther || pOther->empty() );
+ if( !pOther )
+ return mpMacroTable->empty();
+
+ const SvxMacroTableDtor& rOwn = *mpMacroTable;
+ const SvxMacroTableDtor& rOther = *pOther;
+
+ return rOwn == rOther;
+}
+
+SwFormatINetFormat* SwFormatINetFormat::Clone( SfxItemPool* ) const
+{
+ return new SwFormatINetFormat( *this );
+}
+
+void SwFormatINetFormat::SetMacroTable( const SvxMacroTableDtor* pNewTable )
+{
+ if( pNewTable )
+ {
+ if( mpMacroTable )
+ *mpMacroTable = *pNewTable;
+ else
+ mpMacroTable.reset( new SvxMacroTableDtor( *pNewTable ) );
+ }
+ else
+ {
+ mpMacroTable.reset();
+ }
+}
+
+void SwFormatINetFormat::SetMacro( SvMacroItemId nEvent, const SvxMacro& rMacro )
+{
+ if( !mpMacroTable )
+ mpMacroTable.reset( new SvxMacroTableDtor );
+
+ mpMacroTable->Insert( nEvent, rMacro );
+}
+
+const SvxMacro* SwFormatINetFormat::GetMacro( SvMacroItemId nEvent ) const
+{
+ const SvxMacro* pRet = nullptr;
+ if( mpMacroTable && mpMacroTable->IsKeyValid( nEvent ) )
+ pRet = mpMacroTable->Get( nEvent );
+ return pRet;
+}
+
+bool SwFormatINetFormat::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const
+{
+ nMemberId &= ~CONVERT_TWIPS;
+ switch(nMemberId)
+ {
+ case MID_URL_URL:
+ rVal <<= msURL;
+ break;
+ case MID_URL_TARGET:
+ rVal <<= msTargetFrame;
+ break;
+ case MID_URL_HYPERLINKNAME:
+ rVal <<= msHyperlinkName;
+ break;
+ case MID_URL_VISITED_FMT:
+ {
+ OUString sVal = msVisitedFormatName;
+ if (sVal.isEmpty() && mnVisitedFormatId != 0)
+ SwStyleNameMapper::FillUIName(mnVisitedFormatId, sVal);
+ if (!sVal.isEmpty())
+ SwStyleNameMapper::FillProgName(sVal, sVal,
+ SwGetPoolIdFromName::ChrFmt);
+ rVal <<= sVal;
+ }
+ break;
+ case MID_URL_UNVISITED_FMT:
+ {
+ OUString sVal = msINetFormatName;
+ if (sVal.isEmpty() && mnINetFormatId != 0)
+ SwStyleNameMapper::FillUIName(mnINetFormatId, sVal);
+ if (!sVal.isEmpty())
+ SwStyleNameMapper::FillProgName(sVal, sVal,
+ SwGetPoolIdFromName::ChrFmt);
+ rVal <<= sVal;
+ }
+ break;
+ case MID_URL_HYPERLINKEVENTS:
+ {
+ // create (and return) event descriptor
+ rtl::Reference<SwHyperlinkEventDescriptor> pEvents =
+ new SwHyperlinkEventDescriptor();
+ pEvents->copyMacrosFromINetFormat(*this);
+
+ // all others return a string; so we just set rVal here and exit
+ rVal <<= uno::Reference<container::XNameReplace>(pEvents);
+ }
+ break;
+ default:
+ rVal <<= OUString();
+ break;
+ }
+ return true;
+}
+bool SwFormatINetFormat::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId )
+{
+ bool bRet = true;
+ nMemberId &= ~CONVERT_TWIPS;
+
+ // all properties except HyperlinkEvents are of type string, hence
+ // we treat HyperlinkEvents specially
+ if (MID_URL_HYPERLINKEVENTS == nMemberId)
+ {
+ uno::Reference<container::XNameReplace> xReplace;
+ rVal >>= xReplace;
+ if (xReplace.is())
+ {
+ // Create hyperlink event descriptor. Then copy events
+ // from argument into descriptor. Then copy events from
+ // the descriptor into the format.
+ rtl::Reference<SwHyperlinkEventDescriptor> pEvents = new SwHyperlinkEventDescriptor();
+ pEvents->copyMacrosFromNameReplace(xReplace);
+ pEvents->copyMacrosIntoINetFormat(*this);
+ }
+ else
+ {
+ // wrong type!
+ bRet = false;
+ }
+ }
+ else
+ {
+ // all string properties:
+ if(rVal.getValueType() != ::cppu::UnoType<OUString>::get())
+ return false;
+
+ switch(nMemberId)
+ {
+ case MID_URL_URL:
+ rVal >>= msURL;
+ break;
+ case MID_URL_TARGET:
+ rVal >>= msTargetFrame;
+ break;
+ case MID_URL_HYPERLINKNAME:
+ rVal >>= msHyperlinkName;
+ break;
+ case MID_URL_VISITED_FMT:
+ {
+ OUString sVal;
+ rVal >>= sVal;
+ OUString aString;
+ SwStyleNameMapper::FillUIName( sVal, aString, SwGetPoolIdFromName::ChrFmt );
+ msVisitedFormatName = aString;
+ mnVisitedFormatId = SwStyleNameMapper::GetPoolIdFromUIName( msVisitedFormatName,
+ SwGetPoolIdFromName::ChrFmt );
+ }
+ break;
+ case MID_URL_UNVISITED_FMT:
+ {
+ OUString sVal;
+ rVal >>= sVal;
+ OUString aString;
+ SwStyleNameMapper::FillUIName( sVal, aString, SwGetPoolIdFromName::ChrFmt );
+ msINetFormatName = aString;
+ mnINetFormatId = SwStyleNameMapper::GetPoolIdFromUIName( msINetFormatName, SwGetPoolIdFromName::ChrFmt );
+ }
+ break;
+ default:
+ bRet = false;
+ }
+ }
+ return bRet;
+}
+
+SwFormatRuby::SwFormatRuby( OUString aRubyText )
+ : SfxPoolItem( RES_TXTATR_CJK_RUBY ),
+ m_sRubyText( std::move(aRubyText) ),
+ m_pTextAttr( nullptr ),
+ m_nCharFormatId( 0 ),
+ m_nPosition( 0 ),
+ m_eAdjustment( css::text::RubyAdjust_LEFT )
+{
+}
+
+SwFormatRuby::SwFormatRuby( const SwFormatRuby& rAttr )
+ : SfxPoolItem( RES_TXTATR_CJK_RUBY ),
+ m_sRubyText( rAttr.m_sRubyText ),
+ m_sCharFormatName( rAttr.m_sCharFormatName ),
+ m_pTextAttr( nullptr ),
+ m_nCharFormatId( rAttr.m_nCharFormatId),
+ m_nPosition( rAttr.m_nPosition ),
+ m_eAdjustment( rAttr.m_eAdjustment )
+{
+}
+
+SwFormatRuby::~SwFormatRuby()
+{
+}
+
+SwFormatRuby& SwFormatRuby::operator=( const SwFormatRuby& rAttr )
+{
+ // SwFormatRuby is not shareable, so ptr compare is OK
+ if (areSfxPoolItemPtrsEqual(this, &rAttr))
+ return *this;
+
+ m_sRubyText = rAttr.m_sRubyText;
+ m_sCharFormatName = rAttr.m_sCharFormatName;
+ m_nCharFormatId = rAttr.m_nCharFormatId;
+ m_nPosition = rAttr.m_nPosition;
+ m_eAdjustment = rAttr.m_eAdjustment;
+ m_pTextAttr = nullptr;
+ return *this;
+}
+
+bool SwFormatRuby::operator==( const SfxPoolItem& rAttr ) const
+{
+ assert(SfxPoolItem::operator==(rAttr));
+ return m_sRubyText == static_cast<const SwFormatRuby&>(rAttr).m_sRubyText &&
+ m_sCharFormatName == static_cast<const SwFormatRuby&>(rAttr).m_sCharFormatName &&
+ m_nCharFormatId == static_cast<const SwFormatRuby&>(rAttr).m_nCharFormatId &&
+ m_nPosition == static_cast<const SwFormatRuby&>(rAttr).m_nPosition &&
+ m_eAdjustment == static_cast<const SwFormatRuby&>(rAttr).m_eAdjustment;
+}
+
+SwFormatRuby* SwFormatRuby::Clone( SfxItemPool* ) const
+{
+ return new SwFormatRuby( *this );
+}
+
+bool SwFormatRuby::QueryValue( uno::Any& rVal,
+ sal_uInt8 nMemberId ) const
+{
+ bool bRet = true;
+ nMemberId &= ~CONVERT_TWIPS;
+ switch( nMemberId )
+ {
+ case MID_RUBY_TEXT: rVal <<= m_sRubyText; break;
+ case MID_RUBY_ADJUST: rVal <<= static_cast<sal_Int16>(m_eAdjustment); break;
+ case MID_RUBY_CHARSTYLE:
+ {
+ OUString aString;
+ SwStyleNameMapper::FillProgName(m_sCharFormatName, aString, SwGetPoolIdFromName::ChrFmt );
+ rVal <<= aString;
+ }
+ break;
+ case MID_RUBY_ABOVE:
+ {
+ rVal <<= static_cast<bool>(!m_nPosition);
+ }
+ break;
+ case MID_RUBY_POSITION:
+ {
+ rVal <<= m_nPosition;
+ }
+ break;
+ default:
+ bRet = false;
+ }
+ return bRet;
+}
+bool SwFormatRuby::PutValue( const uno::Any& rVal,
+ sal_uInt8 nMemberId )
+{
+ bool bRet = true;
+ nMemberId &= ~CONVERT_TWIPS;
+ switch( nMemberId )
+ {
+ case MID_RUBY_TEXT:
+ bRet = rVal >>= m_sRubyText;
+ break;
+ case MID_RUBY_ADJUST:
+ {
+ sal_Int16 nSet = 0;
+ rVal >>= nSet;
+ if(nSet >= sal_Int16(text::RubyAdjust_LEFT) && nSet <= sal_Int16(text::RubyAdjust_INDENT_BLOCK))
+ m_eAdjustment = static_cast<text::RubyAdjust>(nSet);
+ else
+ bRet = false;
+ }
+ break;
+ case MID_RUBY_ABOVE:
+ {
+ const uno::Type& rType = cppu::UnoType<bool>::get();
+ if(rVal.hasValue() && rVal.getValueType() == rType)
+ {
+ bool bAbove = *o3tl::doAccess<bool>(rVal);
+ m_nPosition = bAbove ? 0 : 1;
+ }
+ }
+ break;
+ case MID_RUBY_POSITION:
+ {
+ sal_Int16 nSet = 0;
+ rVal >>= nSet;
+ if(nSet >= sal_Int16(text::RubyPosition::ABOVE) && nSet <= sal_Int16(text::RubyPosition::INTER_CHARACTER))
+ m_nPosition = nSet;
+ else
+ bRet = false;
+ }
+ break;
+ case MID_RUBY_CHARSTYLE:
+ {
+ OUString sTmp;
+ bRet = rVal >>= sTmp;
+ if(bRet)
+ m_sCharFormatName = SwStyleNameMapper::GetUIName(sTmp, SwGetPoolIdFromName::ChrFmt );
+ }
+ break;
+ default:
+ bRet = false;
+ }
+ return bRet;
+}
+
+SwFormatMeta * SwFormatMeta::CreatePoolDefault(const sal_uInt16 i_nWhich)
+{
+ return new SwFormatMeta(i_nWhich);
+}
+
+SwFormatMeta::SwFormatMeta(const sal_uInt16 i_nWhich)
+ : SfxPoolItem( i_nWhich )
+ , m_pMeta()
+ , m_pTextAttr( nullptr )
+{
+ OSL_ENSURE((RES_TXTATR_META == i_nWhich) || (RES_TXTATR_METAFIELD == i_nWhich),
+ "ERROR: SwFormatMeta: invalid which id!");
+}
+
+SwFormatMeta::SwFormatMeta( std::shared_ptr< ::sw::Meta > i_pMeta,
+ const sal_uInt16 i_nWhich )
+ : SfxPoolItem( i_nWhich )
+ , m_pMeta( std::move(i_pMeta) )
+ , m_pTextAttr( nullptr )
+{
+ OSL_ENSURE((RES_TXTATR_META == i_nWhich) || (RES_TXTATR_METAFIELD == i_nWhich),
+ "ERROR: SwFormatMeta: invalid which id!");
+ OSL_ENSURE(m_pMeta, "SwFormatMeta: no Meta ?");
+ // DO NOT call m_pMeta->SetFormatMeta(this) here; only from SetTextAttr!
+}
+
+SwFormatMeta::~SwFormatMeta()
+{
+ // SwFormatMeta is not shareable, so ptr compare is OK
+ if (m_pMeta && areSfxPoolItemPtrsEqual(m_pMeta->GetFormatMeta(), this))
+ {
+ NotifyChangeTextNode(nullptr);
+ m_pMeta->SetFormatMeta(nullptr);
+ }
+}
+
+bool SwFormatMeta::operator==( const SfxPoolItem & i_rOther ) const
+{
+ return SfxPoolItem::operator==( i_rOther )
+ && m_pMeta == static_cast<SwFormatMeta const &>( i_rOther ).m_pMeta;
+}
+
+SwFormatMeta* SwFormatMeta::Clone( SfxItemPool * /*pPool*/ ) const
+{
+ // if this is indeed a copy, then DoCopy must be called later!
+ return m_pMeta // #i105148# pool default may be cloned also!
+ ? new SwFormatMeta( m_pMeta, Which() ) : new SwFormatMeta( Which() );
+}
+
+void SwFormatMeta::SetTextAttr(SwTextMeta * const i_pTextAttr)
+{
+ OSL_ENSURE(!(m_pTextAttr && i_pTextAttr),
+ "SwFormatMeta::SetTextAttr: already has text attribute?");
+ OSL_ENSURE( m_pTextAttr || i_pTextAttr ,
+ "SwFormatMeta::SetTextAttr: no attribute to remove?");
+ m_pTextAttr = i_pTextAttr;
+ OSL_ENSURE(m_pMeta, "inserted SwFormatMeta has no sw::Meta?");
+ // the sw::Meta must be able to find the current text attribute!
+ if (m_pMeta)
+ {
+ if (i_pTextAttr)
+ {
+ m_pMeta->SetFormatMeta(this);
+ }
+ // SwFormatMeta is not shareable, so ptr compare is OK
+ else if (areSfxPoolItemPtrsEqual(m_pMeta->GetFormatMeta(), this))
+ { // text attribute gone => de-register from text node!
+ NotifyChangeTextNode(nullptr);
+ m_pMeta->SetFormatMeta(nullptr);
+ }
+ }
+}
+
+void SwFormatMeta::NotifyChangeTextNode(SwTextNode *const pTextNode)
+{
+ // N.B.: do not reset m_pTextAttr here: see call in nodes.cxx,
+ // where the hint is not deleted!
+ OSL_ENSURE(m_pMeta, "SwFormatMeta::NotifyChangeTextNode: no Meta?");
+ // SwFormatMeta is not shareable, so ptr compare is OK
+ if (m_pMeta && areSfxPoolItemPtrsEqual(m_pMeta->GetFormatMeta(), this))
+ { // do not call Modify, that would call SwXMeta::SwClientNotify
+ m_pMeta->NotifyChangeTextNode(pTextNode);
+ }
+}
+
+// this SwFormatMeta has been cloned and points at the same sw::Meta as the source
+// this method copies the sw::Meta
+void SwFormatMeta::DoCopy(::sw::MetaFieldManager & i_rTargetDocManager,
+ SwTextNode & i_rTargetTextNode)
+{
+ OSL_ENSURE(m_pMeta, "DoCopy called for SwFormatMeta with no sw::Meta?");
+ if (!m_pMeta)
+ return;
+
+ const std::shared_ptr< ::sw::Meta> pOriginal( m_pMeta );
+ if (RES_TXTATR_META == Which())
+ {
+ m_pMeta = std::make_shared<::sw::Meta>(this);
+ }
+ else
+ {
+ ::sw::MetaField *const pMetaField(
+ static_cast< ::sw::MetaField* >(pOriginal.get()));
+ m_pMeta = i_rTargetDocManager.makeMetaField( this,
+ pMetaField->m_nNumberFormat, pMetaField->IsFixedLanguage() );
+ }
+ // Meta must have a text node before calling RegisterAsCopyOf
+ m_pMeta->NotifyChangeTextNode(& i_rTargetTextNode);
+ // this cannot be done in Clone: a Clone is not necessarily a copy!
+ m_pMeta->RegisterAsCopyOf(*pOriginal);
+}
+
+namespace sw {
+
+Meta::Meta(SwFormatMeta * const i_pFormat)
+ : ::sfx2::Metadatable()
+ , sw::BroadcastingModify()
+ , m_pFormat(i_pFormat)
+ , m_pTextNode(nullptr)
+{
+}
+
+Meta::~Meta()
+{
+}
+
+SwTextMeta * Meta::GetTextAttr() const
+{
+ return m_pFormat ? m_pFormat->GetTextAttr() : nullptr;
+}
+
+void Meta::SetXMeta(rtl::Reference<SwXMeta> const& xMeta)
+{ m_wXMeta = xMeta.get(); }
+
+void Meta::NotifyChangeTextNode(SwTextNode *const pTextNode)
+{
+ m_pTextNode = pTextNode;
+ if (m_pTextNode && (GetRegisteredIn() != m_pTextNode))
+ {
+ m_pTextNode->Add(this);
+ }
+ else if (!m_pTextNode)
+ {
+ EndListeningAll();
+ }
+ if (!pTextNode) // text node gone? invalidate UNO object!
+ {
+ GetNotifier().Broadcast(SfxHint(SfxHintId::Deinitializing));
+ }
+}
+
+void Meta::SwClientNotify(const SwModify&, const SfxHint& rHint)
+{
+ if (rHint.GetId() != SfxHintId::SwLegacyModify)
+ return;
+ auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ CallSwClientNotify(rHint);
+ GetNotifier().Broadcast(SfxHint(SfxHintId::DataChanged));
+ if(RES_REMOVE_UNO_OBJECT == pLegacy->GetWhich())
+ { // invalidate cached uno object
+ SetXMeta(nullptr);
+ GetNotifier().Broadcast(SfxHint(SfxHintId::Deinitializing));
+ }
+}
+
+// sfx2::Metadatable
+::sfx2::IXmlIdRegistry& Meta::GetRegistry()
+{
+ SwTextNode * const pTextNode( GetTextNode() );
+ // GetRegistry may only be called on a meta that is actually in the
+ // document, which means it has a pointer to its text node
+ OSL_ENSURE(pTextNode, "ERROR: GetRegistry: no text node?");
+ if (!pTextNode)
+ throw uno::RuntimeException();
+ return pTextNode->GetRegistry();
+}
+
+bool Meta::IsInClipboard() const
+{
+ const SwTextNode * const pTextNode( GetTextNode() );
+// no text node: in UNDO OSL_ENSURE(pTextNode, "IsInClipboard: no text node?");
+ return pTextNode && pTextNode->IsInClipboard();
+}
+
+bool Meta::IsInUndo() const
+{
+ const SwTextNode * const pTextNode( GetTextNode() );
+// no text node: in UNDO OSL_ENSURE(pTextNode, "IsInUndo: no text node?");
+ return pTextNode == nullptr || pTextNode->IsInUndo();
+}
+
+bool Meta::IsInContent() const
+{
+ const SwTextNode * const pTextNode( GetTextNode() );
+ OSL_ENSURE(pTextNode, "IsInContent: no text node?");
+ return pTextNode == nullptr || pTextNode->IsInContent();
+}
+
+css::uno::Reference< css::rdf::XMetadatable > Meta::MakeUnoObject()
+{
+ return SwXMeta::CreateXMeta(*this, {}, {});
+}
+
+MetaField::MetaField(SwFormatMeta * const i_pFormat,
+ const sal_uInt32 nNumberFormat, const bool bIsFixedLanguage)
+ : Meta(i_pFormat)
+ , m_nNumberFormat( nNumberFormat )
+ , m_bIsFixedLanguage( bIsFixedLanguage )
+{
+}
+
+void MetaField::GetPrefixAndSuffix(
+ OUString *const o_pPrefix, OUString *const o_pSuffix, OUString *const o_pShadowsColor)
+{
+ try
+ {
+ const uno::Reference<rdf::XMetadatable> xMetaField( MakeUnoObject() );
+ assert(dynamic_cast<SwXMetaField*>(xMetaField.get()) && "GetPrefixAndSuffix: no SwXMetaField?");
+ if (xMetaField.is())
+ {
+ SwTextNode * const pTextNode( GetTextNode() );
+ SwDocShell const * const pShell(pTextNode->GetDoc().GetDocShell());
+ const uno::Reference<frame::XModel> xModel(
+ pShell ? pShell->GetModel() : nullptr, uno::UNO_SET_THROW);
+ getPrefixAndSuffix(xModel, xMetaField, o_pPrefix, o_pSuffix, o_pShadowsColor);
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "sw", "");
+ }
+}
+
+sal_uInt32 MetaField::GetNumberFormat(const OUString& aContent) const
+{
+ //TODO: this probably lacks treatment for some special cases
+ sal_uInt32 nNumberFormat( m_nNumberFormat );
+ SwTextNode * const pTextNode( GetTextNode() );
+ if (pTextNode)
+ {
+ double number;
+ (void) pTextNode->GetDoc().IsNumberFormat( aContent, nNumberFormat, number );
+ }
+ return nNumberFormat;
+}
+
+void MetaField::SetNumberFormat(sal_uInt32 nNumberFormat)
+{
+ // effectively, the member is only a default:
+ // GetNumberFormat checks if the text actually conforms
+ m_nNumberFormat = nNumberFormat;
+}
+
+MetaFieldManager::MetaFieldManager()
+{
+}
+
+std::shared_ptr<MetaField>
+MetaFieldManager::makeMetaField(SwFormatMeta * const i_pFormat,
+ const sal_uInt32 nNumberFormat, const bool bIsFixedLanguage)
+{
+ const std::shared_ptr<MetaField> pMetaField(
+ new MetaField(i_pFormat, nNumberFormat, bIsFixedLanguage) );
+ m_MetaFields.push_back(pMetaField);
+ return pMetaField;
+}
+
+namespace {
+
+struct IsInUndo
+{
+ bool operator()(std::weak_ptr<MetaField> const & pMetaField) {
+ return pMetaField.lock()->IsInUndo();
+ }
+};
+
+struct MakeUnoObject
+{
+ uno::Reference<text::XTextField>
+ operator()(std::weak_ptr<MetaField> const & pMetaField) {
+ return uno::Reference<text::XTextField>(
+ pMetaField.lock()->MakeUnoObject(), uno::UNO_QUERY);
+ }
+};
+
+}
+
+std::vector< uno::Reference<text::XTextField> >
+MetaFieldManager::getMetaFields()
+{
+ // erase deleted fields
+ std::erase_if(m_MetaFields, [] (std::weak_ptr<MetaField> const& rField) { return rField.expired(); });
+ // filter out fields in UNDO
+ MetaFieldList_t filtered(m_MetaFields.size());
+ const MetaFieldList_t::iterator iter2(
+ std::remove_copy_if(m_MetaFields.begin(), m_MetaFields.end(),
+ filtered.begin(), IsInUndo()));
+ filtered.erase(iter2, filtered.end());
+ // create uno objects
+ std::vector< uno::Reference<text::XTextField> > ret(filtered.size());
+ std::transform(filtered.begin(), filtered.end(), ret.begin(),
+ MakeUnoObject());
+ return ret;
+}
+
+void MetaFieldManager::copyDocumentProperties(const SwDoc& rSource)
+{
+ const SwDocShell* pDocShell = rSource.GetDocShell();
+ if (!pDocShell)
+ return;
+
+ uno::Reference<document::XDocumentPropertiesSupplier> xDocumentPropertiesSupplier(pDocShell->GetModel(), uno::UNO_QUERY);
+ uno::Reference<util::XCloneable> xCloneable(xDocumentPropertiesSupplier->getDocumentProperties(), uno::UNO_QUERY);
+ m_xDocumentProperties.set(xCloneable->createClone(), uno::UNO_QUERY);
+}
+
+const uno::Reference<document::XDocumentProperties>& MetaFieldManager::getDocumentProperties() const
+{
+ return m_xDocumentProperties;
+}
+
+} // namespace sw
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/fntcache.cxx b/sw/source/core/txtnode/fntcache.cxx
new file mode 100644
index 0000000000..4e9f2a1c1d
--- /dev/null
+++ b/sw/source/core/txtnode/fntcache.cxx
@@ -0,0 +1,2274 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <i18nlangtag/mslangid.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/kernarray.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/lazydelete.hxx>
+#include <vcl/glyphitemcache.hxx>
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <breakit.hxx>
+#include <paintfrm.hxx>
+#include <viewsh.hxx>
+#include <viewopt.hxx>
+#include <fntcache.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <swfont.hxx>
+#include <wrong.hxx>
+#include <txtfrm.hxx>
+#include <pagefrm.hxx>
+#include <tgrditem.hxx>
+#include <scriptinfo.hxx>
+#include <editeng/brushitem.hxx>
+#include <accessibilityoptions.hxx>
+#include <svx/sdr/attribute/sdrallfillattributeshelper.hxx>
+#include <doc.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <vcl/glyphitem.hxx>
+#include <vcl/vcllayout.hxx>
+#include <docsh.hxx>
+#include <strings.hrc>
+#include <fntcap.hxx>
+#include <vcl/outdev/ScopedStates.hxx>
+#include <o3tl/hash_combine.hxx>
+#include <cstdint>
+#include <memory>
+#include "justify.hxx"
+
+using namespace ::com::sun::star;
+
+// global variables declared in fntcache.hxx
+// FontCache is created in txtinit.cxx TextInit_ and deleted in TextFinit
+SwFntCache *pFntCache = nullptr;
+// last Font set by ChgFntCache
+SwFntObj *pLastFont = nullptr;
+
+constexpr Color gWaveCol(COL_GRAY);
+
+tools::Long SwFntObj::s_nPixWidth;
+MapMode* SwFntObj::s_pPixMap = nullptr;
+static vcl::DeleteOnDeinit< VclPtr<OutputDevice> > s_pFntObjPixOut {};
+
+void SwFntCache::Flush( )
+{
+ if ( pLastFont )
+ {
+ pLastFont->Unlock();
+ pLastFont = nullptr;
+ }
+ SwCache::Flush( );
+}
+
+SwFntObj::SwFntObj(const SwSubFont &rFont, std::uintptr_t nFontCacheId, SwViewShell const *pSh)
+ : SwCacheObj(reinterpret_cast<void *>(nFontCacheId))
+ , m_aFont(rFont)
+ , m_pScrFont(nullptr)
+ , m_pPrtFont(&m_aFont)
+ , m_pPrinter(nullptr)
+ , m_nGuessedLeading(USHRT_MAX)
+ , m_nExtLeading(USHRT_MAX)
+ , m_nScrAscent(0)
+ , m_nPrtAscent(USHRT_MAX)
+ , m_nScrHeight(0)
+ , m_nPrtHeight(USHRT_MAX)
+ , m_nPropWidth(rFont.GetPropWidth())
+ , m_nScrHangingBaseline(0)
+ , m_nPrtHangingBaseline(0)
+{
+ m_nZoom = pSh ? pSh->GetViewOptions()->GetZoom() : USHRT_MAX;
+ m_bSymbol = RTL_TEXTENCODING_SYMBOL == m_aFont.GetCharSet();
+ m_bPaintBlank = ( LINESTYLE_NONE != m_aFont.GetUnderline()
+ || LINESTYLE_NONE != m_aFont.GetOverline()
+ || STRIKEOUT_NONE != m_aFont.GetStrikeout() )
+ && !m_aFont.IsWordLineMode();
+ m_aFont.SetLanguage(rFont.GetLanguage());
+}
+
+SwFntObj::~SwFntObj()
+{
+ if ( m_pScrFont != m_pPrtFont )
+ delete m_pScrFont;
+ if ( m_pPrtFont != &m_aFont )
+ delete m_pPrtFont;
+}
+
+void SwFntObj::CreatePrtFont( const OutputDevice& rPrt )
+{
+ if ( m_nPropWidth == 100 || m_pPrinter == &rPrt )
+ return;
+
+ if( m_pScrFont != m_pPrtFont )
+ delete m_pScrFont;
+ if( m_pPrtFont != &m_aFont )
+ delete m_pPrtFont;
+
+ const vcl::Font aOldFnt( rPrt.GetFont() );
+ const_cast<OutputDevice&>(rPrt).SetFont( m_aFont );
+ const FontMetric aWinMet( rPrt.GetFontMetric() );
+ const_cast<OutputDevice&>(rPrt).SetFont( aOldFnt );
+ auto nWidth = ( aWinMet.GetFontSize().Width() * m_nPropWidth ) / 100;
+
+ if( !nWidth )
+ ++nWidth;
+ m_pPrtFont = new vcl::Font( m_aFont );
+ m_pPrtFont->SetFontSize( Size( nWidth, m_aFont.GetFontSize().Height() ) );
+ m_pScrFont = nullptr;
+
+}
+
+/*
+ * returns whether we have to adjust the output font to resemble
+ * the formatting font
+ *
+ * _Not_ necessary if
+ *
+ * 1. RefDef == OutDev (text formatting, online layout...)
+ * 2. PDF export from online layout
+ * 3. Prospect/PagePreview printing
+ */
+static bool lcl_IsFontAdjustNecessary( const vcl::RenderContext& rOutDev,
+ const vcl::RenderContext& rRefDev )
+{
+ return &rRefDev != &rOutDev &&
+ OUTDEV_WINDOW != rRefDev.GetOutDevType() &&
+ ( OUTDEV_PRINTER != rRefDev.GetOutDevType() ||
+ OUTDEV_PRINTER != rOutDev.GetOutDevType() );
+}
+
+namespace {
+
+struct CalcLinePosData
+{
+ SwDrawTextInfo& rInf;
+ vcl::Font& rFont;
+ TextFrameIndex nCnt;
+ const bool bSwitchH2V;
+ const bool bSwitchH2VLRBT;
+ const bool bSwitchL2R;
+ tools::Long nHalfSpace;
+ KernArray& rKernArray;
+ const bool bBidiPor;
+
+ CalcLinePosData( SwDrawTextInfo& _rInf, vcl::Font& _rFont,
+ TextFrameIndex const _nCnt, const bool _bSwitchH2V, const bool _bSwitchH2VLRBT, const bool _bSwitchL2R,
+ tools::Long _nHalfSpace, KernArray& _rKernArray, const bool _bBidiPor) :
+ rInf( _rInf ),
+ rFont( _rFont ),
+ nCnt( _nCnt ),
+ bSwitchH2V( _bSwitchH2V ),
+ bSwitchH2VLRBT( _bSwitchH2VLRBT ),
+ bSwitchL2R( _bSwitchL2R ),
+ nHalfSpace( _nHalfSpace ),
+ rKernArray( _rKernArray ),
+ bBidiPor( _bBidiPor )
+ {
+ }
+};
+
+}
+
+// Computes the start and end position of an underline. This function is called
+// from the DrawText-method (for underlining misspelled words or smarttag terms).
+static void lcl_calcLinePos( const CalcLinePosData &rData,
+ Point &rStart, Point &rEnd, TextFrameIndex const nStart, TextFrameIndex const nWrLen)
+{
+ tools::Long nBlank = 0;
+ const TextFrameIndex nEnd = nStart + nWrLen;
+ const tools::Long nTmpSpaceAdd = rData.rInf.GetSpace() / SPACING_PRECISION_FACTOR;
+
+ if ( nEnd < rData.nCnt
+ && CH_BLANK == rData.rInf.GetText()[sal_Int32(rData.rInf.GetIdx() + nEnd)] )
+ {
+ if (nEnd + TextFrameIndex(1) == rData.nCnt)
+ nBlank -= nTmpSpaceAdd;
+ else
+ nBlank -= rData.nHalfSpace;
+ }
+
+ // determine start, end and length of wave line
+ sal_Int32 nKernStart = nStart ? rData.rKernArray[sal_Int32(nStart) - 1] : 0;
+ sal_Int32 nKernEnd = rData.rKernArray[sal_Int32(nEnd) - 1];
+
+ const Degree10 nDir = rData.bBidiPor ? 1800_deg10
+ : UnMapDirection(rData.rFont.GetOrientation(),
+ rData.bSwitchH2V, rData.bSwitchH2VLRBT);
+
+ switch ( nDir.get() )
+ {
+ case 0 :
+ rStart.AdjustX(nKernStart );
+ rEnd.setX( nBlank + rData.rInf.GetPos().X() + nKernEnd );
+ rEnd.setY( rData.rInf.GetPos().Y() );
+ break;
+ case 900 :
+ rStart.AdjustY( -nKernStart );
+ rEnd.setX( rData.rInf.GetPos().X() );
+ rEnd.setY( nBlank + rData.rInf.GetPos().Y() - nKernEnd );
+ break;
+ case 1800 :
+ rStart.AdjustX( -nKernStart );
+ rEnd.setX( rData.rInf.GetPos().X() - nKernEnd - nBlank );
+ rEnd.setY( rData.rInf.GetPos().Y() );
+ break;
+ case 2700 :
+ rStart.AdjustY(nKernStart );
+ rEnd.setX( rData.rInf.GetPos().X() );
+ rEnd.setY( nBlank + rData.rInf.GetPos().Y() + nKernEnd );
+ break;
+ }
+
+ // tdf#151968
+ // if start < end, OutputDevice::DrawWaveLine() will think it is a rotated
+ // line, so we swap nStart and nEnd to avoid this.
+ if ( rData.bBidiPor )
+ std::swap(rStart, rEnd);
+
+ if ( rData.bSwitchL2R )
+ {
+ rData.rInf.GetFrame()->SwitchLTRtoRTL( rStart );
+ rData.rInf.GetFrame()->SwitchLTRtoRTL( rEnd );
+
+ // tdf#151968
+ // We need to do this here as well for LTR text in a RTL paragraph.
+ std::swap(rStart, rEnd);
+ }
+
+ if ( rData.bSwitchH2V )
+ {
+ rData.rInf.GetFrame()->SwitchHorizontalToVertical( rStart );
+ rData.rInf.GetFrame()->SwitchHorizontalToVertical( rEnd );
+ }
+}
+
+// Returns the Ascent of the Font on the given output device;
+// it may be necessary to create the screen font first.
+sal_uInt16 SwFntObj::GetFontAscent( const SwViewShell *pSh, const OutputDevice& rOut )
+{
+ sal_uInt16 nRet = 0;
+ const OutputDevice& rRefDev = pSh ? pSh->GetRefDev() : rOut;
+
+ if ( pSh && lcl_IsFontAdjustNecessary( rOut, rRefDev ) )
+ {
+ CreateScrFont( *pSh, rOut );
+ OSL_ENSURE( USHRT_MAX != m_nScrAscent, "nScrAscent is going berzerk" );
+ nRet = m_nScrAscent;
+ }
+ else
+ {
+ if (m_nPrtAscent == USHRT_MAX) // printer ascent unknown?
+ {
+ CreatePrtFont( rOut );
+ const vcl::Font aOldFnt( rRefDev.GetFont() );
+ const_cast<OutputDevice&>(rRefDev).SetFont( *m_pPrtFont );
+ const FontMetric aOutMet( rRefDev.GetFontMetric() );
+ m_nPrtAscent = o3tl::narrowing<sal_uInt16>(aOutMet.GetAscent());
+ m_nPrtHangingBaseline = o3tl::narrowing<sal_uInt16>(aOutMet.GetHangingBaseline());
+ const_cast<OutputDevice&>(rRefDev).SetFont( aOldFnt );
+ }
+
+ nRet = m_nPrtAscent;
+ }
+
+#if !defined(MACOSX) // #i89844# extleading is below the line for Mac
+ // TODO: move extleading below the line for all platforms too
+ nRet += GetFontLeading( pSh, rRefDev );
+#endif
+
+ OSL_ENSURE( USHRT_MAX != nRet, "GetFontAscent returned USHRT_MAX" );
+ return nRet;
+}
+
+// Returns the height of the Font on the given output device;
+// it may be necessary to create the screen font first.
+sal_uInt16 SwFntObj::GetFontHeight( const SwViewShell* pSh, const OutputDevice& rOut )
+{
+ sal_uInt16 nRet = 0;
+ const OutputDevice& rRefDev = pSh ? pSh->GetRefDev() : rOut;
+
+ if ( pSh && lcl_IsFontAdjustNecessary( rOut, rRefDev ) )
+ {
+ CreateScrFont( *pSh, rOut );
+ OSL_ENSURE( USHRT_MAX != m_nScrHeight, "nScrHeight is going berzerk" );
+ nRet = m_nScrHeight + GetFontLeading( pSh, rRefDev );
+ }
+ else
+ {
+ if (m_nPrtHeight == USHRT_MAX) // printer height unknown?
+ {
+ CreatePrtFont( rOut );
+ const vcl::Font aOldFnt( rRefDev.GetFont() );
+ const_cast<OutputDevice&>(rRefDev).SetFont( *m_pPrtFont );
+ m_nPrtHeight = o3tl::narrowing<sal_uInt16>(rRefDev.GetTextHeight());
+
+#if OSL_DEBUG_LEVEL > 0
+ // Check if vcl did not change the meaning of GetTextHeight
+ const FontMetric aOutMet( rRefDev.GetFontMetric() );
+ tools::Long nTmpPrtHeight = o3tl::narrowing<sal_uInt16>(aOutMet.GetAscent()) + aOutMet.GetDescent();
+ // #i106098#: do not compare with == here due to rounding error
+ OSL_ENSURE( std::abs(nTmpPrtHeight - m_nPrtHeight) < 3,
+ "GetTextHeight != Ascent + Descent" );
+#endif
+
+ const_cast<OutputDevice&>(rRefDev).SetFont( aOldFnt );
+ }
+
+ nRet = m_nPrtHeight + GetFontLeading( pSh, rRefDev );
+ }
+
+ OSL_ENSURE( USHRT_MAX != nRet, "GetFontHeight returned USHRT_MAX" );
+ return nRet;
+}
+
+sal_uInt16 SwFntObj::GetFontLeading( const SwViewShell *pSh, const OutputDevice& rOut )
+{
+ sal_uInt16 nRet = 0;
+
+ if ( pSh )
+ {
+ if ( USHRT_MAX == m_nGuessedLeading || USHRT_MAX == m_nExtLeading )
+ {
+ SolarMutexGuard aGuard;
+
+ const vcl::Font aOldFnt( rOut.GetFont() );
+ const_cast<OutputDevice&>(rOut).SetFont( *m_pPrtFont );
+ const FontMetric aMet( rOut.GetFontMetric() );
+ const_cast<OutputDevice&>(rOut).SetFont( aOldFnt );
+ m_bSymbol = RTL_TEXTENCODING_SYMBOL == aMet.GetCharSet();
+ GuessLeading( *pSh, aMet );
+ m_nExtLeading = o3tl::narrowing<sal_uInt16>(aMet.GetExternalLeading());
+ /* HACK: FIXME There is something wrong with Writer's bullet rendering, causing lines
+ with bullets to be higher than they should be. I think this is because
+ Writer uses font's external leading incorrect, as the vertical distance
+ added to every line instead of only a distance between multiple lines,
+ which means a single bullet has external leading added even though it
+ shouldn't, but frankly this is just an educated guess rather than understanding
+ Writer's layout (heh).
+ Symbol font in some documents is 'StarSymbol; Arial Unicode MS', and Windows
+ machines often do not have StarSymbol, falling back to Arial Unicode MS, which
+ has unusually high external leading. So just reset external leading for fonts
+ which are used to bullets, as those should not be used on multiple lines anyway,
+ so in correct rendering external leading should be irrelevant anyway.
+ Interestingly enough, bSymbol is false for 'StarSymbol; Arial Unicode MS', so
+ also check explicitly.
+ */
+ if( m_bSymbol || IsOpenSymbol( m_pPrtFont->GetFamilyName()))
+ m_nExtLeading = 0;
+ }
+
+ const IDocumentSettingAccess& rIDSA = pSh->getIDocumentSettingAccess();
+ const bool bBrowse = ( pSh->GetWin() &&
+ pSh->GetViewOptions()->getBrowseMode() &&
+ !pSh->GetViewOptions()->IsPrtFormat() );
+
+ if ( !bBrowse && rIDSA.get(DocumentSettingId::ADD_EXT_LEADING) )
+ nRet = m_nExtLeading;
+ else
+ nRet = m_nGuessedLeading;
+ }
+
+ OSL_ENSURE( USHRT_MAX != nRet, "GetFontLeading returned USHRT_MAX" );
+ return nRet;
+}
+
+sal_uInt16 SwFntObj::GetFontHangingBaseline( const SwViewShell* pSh, const OutputDevice& rOut )
+{
+ sal_uInt16 nRet = 0;
+ const OutputDevice& rRefDev = pSh ? pSh->GetRefDev() : rOut;
+
+ GetFontAscent(pSh, rOut);
+
+ if ( pSh && lcl_IsFontAdjustNecessary( rOut, rRefDev ) )
+ nRet = m_nScrHangingBaseline;
+ else
+ nRet = m_nPrtHangingBaseline;
+
+ return nRet;
+}
+
+// pOut is the output device, not the reference device
+void SwFntObj::CreateScrFont( const SwViewShell& rSh, const OutputDevice& rOut )
+{
+ if ( m_pScrFont )
+ return;
+
+ // any changes to the output device are reset at the end of the function
+ OutputDevice* pOut = const_cast<OutputDevice*>(&rOut);
+
+ // Save old font
+ vcl::Font aOldOutFont( pOut->GetFont() );
+
+ m_nScrHeight = USHRT_MAX;
+
+ // Condition for output font / refdev font adjustment
+ OutputDevice* pPrt = &rSh.GetRefDev();
+
+ if( !rSh.GetWin() ||
+ !rSh.GetViewOptions()->getBrowseMode() ||
+ rSh.GetViewOptions()->IsPrtFormat() )
+ {
+ // After CreatePrtFont m_pPrtFont is the font which is actually used
+ // by the reference device
+ CreatePrtFont( *pPrt );
+ m_pPrinter = pPrt;
+
+ // save old reference device font
+ vcl::Font aOldPrtFnt( pPrt->GetFont() );
+
+ // set the font used at the reference device at the reference device
+ // and the output device
+ pPrt->SetFont( *m_pPrtFont );
+ pOut->SetFont( *m_pPrtFont );
+
+ // This should be the default for pScrFont.
+ m_pScrFont = m_pPrtFont;
+
+ FontMetric aMet = pPrt->GetFontMetric( );
+ // Don't lose "faked" properties of the logical font that don't truly
+ // exist in the physical font metrics which vcl which fake up for us
+ aMet.SetWeight(m_pScrFont->GetWeight());
+ aMet.SetItalic(m_pScrFont->GetItalic());
+
+ m_bSymbol = RTL_TEXTENCODING_SYMBOL == aMet.GetCharSet();
+
+ if ( USHRT_MAX == m_nGuessedLeading )
+ GuessLeading( rSh, aMet );
+
+ if ( USHRT_MAX == m_nExtLeading )
+ m_nExtLeading = o3tl::narrowing<sal_uInt16>(aMet.GetExternalLeading());
+
+ // reset the original reference device font
+ pPrt->SetFont( aOldPrtFnt );
+ }
+ else
+ {
+ m_bSymbol = RTL_TEXTENCODING_SYMBOL == m_aFont.GetCharSet();
+ if ( m_nGuessedLeading == USHRT_MAX )
+ m_nGuessedLeading = 0;
+
+ // no external leading in browse mode
+ if ( m_nExtLeading == USHRT_MAX )
+ m_nExtLeading = 0;
+
+ m_pScrFont = m_pPrtFont;
+ }
+
+ m_nScrAscent = o3tl::narrowing<sal_uInt16>(pOut->GetFontMetric().GetAscent());
+ m_nScrHangingBaseline = o3tl::narrowing<sal_uInt16>(pOut->GetFontMetric().GetHangingBaseline());
+ if ( USHRT_MAX == m_nScrHeight )
+ m_nScrHeight = o3tl::narrowing<sal_uInt16>(pOut->GetTextHeight());
+
+ // reset original output device font
+ pOut->SetFont( aOldOutFont );
+}
+
+void SwFntObj::GuessLeading( const SwViewShell&
+#if defined(_WIN32)
+ rSh
+#endif
+ , const FontMetric& rMet )
+{
+ // If leading >= 5, this seems to be enough leading.
+ // Nothing has to be done.
+ if ( rMet.GetInternalLeading() >= 5 )
+ {
+ m_nGuessedLeading = 0;
+ return;
+ }
+
+#if defined(_WIN32)
+ OutputDevice *pWin = rSh.GetWin() ?
+ rSh.GetWin()->GetOutDev() :
+ Application::GetDefaultDevice();
+ if ( pWin )
+ {
+ MapMode aTmpMap( MapUnit::MapTwip );
+ MapMode aOldMap = pWin->GetMapMode( );
+ pWin->SetMapMode( aTmpMap );
+ const vcl::Font aOldFnt( pWin->GetFont() );
+ pWin->SetFont( *m_pPrtFont );
+ const FontMetric aWinMet( pWin->GetFontMetric() );
+ const sal_uInt16 nWinHeight = sal_uInt16( aWinMet.GetFontSize().Height() );
+ if( m_pPrtFont->GetFamilyName().indexOf( aWinMet.GetFamilyName() ) != -1 )
+ {
+ // If the Leading on the Window is also 0, then it has to stay
+ // that way (see also StarMath).
+ sal_Int32 nTmpLeading = aWinMet.GetInternalLeading();
+ if( nTmpLeading <= 0 )
+ {
+ pWin->SetFont( rMet );
+ nTmpLeading = pWin->GetFontMetric().GetInternalLeading();
+ if( nTmpLeading < 0 )
+ m_nGuessedLeading = 0;
+ else
+ m_nGuessedLeading = sal_uInt16(nTmpLeading);
+ }
+ else
+ {
+ m_nGuessedLeading = sal_uInt16(nTmpLeading);
+ // Manta-Hack #50153#:
+ // Wer beim Leading luegt, luegt moeglicherweise auch beim
+ // Ascent/Descent, deshalb wird hier ggf. der Font ein wenig
+ // tiefergelegt, ohne dabei seine Hoehe zu aendern.
+ // (above original comment preserved for cultural reasons)
+ // Those who lie about their Leading, may lie about their
+ // Ascent/Descent as well, hence the Font will be lowered a
+ // little without changing its height.
+ sal_Int32 nDiff = std::min( rMet.GetDescent() - aWinMet.GetDescent(),
+ aWinMet.GetAscent() - rMet.GetAscent() - nTmpLeading );
+ if( nDiff > 0 )
+ {
+ OSL_ENSURE( m_nPrtAscent < USHRT_MAX, "GuessLeading: PrtAscent-Fault" );
+ if ( m_nPrtAscent < USHRT_MAX )
+ m_nPrtAscent = m_nPrtAscent + o3tl::narrowing<sal_uInt16>(( 2 * nDiff ) / 5);
+ }
+ }
+ }
+ else
+ {
+ // If all else fails, take 15% of the height, as empirically
+ // determined by CL
+ m_nGuessedLeading = (nWinHeight * 15) / 100;
+ }
+ pWin->SetFont( aOldFnt );
+ pWin->SetMapMode( aOldMap );
+ }
+ else
+ m_nGuessedLeading = 0;
+#else
+ m_nGuessedLeading = 0;
+#endif
+}
+
+// Set the font at the given output device; for screens it may be
+// necessary to do some adjustment first.
+void SwFntObj::SetDevFont( const SwViewShell *pSh, OutputDevice& rOut )
+{
+ const OutputDevice& rRefDev = pSh ? pSh->GetRefDev() : rOut;
+
+ if ( pSh && lcl_IsFontAdjustNecessary( rOut, rRefDev ) )
+ {
+ CreateScrFont( *pSh, rOut );
+ if( !GetScrFont()->IsSameInstance( rOut.GetFont() ) )
+ rOut.SetFont( *m_pScrFont );
+ if( m_pPrinter && ( !m_pPrtFont->IsSameInstance( m_pPrinter->GetFont() ) ) )
+ m_pPrinter->SetFont( *m_pPrtFont );
+ }
+ else
+ {
+ CreatePrtFont( rOut );
+ if( !m_pPrtFont->IsSameInstance( rOut.GetFont() ) )
+ rOut.SetFont( *m_pPrtFont );
+ }
+
+ // Here, we actually do not need the leading values, but by calling
+ // GetFontLeading() we assure that the values are calculated for later use.
+ GetFontLeading( pSh, rRefDev );
+}
+
+#define WRONG_SHOW_MIN 5
+
+/*
+ * Output text:
+ * on screen => DrawTextArray
+ * on printer, !Kerning => DrawText
+ * on printer + Kerning => DrawStretchText
+ */
+static bool lcl_IsMonoSpaceFont( const vcl::RenderContext& rOut )
+{
+ const tools::Long nWidth1 = rOut.GetTextWidth( OUString( u'\x3008' ) );
+ const tools::Long nWidth2 = rOut.GetTextWidth( OUString( u'\x307C' ) );
+ return nWidth1 == nWidth2;
+}
+
+static bool lcl_IsFullstopCentered( const vcl::RenderContext& rOut )
+{
+ const FontMetric aMetric( rOut.GetFontMetric() );
+ return aMetric.IsFullstopCentered() ;
+}
+
+/* This helper structure (SwForbidden) contains the already marked parts of the string
+ to avoid double lines (e.g grammar + spell check error) */
+
+typedef std::vector<std::pair<TextFrameIndex, TextFrameIndex>> SwForbidden;
+
+static void lcl_DrawLineForWrongListData(
+ SwForbidden &rForbidden,
+ const SwDrawTextInfo &rInf,
+ sw::WrongListIterator *pWList,
+ const CalcLinePosData &rCalcLinePosData,
+ const Size &rPrtFontSize )
+{
+ if (!pWList) return;
+
+ TextFrameIndex nStart = rInf.GetIdx();
+ TextFrameIndex nWrLen = rInf.GetLen();
+
+ // check if respective data is available in the current text range
+ if (!pWList->Check( nStart, nWrLen ))
+ {
+ return;
+ }
+
+ tools::Long nHght = rInf.GetOut().LogicToPixel( rPrtFontSize ).Height();
+
+ // Draw wavy lines for spell and grammar errors only if font is large enough.
+ // Lines for smart tags will always be drawn.
+ if (pWList != rInf.GetSmartTags() && WRONG_SHOW_MIN >= nHght)
+ {
+ return;
+ }
+
+ SwForbidden::iterator pIter = rForbidden.begin();
+ if (rInf.GetOut().GetConnectMetaFile())
+ rInf.GetOut().Push();
+
+ const Color aCol( rInf.GetOut().GetLineColor() );
+
+ // iterate over all ranges stored in the respective SwWrongList
+ do
+ {
+ nStart -= rInf.GetIdx();
+
+ const TextFrameIndex nEnd = nStart + nWrLen;
+ TextFrameIndex nNext = nStart;
+ while( nNext < nEnd )
+ {
+ while( pIter != rForbidden.end() && pIter->second <= nNext )
+ ++pIter;
+
+ const TextFrameIndex nNextStart = nNext;
+ TextFrameIndex nNextEnd = nEnd;
+
+ if( pIter == rForbidden.end() || nNextEnd <= pIter->first )
+ {
+ // No overlapping mark up found
+ rForbidden.insert(pIter, std::make_pair(nNextStart, nNextEnd));
+ pIter = rForbidden.begin();
+ nNext = nEnd;
+ }
+ else
+ {
+ nNext = pIter->second;
+ if( nNextStart < pIter->first )
+ {
+ nNextEnd = pIter->first;
+ pIter->first = nNextStart;
+ }
+ else
+ continue;
+ }
+ // determine line pos
+ Point aStart( rInf.GetPos() );
+ Point aEnd;
+ lcl_calcLinePos( rCalcLinePosData, aStart, aEnd, nNextStart, nNextEnd - nNextStart );
+
+ SwWrongArea const*const wrongArea = pWList->GetWrongElement(nNextStart + rInf.GetIdx());
+ if (wrongArea != nullptr)
+ {
+ const SwViewShell* pShell = rInf.GetShell();
+ sal_uInt16 nZoom = pShell ? round(pShell->GetViewOptions()->GetZoom()/100) : 1;
+ if (WRONGAREA_WAVE == wrongArea->mLineType)
+ {
+ vcl::ScopedAntialiasing a(rInf.GetOut(), true);
+ rInf.GetOut().SetLineColor( wrongArea->mColor );
+ rInf.GetOut().DrawWaveLine( aStart, aEnd, 1 + nZoom, 3 + nZoom );
+ }
+ else if (WRONGAREA_BOLDWAVE == wrongArea->mLineType)
+ {
+ vcl::ScopedAntialiasing a(rInf.GetOut(), true);
+ rInf.GetOut().SetLineColor( wrongArea->mColor );
+ rInf.GetOut().DrawWaveLine( aStart, aEnd, 2 + nZoom, 4 + nZoom );
+ }
+ else if (WRONGAREA_BOLD == wrongArea->mLineType)
+ {
+ rInf.GetOut().SetLineColor( wrongArea->mColor );
+
+ aStart.AdjustY(30 );
+ aEnd.AdjustY(30 );
+
+ LineInfo aLineInfo( LineStyle::Solid, 26 );
+
+ rInf.GetOut().DrawLine( aStart, aEnd, aLineInfo );
+ }
+ else if (WRONGAREA_DASHED == wrongArea->mLineType)
+ {
+ rInf.GetOut().SetLineColor( wrongArea->mColor );
+
+ aStart.AdjustY(30 );
+ aEnd.AdjustY(30 );
+
+ LineInfo aLineInfo( LineStyle::Dash );
+ aLineInfo.SetDistance( 40 );
+ aLineInfo.SetDashLen( 1 );
+ aLineInfo.SetDashCount(1);
+
+ rInf.GetOut().DrawLine( aStart, aEnd, aLineInfo );
+ }
+ }
+ }
+
+ nStart = nEnd + rInf.GetIdx();
+ nWrLen = rInf.GetIdx() + rInf.GetLen() - nStart;
+ }
+ while (nWrLen && pWList->Check( nStart, nWrLen ));
+
+ rInf.GetOut().SetLineColor( aCol );
+
+ if (rInf.GetOut().GetConnectMetaFile())
+ rInf.GetOut().Pop();
+}
+
+static void GetTextArray(const OutputDevice& rDevice, const OUString& rStr, KernArray& rDXAry,
+ sal_Int32 nIndex, sal_Int32 nLen, bool bCaret = false,
+ const vcl::text::TextLayoutCache* layoutCache = nullptr)
+{
+ const SalLayoutGlyphs* pLayoutCache = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(&rDevice, rStr, nIndex, nLen,
+ 0, layoutCache);
+ rDevice.GetTextArray(rStr, &rDXAry, nIndex, nLen, bCaret, layoutCache, pLayoutCache);
+}
+
+static void GetTextArray(const OutputDevice& rOutputDevice, const SwDrawTextInfo& rInf, KernArray& rDXAry,
+ bool bCaret = false)
+{
+ return GetTextArray(rOutputDevice, rInf.GetText(), rDXAry, rInf.GetIdx().get(), rInf.GetLen().get(),
+ bCaret, rInf.GetVclCache());
+}
+
+static void GetTextArray(const OutputDevice& rOutputDevice, const SwDrawTextInfo& rInf, KernArray& rDXAry,
+ sal_Int32 nLen, bool bCaret = false)
+{
+ // Substring is fine.
+ assert( nLen <= rInf.GetLen().get());
+ return GetTextArray(rOutputDevice, rInf.GetText(), rDXAry, rInf.GetIdx().get(), nLen, bCaret, rInf.GetVclCache());
+}
+
+void SwFntObj::DrawText( SwDrawTextInfo &rInf )
+{
+ OSL_ENSURE( rInf.GetShell(), "SwFntObj::DrawText without shell" );
+
+ OutputDevice& rRefDev = rInf.GetShell()->GetRefDev();
+ vcl::Window* pWin = rInf.GetShell()->GetWin();
+
+ // true if pOut is the printer and the printer has been used for formatting
+ const bool bPrt = OUTDEV_PRINTER == rInf.GetOut().GetOutDevType() &&
+ OUTDEV_PRINTER == rRefDev.GetOutDevType();
+ const bool bBrowse = ( pWin &&
+ rInf.GetShell()->GetViewOptions()->getBrowseMode() &&
+ !rInf.GetShell()->GetViewOptions()->IsPrtFormat() &&
+ !rInf.GetBullet() &&
+ ( rInf.GetSpace() || !rInf.GetKern() ) &&
+ !rInf.GetWrong() &&
+ !rInf.GetGrammarCheck() &&
+ !rInf.GetSmartTags() &&
+ !rInf.GetGreyWave() );
+
+ // bDirectPrint indicates that we can enter the branch which calls
+ // the DrawText functions instead of calling the DrawTextArray functions
+ const bool bDirectPrint = bPrt || bBrowse;
+
+ // Condition for output font / refdev font adjustment
+ const bool bUseScrFont =
+ lcl_IsFontAdjustNecessary( rInf.GetOut(), rRefDev );
+
+ vcl::Font* pTmpFont = bUseScrFont ? m_pScrFont : m_pPrtFont;
+
+ // bDirectPrint and bUseScrFont should have these values:
+
+ // Outdev / RefDef | Printer | VirtPrinter | Window
+
+ // Printer | 1 - 0 | 0 - 1 | -
+
+ // VirtPrinter/PDF | 0 - 1 | 0 - 1 | -
+
+ // Window/VirtWindow| 0 - 1 | 0 - 1 | 1 - 0
+
+ // Exception: During painting of a Writer OLE object, we do not have
+ // a window. Therefore bUseSrcFont is always 0 in this case.
+
+#if OSL_DEBUG_LEVEL > 0
+
+ const bool bNoAdjust = bPrt ||
+ ( pWin &&
+ rInf.GetShell()->GetViewOptions()->getBrowseMode() &&
+ !rInf.GetShell()->GetViewOptions()->IsPrtFormat() );
+
+ if ( OUTDEV_PRINTER == rInf.GetOut().GetOutDevType() )
+ {
+ // Printer output
+ if ( OUTDEV_PRINTER == rRefDev.GetOutDevType() )
+ {
+ OSL_ENSURE( bNoAdjust && !bUseScrFont, "Outdev Check failed" );
+ }
+ else if ( rRefDev.IsVirtual() )
+ {
+ OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" );
+ }
+ else
+ {
+ OSL_FAIL( "Outdev Check failed" );
+ }
+ }
+ else if ( rInf.GetOut().IsVirtual() && ! pWin )
+ {
+ // PDF export
+ if ( OUTDEV_PRINTER == rRefDev.GetOutDevType() )
+ {
+ OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" );
+ }
+ else if ( rRefDev.IsVirtual() )
+ {
+ OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" );
+ }
+ else
+ {
+ OSL_FAIL( "Outdev Check failed" );
+ }
+ }
+ else if ( OUTDEV_WINDOW == rInf.GetOut().GetOutDevType() ||
+ ( rInf.GetOut().IsVirtual() && pWin ) )
+ {
+ // Window or virtual window
+ if ( OUTDEV_PRINTER == rRefDev.GetOutDevType() )
+ {
+ OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" );
+ }
+ else if ( rRefDev.IsVirtual() )
+ {
+ OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" );
+ }
+ else if ( OUTDEV_WINDOW == rRefDev.GetOutDevType() )
+ {
+ OSL_ENSURE( bNoAdjust && !bUseScrFont, "Outdev Check failed" );
+ }
+ else
+ {
+ OSL_FAIL( "Outdev Check failed" );
+ }
+ }
+ else
+ {
+ OSL_FAIL( "Outdev Check failed" );
+ }
+
+#endif
+
+ // robust: better use the printer font instead of using no font at all
+ OSL_ENSURE( pTmpFont, "No screen or printer font?" );
+ if ( ! pTmpFont )
+ pTmpFont = m_pPrtFont;
+
+ // HACK: LINESTYLE_WAVE must not be abused any more, hence the grey wave
+ // line of the ExtendedAttributeSets will appear in the font color first
+
+ const bool bSwitchH2V = rInf.GetFrame() && rInf.GetFrame()->IsVertical();
+ const bool bSwitchH2VLRBT = rInf.GetFrame() && rInf.GetFrame()->IsVertLRBT();
+ const bool bSwitchL2R = rInf.GetFrame() && rInf.GetFrame()->IsRightToLeft() &&
+ ! rInf.IsIgnoreFrameRTL();
+ const vcl::text::ComplexTextLayoutFlags nMode = rInf.GetOut().GetLayoutMode();
+ const bool bBidiPor = ( bSwitchL2R !=
+ ( vcl::text::ComplexTextLayoutFlags::Default != ( vcl::text::ComplexTextLayoutFlags::BiDiRtl & nMode ) ) );
+
+ // be sure to have the correct layout mode at the printer
+ if ( m_pPrinter )
+ {
+ m_pPrinter->SetLayoutMode( rInf.GetOut().GetLayoutMode() );
+ m_pPrinter->SetDigitLanguage( rInf.GetOut().GetDigitLanguage() );
+ }
+
+ Point aTextOriginPos( rInf.GetPos() );
+ if( !bPrt )
+ {
+ if( rInf.GetpOut() != *s_pFntObjPixOut.get() || rInf.GetOut().GetMapMode() != *s_pPixMap )
+ {
+ *s_pPixMap = rInf.GetOut().GetMapMode();
+ (*s_pFntObjPixOut.get()) = rInf.GetpOut();
+ Size aTmp( 1, 1 );
+ s_nPixWidth = rInf.GetOut().PixelToLogic( aTmp ).Width();
+ }
+
+ aTextOriginPos.AdjustX(rInf.GetFrame()->IsRightToLeft() ? 0 : s_nPixWidth );
+ }
+
+ Color aOldColor( pTmpFont->GetColor() );
+ bool bChgColor = rInf.ApplyAutoColor( pTmpFont );
+ if( !pTmpFont->IsSameInstance( rInf.GetOut().GetFont() ) )
+ rInf.GetOut().SetFont( *pTmpFont );
+ if ( bChgColor )
+ pTmpFont->SetColor( aOldColor );
+
+ if (TextFrameIndex(COMPLETE_STRING) == rInf.GetLen())
+ rInf.SetLen( TextFrameIndex(rInf.GetText().getLength()) );
+
+ // ASIAN LINE AND CHARACTER GRID MODE START
+
+ if ( rInf.GetFrame() && rInf.SnapToGrid() && rInf.GetFont() &&
+ SwFontScript::CJK == rInf.GetFont()->GetActual() )
+ {
+ SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame()));
+
+ // ASIAN LINE AND CHARACTER GRID MODE
+ if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() )
+ {
+ //for textgrid refactor
+ const SwDoc* pDoc = rInf.GetShell()->GetDoc();
+ const tools::Long nGridWidth = GetGridWidth(*pGrid, *pDoc);
+ tools::Long nSpaceAdd = rInf.GetSpace() / SPACING_PRECISION_FACTOR;
+
+ // kerning array - gives the absolute position of end of each character
+ KernArray aKernArray;
+
+ if ( m_pPrinter )
+ GetTextArray(*m_pPrinter, rInf, aKernArray);
+ else
+ GetTextArray(rInf.GetOut(), rInf, aKernArray);
+
+ tools::Long nDelta = 0;
+
+ if (pGrid->IsSnapToChars())
+ {
+ nDelta = sw::Justify::SnapToGrid(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()),
+ sal_Int32(rInf.GetLen()), nGridWidth, false);
+ }
+ else
+ {
+ sw::Justify::SnapToGridEdge(aKernArray, sal_Int32(rInf.GetLen()), nGridWidth,
+ nSpaceAdd, rInf.GetKern());
+ }
+
+ if (nDelta)
+ aTextOriginPos.AdjustX(nDelta);
+
+ if ( bSwitchH2V )
+ rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos );
+
+ rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
+ aKernArray, {}, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
+
+ return;
+ }
+ }
+
+ // DIRECT PAINTING WITHOUT SCREEN ADJUSTMENT
+
+ if ( bDirectPrint )
+ {
+ const Fraction aTmp( 1, 1 );
+ bool bStretch = rInf.GetWidth() && (rInf.GetLen() > TextFrameIndex(1)) && bPrt
+ && ( aTmp != rInf.GetOut().GetMapMode().GetScaleX() );
+
+ if ( bSwitchL2R )
+ rInf.GetFrame()->SwitchLTRtoRTL( aTextOriginPos );
+
+ if ( bSwitchH2V )
+ rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos );
+
+ // In the good old days we used to have a simple DrawText if the
+ // output device is the printer. Now we need a DrawTextArray if
+ // 1. KanaCompression is enabled
+ // 2. Justified alignment
+ // Simple kerning is handled by DrawStretchText
+ if( rInf.GetSpace() || rInf.GetKanaComp() )
+ {
+ KernArray aKernArray;
+ GetTextArray(rInf.GetOut(), rInf, aKernArray);
+ std::vector<sal_Bool> aKashidaArray;
+
+ if( bStretch )
+ {
+ sal_Int32 nZwi = sal_Int32(rInf.GetLen()) - 1;
+ tools::Long nDiff = rInf.GetWidth() - aKernArray[ nZwi ]
+ - sal_Int32(rInf.GetLen()) * rInf.GetKern();
+ tools::Long nRest = nDiff % nZwi;
+ tools::Long nAdd;
+ if( nRest < 0 )
+ {
+ nAdd = -1;
+ nRest += nZwi;
+ }
+ else
+ {
+ nAdd = +1;
+ nRest = nZwi - nRest;
+ }
+ nDiff /= nZwi;
+ tools::Long nSum = nDiff;
+ for( sal_Int32 i = 0; i < nZwi; )
+ {
+ aKernArray.adjust(i, nSum);
+ if( ++i == nRest )
+ nDiff += nAdd;
+ nSum += nDiff;
+ }
+ }
+
+ // Modify Array for special justifications
+
+ tools::Long nSpaceAdd = rInf.GetSpace() / SPACING_PRECISION_FACTOR;
+ bool bSpecialJust = false;
+
+ if ( rInf.GetFont() && rInf.GetLen() )
+ {
+ const SwScriptInfo* pSI = rInf.GetScriptInfo();
+ const SwFontScript nActual = rInf.GetFont()->GetActual();
+
+ // Kana Compression
+ if ( SwFontScript::CJK == nActual && rInf.GetKanaComp() &&
+ pSI && pSI->CountCompChg() &&
+ lcl_IsMonoSpaceFont( rInf.GetOut() ) )
+ {
+ pSI->Compress( aKernArray, rInf.GetIdx(), rInf.GetLen(),
+ rInf.GetKanaComp(),
+ o3tl::narrowing<sal_uInt16>(m_aFont.GetFontSize().Height()), lcl_IsFullstopCentered( rInf.GetOut() ), &aTextOriginPos );
+ bSpecialJust = true;
+ }
+
+ // Asian Justification
+ if ( SwFontScript::CJK == nActual && nSpaceAdd )
+ {
+ LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CJK );
+
+ if (!MsLangId::isKorean(aLang))
+ {
+ SwScriptInfo::CJKJustify( rInf.GetText(), aKernArray,
+ rInf.GetIdx(), rInf.GetLen(), aLang, nSpaceAdd, rInf.IsSpaceStop() );
+
+ bSpecialJust = true;
+ nSpaceAdd = 0;
+ }
+ }
+
+ // Kashida Justification
+ if ( SwFontScript::CTL == nActual && nSpaceAdd )
+ {
+ if ( SwScriptInfo::IsArabicText( rInf.GetText(), rInf.GetIdx(), rInf.GetLen() ) )
+ {
+ aKashidaArray.resize(aKernArray.size(), false);
+ if ( pSI && pSI->CountKashida() &&
+ pSI->KashidaJustify( &aKernArray, aKashidaArray.data(), rInf.GetIdx(),
+ rInf.GetLen(), nSpaceAdd ) != -1 )
+ {
+ bSpecialJust = true;
+ nSpaceAdd = 0;
+ }
+ else
+ aKashidaArray.clear();
+ }
+ }
+
+ // Thai Justification
+ if ( SwFontScript::CTL == nActual && nSpaceAdd )
+ {
+ LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CTL );
+
+ if ( LANGUAGE_THAI == aLang )
+ {
+ // Use rInf.GetSpace() because it has more precision than
+ // nSpaceAdd:
+ SwScriptInfo::ThaiJustify( rInf.GetText(), &aKernArray,
+ rInf.GetIdx(), rInf.GetLen(),
+ rInf.GetNumberOfBlanks(),
+ rInf.GetSpace() );
+
+ // adding space to blanks is already done
+ bSpecialJust = true;
+ nSpaceAdd = 0;
+ }
+ }
+ }
+
+ tools::Long nKernSum = rInf.GetKern();
+
+ if ( bStretch || m_bPaintBlank || rInf.GetKern() || bSpecialJust )
+ {
+ for (sal_Int32 i = 0; i < sal_Int32(rInf.GetLen()); i++,
+ nKernSum += rInf.GetKern() )
+ {
+ if (CH_BLANK == rInf.GetText()[sal_Int32(rInf.GetIdx()) + i])
+ nKernSum += nSpaceAdd;
+ aKernArray.adjust(i, nKernSum);
+ }
+
+ // In case of underlined/strike-through justified text
+ // a blank at the end requires special handling:
+ if( m_bPaintBlank && rInf.GetLen() && ( CH_BLANK ==
+ rInf.GetText()[sal_Int32(rInf.GetIdx() + rInf.GetLen())-1]))
+ {
+ // If it is a single underlined space, output 2 spaces:
+ if (TextFrameIndex(1) == rInf.GetLen())
+ {
+ aKernArray.set(0, rInf.GetWidth() + nSpaceAdd);
+
+ rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
+ aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), 1 );
+ }
+ else
+ {
+ sal_Int32 nIndex(sal_Int32(rInf.GetLen()) - 2);
+ aKernArray.adjust(nIndex, nSpaceAdd);
+ rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
+ aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
+ }
+ }
+ else
+ rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
+ aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
+ }
+ else
+ {
+ Point aTmpPos( aTextOriginPos );
+ sal_Int32 j = 0;
+ sal_Int32 i;
+ for( i = 0; i < sal_Int32(rInf.GetLen()); i++ )
+ {
+ if (CH_BLANK == rInf.GetText()[sal_Int32(rInf.GetIdx()) + i])
+ {
+ nKernSum += nSpaceAdd;
+ if( j < i )
+ rInf.GetOut().DrawText( aTmpPos, rInf.GetText(),
+ sal_Int32(rInf.GetIdx()) + j, i - j);
+ j = i + 1;
+ SwTwips nAdd = aKernArray[ i ] + nKernSum;
+ if ( ( vcl::text::ComplexTextLayoutFlags::BiDiStrong | vcl::text::ComplexTextLayoutFlags::BiDiRtl ) == nMode )
+ nAdd *= -1;
+ aTmpPos.setX( aTextOriginPos.X() + nAdd );
+ }
+ }
+ if( j < i )
+ rInf.GetOut().DrawText( aTmpPos, rInf.GetText(),
+ sal_Int32(rInf.GetIdx()) + j, i - j);
+ }
+ }
+ else if( bStretch )
+ {
+ tools::Long nTmpWidth = rInf.GetWidth();
+ if( rInf.GetKern() && rInf.GetLen() && nTmpWidth > rInf.GetKern() )
+ nTmpWidth -= rInf.GetKern();
+ rInf.GetOut().DrawStretchText( aTextOriginPos, nTmpWidth,
+ rInf.GetText(),
+ sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
+ }
+ else if( rInf.GetKern() )
+ {
+ const tools::Long nTmpWidth = GetTextSize( rInf ).Width();
+
+ const Color aSaveColor( pTmpFont->GetColor() );
+ const bool bColorChanged = rInf.ApplyAutoColor( pTmpFont );
+
+ if( bColorChanged )
+ {
+ if( !pTmpFont->IsSameInstance( rInf.GetOut().GetFont() ) )
+ rInf.GetOut().SetFont( *pTmpFont );
+ pTmpFont->SetColor( aSaveColor );
+ }
+
+ rInf.GetOut().DrawStretchText( aTextOriginPos, nTmpWidth,
+ rInf.GetText(),
+ sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
+ }
+ else
+ rInf.GetOut().DrawText( aTextOriginPos, rInf.GetText(),
+ sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
+ }
+
+ // PAINTING WITH FORMATTING DEVICE/SCREEN ADJUSTMENT
+
+ else
+ {
+ OUString aBulletOverlay;
+ bool bBullet = rInf.GetBullet();
+ if( m_bSymbol )
+ bBullet = false;
+ CreateScrFont( *rInf.GetShell(), rInf.GetOut() );
+
+ VclPtr<OutputDevice> xFormattingDevice;
+
+ // OLE: no printer available
+ // OSL_ENSURE( pPrinter, "DrawText needs pPrinter" )
+ if ( m_pPrinter )
+ {
+ // pTmpFont has already been set as current font for rInf.GetOut()
+ if ( m_pPrinter.get() != rInf.GetpOut() || pTmpFont != m_pPrtFont )
+ {
+ if( !m_pPrtFont->IsSameInstance( m_pPrinter->GetFont() ) )
+ m_pPrinter->SetFont( *m_pPrtFont );
+ }
+ xFormattingDevice = m_pPrinter;
+ }
+ else
+ {
+ xFormattingDevice = &rInf.GetOut();
+ }
+
+ //tdf#152094 see if we can retain a subpixel factor
+ int nSubPixels = 1;
+ MapMode aMapMode(xFormattingDevice->GetMapMode());
+ if (aMapMode.IsSimple() && aMapMode.GetMapUnit() == MapUnit::MapTwip)
+ {
+ if (xFormattingDevice->GetDPIX() == xFormattingDevice->GetDPIY())
+ {
+ int nRatio = xFormattingDevice->GetDPIX() / 1440;
+ if (nRatio * 1440 == xFormattingDevice->GetDPIX())
+ nSubPixels = nRatio;
+ }
+ }
+ KernArray aKernArray(nSubPixels);
+ GetTextArray(*xFormattingDevice, rInf, aKernArray);
+
+ std::vector<sal_Bool> aKashidaArray;
+
+ // Modify Printer and ScreenArrays for special justifications
+
+ tools::Long nSpaceAdd = rInf.GetSpace() / SPACING_PRECISION_FACTOR;
+ bool bNoHalfSpace = false;
+
+ if ( rInf.GetFont() && rInf.GetLen() )
+ {
+ const SwFontScript nActual = rInf.GetFont()->GetActual();
+ const SwScriptInfo* pSI = rInf.GetScriptInfo();
+
+ // Kana Compression
+ if ( SwFontScript::CJK == nActual && rInf.GetKanaComp() &&
+ pSI && pSI->CountCompChg() &&
+ lcl_IsMonoSpaceFont( rInf.GetOut() ) )
+ {
+ pSI->Compress( aKernArray, rInf.GetIdx(), rInf.GetLen(),
+ rInf.GetKanaComp(),
+ o3tl::narrowing<sal_uInt16>(m_aFont.GetFontSize().Height()), lcl_IsFullstopCentered( rInf.GetOut() ), &aTextOriginPos );
+ }
+
+ // Asian Justification
+ if ( SwFontScript::CJK == nActual && nSpaceAdd )
+ {
+ LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CJK );
+
+ if (!MsLangId::isKorean(aLang))
+ {
+ SwScriptInfo::CJKJustify( rInf.GetText(), aKernArray,
+ rInf.GetIdx(), rInf.GetLen(), aLang, nSpaceAdd, rInf.IsSpaceStop() );
+
+ nSpaceAdd = 0;
+ }
+ }
+
+ // Kashida Justification
+ if ( SwFontScript::CTL == nActual && nSpaceAdd )
+ {
+ if ( SwScriptInfo::IsArabicText( rInf.GetText(), rInf.GetIdx(), rInf.GetLen() ) )
+ {
+ aKashidaArray.resize(aKernArray.size(), false);
+ if ( pSI && pSI->CountKashida() &&
+ pSI->KashidaJustify( &aKernArray, aKashidaArray.data(), rInf.GetIdx(),
+ rInf.GetLen(), nSpaceAdd ) != -1 )
+ nSpaceAdd = 0;
+ else
+ {
+ aKashidaArray.clear();
+ bNoHalfSpace = true;
+ }
+ }
+ }
+
+ // Thai Justification
+ if ( SwFontScript::CTL == nActual && nSpaceAdd )
+ {
+ LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CTL );
+
+ if ( LANGUAGE_THAI == aLang )
+ {
+ SwScriptInfo::ThaiJustify( rInf.GetText(), &aKernArray,
+ rInf.GetIdx(),
+ rInf.GetLen(),
+ rInf.GetNumberOfBlanks(),
+ rInf.GetSpace() );
+
+ // adding space to blanks is already done
+ nSpaceAdd = 0;
+ }
+ }
+ }
+
+ if( bBullet )
+ {
+ // Copy the substring that will be painted, and replace spaces with
+ // bullets, and everything else with space.
+ sal_Int32 nCopyStart = sal_Int32(rInf.GetIdx());
+ sal_Int32 nCopyLen = sal_Int32(rInf.GetLen());
+
+ aBulletOverlay = rInf.GetText().copy( nCopyStart, nCopyLen );
+
+ for( sal_Int32 i = 0; i < aBulletOverlay.getLength(); ++i )
+ if( CH_BLANK == aBulletOverlay[ i ] )
+ {
+ /* fdo#72488 Hack: try to see if the space is zero width
+ * and don't bother with inserting a bullet in this case.
+ */
+ if ((i + nCopyStart + 1 >= sal_Int32(rInf.GetLen())) ||
+ aKernArray[i + nCopyStart] != aKernArray[ i + nCopyStart + 1])
+ {
+ aBulletOverlay = aBulletOverlay.replaceAt(i, 1, rtl::OUStringChar(CH_BULLET));
+ }
+ else
+ {
+ aBulletOverlay = aBulletOverlay.replaceAt(i, 1, rtl::OUStringChar(CH_BLANK));
+ }
+ }
+ else
+ {
+ aBulletOverlay = aBulletOverlay.replaceAt(i, 1, rtl::OUStringChar(CH_BLANK));
+ }
+ }
+
+ TextFrameIndex nCnt(rInf.GetText().getLength());
+ if ( nCnt < rInf.GetIdx() )
+ assert(false); // layout bug, not handled below
+ else
+ nCnt = nCnt - rInf.GetIdx();
+ nCnt = std::min(nCnt, rInf.GetLen());
+ sal_Unicode cChPrev = rInf.GetText()[sal_Int32(rInf.GetIdx())];
+
+ // In case of a single underlined space in justified text,
+ // have to output 2 spaces:
+ if ((nCnt == TextFrameIndex(1)) && rInf.GetSpace() && (cChPrev == CH_BLANK))
+ {
+ aKernArray.set(0, rInf.GetWidth() +
+ rInf.GetKern() +
+ (rInf.GetSpace() / SPACING_PRECISION_FACTOR));
+
+ if ( bSwitchL2R )
+ rInf.GetFrame()->SwitchLTRtoRTL( aTextOriginPos );
+
+ if ( bSwitchH2V )
+ rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos );
+
+ rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
+ aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), 1 );
+ if( bBullet )
+ rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(), aKernArray, {},
+ sal_Int32(rInf.GetIdx()), 1 );
+ }
+ else
+ {
+ if (m_pPrtFont->IsWordLineMode())
+ bNoHalfSpace = true;
+
+ sw::Justify::SpaceDistribution(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()),
+ sal_Int32(nCnt), nSpaceAdd, rInf.GetKern(), bNoHalfSpace);
+
+ if( rInf.GetGreyWave() )
+ {
+ if( rInf.GetLen() )
+ {
+ tools::Long nHght = rInf.GetOut().LogicToPixel(
+ m_pPrtFont->GetFontSize() ).Height();
+ if( WRONG_SHOW_MIN < nHght )
+ {
+ if ( rInf.GetOut().GetConnectMetaFile() )
+ rInf.GetOut().Push();
+
+ Color aCol( rInf.GetOut().GetLineColor() );
+ bool bColSave = aCol != gWaveCol;
+ if ( bColSave )
+ rInf.GetOut().SetLineColor( gWaveCol );
+
+ Point aEnd;
+ tools::Long nKernVal = aKernArray[sal_Int32(rInf.GetLen()) - 1];
+
+ const Degree10 nDir = bBidiPor
+ ? 1800_deg10
+ : UnMapDirection(GetFont().GetOrientation(),
+ bSwitchH2V, bSwitchH2VLRBT);
+
+ switch ( nDir.get() )
+ {
+ case 0 :
+ aEnd.setX( rInf.GetPos().X() + nKernVal );
+ aEnd.setY( rInf.GetPos().Y() );
+ break;
+ case 900 :
+ aEnd.setX( rInf.GetPos().X() );
+ aEnd.setY( rInf.GetPos().Y() - nKernVal );
+ break;
+ case 1800 :
+ aEnd.setX( rInf.GetPos().X() - nKernVal );
+ aEnd.setY( rInf.GetPos().Y() );
+ break;
+ case 2700 :
+ aEnd.setX( rInf.GetPos().X() );
+ aEnd.setY( rInf.GetPos().Y() + nKernVal );
+ break;
+ }
+
+ Point aCurrPos( rInf.GetPos() );
+
+ if ( bSwitchL2R )
+ {
+ rInf.GetFrame()->SwitchLTRtoRTL( aCurrPos );
+ rInf.GetFrame()->SwitchLTRtoRTL( aEnd );
+ }
+
+ if ( bSwitchH2V )
+ {
+ rInf.GetFrame()->SwitchHorizontalToVertical( aCurrPos );
+ rInf.GetFrame()->SwitchHorizontalToVertical( aEnd );
+ }
+ {
+ vcl::ScopedAntialiasing a(rInf.GetOut(), true);
+ rInf.GetOut().DrawWaveLine( aCurrPos, aEnd );
+ }
+ if ( bColSave )
+ rInf.GetOut().SetLineColor( aCol );
+
+ if ( rInf.GetOut().GetConnectMetaFile() )
+ rInf.GetOut().Pop();
+ }
+ }
+ }
+ else if( !m_bSymbol && rInf.GetLen() )
+ {
+ // anything to do?
+ if (rInf.GetWrong() || rInf.GetGrammarCheck() || rInf.GetSmartTags())
+ {
+ const tools::Long nHalfSpace = bNoHalfSpace ? 0 : nSpaceAdd / 2;
+ CalcLinePosData aCalcLinePosData(rInf, GetFont(), nCnt, bSwitchH2V,
+ bSwitchH2VLRBT, bSwitchL2R, nHalfSpace,
+ aKernArray, bBidiPor);
+
+ SwForbidden aForbidden;
+ // draw line for smart tag data
+ lcl_DrawLineForWrongListData( aForbidden, rInf, rInf.GetSmartTags(), aCalcLinePosData, Size() );
+ // draw wave line for spell check errors
+ // draw them BEFORE the grammar check lines to 'override' the latter in case of conflict.
+ // reason: some grammar errors can only be found if spelling errors are fixed,
+ // therefore we don't want the user to miss a spelling error.
+ lcl_DrawLineForWrongListData( aForbidden, rInf, rInf.GetWrong(), aCalcLinePosData, m_pPrtFont->GetFontSize() );
+ // draw wave line for grammar check errors
+ lcl_DrawLineForWrongListData( aForbidden, rInf, rInf.GetGrammarCheck(), aCalcLinePosData, m_pPrtFont->GetFontSize() );
+ }
+ }
+
+ sal_Int32 nLen = sal_Int32(rInf.GetLen());
+
+ if( nLen > 0 )
+ {
+
+ if ( bSwitchL2R )
+ rInf.GetFrame()->SwitchLTRtoRTL( aTextOriginPos );
+
+ if ( bSwitchH2V )
+ rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos );
+
+ sal_Int32 nIdx = sal_Int32(rInf.GetIdx());
+ const SalLayoutGlyphs* pGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(&rInf.GetOut(),
+ rInf.GetText(), nIdx, nLen);
+ rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(), aKernArray, aKashidaArray,
+ nIdx, nLen, SalLayoutFlags::NONE, pGlyphs );
+ if (bBullet)
+ {
+ rInf.GetOut().Push();
+ Color aPreviousColor = pTmpFont->GetColor();
+
+ FontLineStyle aPreviousUnderline = pTmpFont->GetUnderline();
+ FontLineStyle aPreviousOverline = pTmpFont->GetOverline();
+ FontStrikeout aPreviousStrikeout = pTmpFont->GetStrikeout();
+
+ pTmpFont->SetColor( NON_PRINTING_CHARACTER_COLOR );
+ pTmpFont->SetUnderline(LINESTYLE_NONE);
+ pTmpFont->SetOverline(LINESTYLE_NONE);
+ pTmpFont->SetStrikeout(STRIKEOUT_NONE);
+ rInf.GetOut().SetFont( *pTmpFont );
+ tools::Long nShift = rInf.GetOut( ).GetFontMetric( ).GetBulletOffset( );
+ if ( nShift )
+ {
+ tools::Long nAdd = 0;
+
+ if ( aBulletOverlay[ 0 ] == CH_BULLET )
+ {
+ if (bSwitchH2V)
+ aTextOriginPos.AdjustY(nShift ) ;
+ else
+ aTextOriginPos.AdjustX(nShift ) ;
+ nAdd = nShift ;
+ }
+ for( sal_Int32 i = 1 ; i < nLen ; ++i )
+ {
+ if ( aBulletOverlay[ i ] == CH_BULLET )
+ aKernArray.adjust(i - 1, nShift);
+ if ( nAdd )
+ aKernArray.adjust(i - 1, -nAdd);
+ }
+ }
+ rInf.GetOut().DrawTextArray( aTextOriginPos, aBulletOverlay, aKernArray,
+ {}, 0, nLen );
+ pTmpFont->SetColor( aPreviousColor );
+
+ pTmpFont->SetUnderline(aPreviousUnderline);
+ pTmpFont->SetOverline(aPreviousOverline);
+ pTmpFont->SetStrikeout(aPreviousStrikeout);
+ rInf.GetOut().Pop();
+ }
+ }
+ }
+ }
+}
+
+Size SwFntObj::GetTextSize( SwDrawTextInfo& rInf )
+{
+ Size aTextSize;
+ const TextFrameIndex nLn = (TextFrameIndex(COMPLETE_STRING) != rInf.GetLen())
+ ? rInf.GetLen()
+ : TextFrameIndex(rInf.GetText().getLength());
+
+ const TextFrameIndex nMsrLn = (TextFrameIndex(COMPLETE_STRING) != rInf.GetMeasureLen())
+ ? rInf.GetMeasureLen()
+ : nLn;
+
+ // If the measure length is different from the length, then we are
+ // measuring substring width for caret positioning, see SetMeasureLength()
+ // use in TextCursor::GetCharRect_().
+ bool bCaret(nMsrLn != nLn);
+
+ // be sure to have the correct layout mode at the printer
+ if ( m_pPrinter )
+ {
+ m_pPrinter->SetLayoutMode( rInf.GetOut().GetLayoutMode() );
+ m_pPrinter->SetDigitLanguage( rInf.GetOut().GetDigitLanguage() );
+ }
+
+ if ( rInf.GetFrame() && nLn && rInf.SnapToGrid() && rInf.GetFont() &&
+ SwFontScript::CJK == rInf.GetFont()->GetActual() )
+ {
+ SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame()));
+ if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() )
+ {
+ const SwDoc* pDoc = rInf.GetShell()->GetDoc();
+ const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc);
+
+ OutputDevice* pOutDev;
+
+ if ( m_pPrinter )
+ {
+ if( !m_pPrtFont->IsSameInstance( m_pPrinter->GetFont() ) )
+ m_pPrinter->SetFont(*m_pPrtFont);
+ pOutDev = m_pPrinter;
+ }
+ else
+ pOutDev = rInf.GetpOut();
+
+ aTextSize.setWidth( pOutDev->GetTextWidth(rInf.GetText(),
+ sal_Int32(rInf.GetIdx()), sal_Int32(nLn)) );
+
+ OSL_ENSURE( !rInf.GetShell() ||
+ ( USHRT_MAX != GetGuessedLeading() && USHRT_MAX != GetExternalLeading() ),
+ "Leading values should be already calculated" );
+ aTextSize.setHeight( pOutDev->GetTextHeight() +
+ GetFontLeading( rInf.GetShell(), rInf.GetOut() ) );
+
+ KernArray aKernArray;
+ GetTextArray(*pOutDev, rInf, aKernArray, sal_Int32(nLn), bCaret);
+ if (pGrid->IsSnapToChars())
+ {
+ sw::Justify::SnapToGrid(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()),
+ sal_Int32(rInf.GetLen()), nGridWidth, true);
+ }
+ else
+ {
+ // use 0 to calculate raw width without rInf.GetSpace().
+ sw::Justify::SnapToGridEdge(aKernArray, sal_Int32(rInf.GetLen()), nGridWidth, 0,
+ rInf.GetKern());
+ }
+
+ aTextSize.setWidth(aKernArray[sal_Int32(nMsrLn) - 1]);
+ rInf.SetKanaDiff( 0 );
+ return aTextSize;
+ }
+ }
+
+ const bool bCompress = rInf.GetKanaComp() && nLn &&
+ rInf.GetFont() &&
+ SwFontScript::CJK == rInf.GetFont()->GetActual() &&
+ rInf.GetScriptInfo() &&
+ rInf.GetScriptInfo()->CountCompChg() &&
+ lcl_IsMonoSpaceFont( rInf.GetOut() );
+
+ OSL_ENSURE( !bCompress || ( rInf.GetScriptInfo() && rInf.GetScriptInfo()->
+ CountCompChg()), "Compression without info" );
+
+ // This is the part used e.g., for cursor travelling
+ // See condition for DrawText or DrawTextArray (bDirectPrint)
+ KernArray aKernArray;
+ if ( m_pPrinter && m_pPrinter.get() != rInf.GetpOut() )
+ {
+ if( !m_pPrtFont->IsSameInstance( m_pPrinter->GetFont() ) )
+ m_pPrinter->SetFont(*m_pPrtFont);
+ aTextSize.setHeight( m_pPrinter->GetTextHeight() );
+
+ CreateScrFont( *rInf.GetShell(), rInf.GetOut() );
+ if( !GetScrFont()->IsSameInstance( rInf.GetOut().GetFont() ) )
+ rInf.GetOut().SetFont( *m_pScrFont );
+
+ GetTextArray(*m_pPrinter, rInf.GetText(), aKernArray,
+ sal_Int32(rInf.GetIdx()), sal_Int32(nLn), bCaret);
+ }
+ else
+ {
+ if( !m_pPrtFont->IsSameInstance( rInf.GetOut().GetFont() ) )
+ rInf.GetOut().SetFont( *m_pPrtFont );
+ aTextSize.setHeight( rInf.GetOut().GetTextHeight() );
+
+ GetTextArray(rInf.GetOut(), rInf, aKernArray, nLn.get(), bCaret);
+ }
+
+ if (bCompress)
+ {
+ rInf.SetKanaDiff(rInf.GetScriptInfo()->Compress(aKernArray, rInf.GetIdx(), nLn, rInf.GetKanaComp(),
+ o3tl::narrowing<sal_uInt16>(m_aFont.GetFontSize().Height()), lcl_IsFullstopCentered(rInf.GetOut())));
+ }
+ else
+ rInf.SetKanaDiff( 0 );
+
+ if (nMsrLn)
+ {
+ aTextSize.setWidth(aKernArray[sal_Int32(nMsrLn) - 1]);
+
+ // Note that we can't simply use sal_Int(nMsrLn) - 1 as nSpaceCount
+ // because a glyph may be made up of more than one characters.
+ sal_Int32 nSpaceCount = 0;
+ tools::Long nOldValue = aKernArray[0];
+
+ for(sal_Int32 i = 1; i < sal_Int32(nMsrLn); ++i)
+ {
+ if (nOldValue != aKernArray[i])
+ {
+ ++nSpaceCount;
+ nOldValue = aKernArray[i];
+ }
+ }
+
+ if (rInf.GetKern())
+ aTextSize.AdjustWidth(nSpaceCount * rInf.GetKern());
+ }
+
+ OSL_ENSURE( !rInf.GetShell() ||
+ ( USHRT_MAX != GetGuessedLeading() && USHRT_MAX != GetExternalLeading() ),
+ "Leading values should be already calculated" );
+ aTextSize.AdjustHeight(GetFontLeading( rInf.GetShell(), rInf.GetOut() ) );
+ return aTextSize;
+}
+
+TextFrameIndex SwFntObj::GetModelPositionForViewPoint(SwDrawTextInfo &rInf)
+{
+ tools::Long nSpaceAdd = rInf.GetSpace() / SPACING_PRECISION_FACTOR;
+ const tools::Long nCharacterSpacing = -rInf.GetCharacterSpacing() / SPACING_PRECISION_FACTOR;
+ tools::Long nKern = rInf.GetKern();
+
+ if( 0 != nCharacterSpacing )
+ nKern -= nCharacterSpacing;
+
+ KernArray aKernArray;
+
+ // be sure to have the correct layout mode at the printer
+ if ( m_pPrinter )
+ {
+ m_pPrinter->SetLayoutMode( rInf.GetOut().GetLayoutMode() );
+ m_pPrinter->SetDigitLanguage( rInf.GetOut().GetDigitLanguage() );
+ GetTextArray(*m_pPrinter, rInf, aKernArray);
+ }
+ else
+ {
+ GetTextArray(rInf.GetOut(), rInf, aKernArray);
+ }
+
+ if ( rInf.GetFrame() && rInf.GetLen() && rInf.SnapToGrid() &&
+ rInf.GetFont() && SwFontScript::CJK == rInf.GetFont()->GetActual() )
+ {
+ SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame()));
+ if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() )
+ {
+ const SwDoc* pDoc = rInf.GetShell()->GetDoc();
+ const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc);
+
+ if (pGrid->IsSnapToChars())
+ {
+ sw::Justify::SnapToGrid(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()),
+ sal_Int32(rInf.GetLen()), nGridWidth, true);
+ }
+ else
+ {
+ sw::Justify::SnapToGridEdge(aKernArray, sal_Int32(rInf.GetLen()), nGridWidth,
+ nSpaceAdd, rInf.GetKern());
+ }
+
+ return TextFrameIndex(sw::Justify::GetModelPosition(aKernArray, sal_Int32(rInf.GetLen()),
+ rInf.GetOffset()));
+ }
+ }
+
+ const SwScriptInfo* pSI = rInf.GetScriptInfo();
+ if ( rInf.GetFont() && rInf.GetLen() )
+ {
+ const SwFontScript nActual = rInf.GetFont()->GetActual();
+
+ // Kana Compression
+ if ( SwFontScript::CJK == nActual && rInf.GetKanaComp() &&
+ pSI && pSI->CountCompChg() &&
+ lcl_IsMonoSpaceFont( rInf.GetOut() ) )
+ {
+ pSI->Compress( aKernArray, rInf.GetIdx(), rInf.GetLen(),
+ rInf.GetKanaComp(),
+ o3tl::narrowing<sal_uInt16>(m_aFont.GetFontSize().Height()),
+ lcl_IsFullstopCentered( rInf.GetOut() ) );
+ }
+
+ // Asian Justification
+ if ( SwFontScript::CJK == rInf.GetFont()->GetActual() )
+ {
+ LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CJK );
+
+ if (!MsLangId::isKorean(aLang))
+ {
+ SwScriptInfo::CJKJustify( rInf.GetText(), aKernArray,
+ rInf.GetIdx(), rInf.GetLen(), aLang, nSpaceAdd, rInf.IsSpaceStop() );
+
+ nSpaceAdd = 0;
+ }
+
+ }
+
+ // Kashida Justification
+ if ( SwFontScript::CTL == nActual && rInf.GetSpace() )
+ {
+ if ( SwScriptInfo::IsArabicText( rInf.GetText(), rInf.GetIdx(), rInf.GetLen() ) )
+ {
+ if ( pSI && pSI->CountKashida() &&
+ pSI->KashidaJustify( &aKernArray, nullptr, rInf.GetIdx(), rInf.GetLen(),
+ nSpaceAdd ) != -1 )
+ nSpaceAdd = 0;
+ }
+ }
+
+ // Thai Justification
+ if ( SwFontScript::CTL == nActual && nSpaceAdd )
+ {
+ LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CTL );
+
+ if ( LANGUAGE_THAI == aLang )
+ {
+ SwScriptInfo::ThaiJustify( rInf.GetText(), &aKernArray,
+ rInf.GetIdx(), rInf.GetLen(),
+ rInf.GetNumberOfBlanks(),
+ rInf.GetSpace() );
+
+ // adding space to blanks is already done
+ nSpaceAdd = 0;
+ }
+ }
+ }
+
+ tools::Long nLeft = 0;
+ tools::Long nRight = 0;
+ TextFrameIndex nCnt(0);
+ tools::Long nSpaceSum = 0;
+ tools::Long nKernSum = 0;
+
+ sal_Int32 nDone = 0;
+ TextFrameIndex nIdx = rInf.GetIdx();
+ TextFrameIndex nLastIdx = nIdx;
+ const TextFrameIndex nEnd = rInf.GetIdx() + rInf.GetLen();
+
+ // #i105901#
+ // skip character cells for all script types
+ LanguageType aLang = rInf.GetFont()->GetLanguage();
+
+ while ( ( nRight < tools::Long( rInf.GetOffset() ) ) && ( nIdx < nEnd ) )
+ {
+ if (nSpaceAdd && CH_BLANK == rInf.GetText()[ sal_Int32(nIdx)])
+ nSpaceSum += nSpaceAdd;
+
+ // go to next character (cell).
+ nLastIdx = nIdx;
+
+ nIdx = TextFrameIndex(g_pBreakIt->GetBreakIter()->nextCharacters(
+ rInf.GetText(), sal_Int32(nIdx),
+ g_pBreakIt->GetLocale( aLang ),
+ i18n::CharacterIteratorMode::SKIPCELL, 1, nDone));
+ if ( nIdx <= nLastIdx )
+ break;
+
+ // the next character might be outside the layout range (e.g tdf124116-1.odt)
+ if (nIdx > nEnd)
+ nIdx = nEnd;
+
+ nLeft = nRight;
+ nRight = aKernArray[sal_Int32(nIdx - rInf.GetIdx()) - 1] + nKernSum + nSpaceSum;
+
+ nKernSum += nKern;
+ }
+
+ // step back if position is before the middle of the character
+ // or if we do not want to go to the next character
+ if ( nIdx > rInf.GetIdx() &&
+ ( rInf.IsPosMatchesBounds() ||
+ ( ( nRight > tools::Long( rInf.GetOffset() ) ) &&
+ ( nRight - rInf.GetOffset() > rInf.GetOffset() - nLeft ) ) ) )
+ nCnt = nLastIdx - rInf.GetIdx(); // first half
+ else
+ nCnt = nIdx - rInf.GetIdx(); // second half
+
+ if ( pSI )
+ rInf.SetCursorBidiLevel( pSI->DirType( nLastIdx ) );
+
+ return nCnt;
+}
+
+SwFntAccess::SwFntAccess( const void* & rnFontCacheId,
+ sal_uInt16 &rIndex, const void *pOwn, SwViewShell const *pSh,
+ bool bCheck ) :
+ SwCacheAccess( *pFntCache, rnFontCacheId, rIndex ),
+ m_pShell( pSh )
+{
+ // the used ctor of SwCacheAccess searches for rnFontCacheId+rIndex in the cache
+ if ( m_pObj )
+ {
+ // fast case: known Font (rnFontCacheId), no need to check printer and zoom
+ if ( !bCheck )
+ return;
+
+ // Font is known, but has to be checked
+ }
+ else
+ { // Font not known, must be searched
+ bCheck = false;
+ }
+
+ {
+ OutputDevice* pOut = nullptr;
+ sal_uInt16 nZoom = USHRT_MAX;
+
+ // Get the reference device
+ if ( pSh )
+ {
+ pOut = &pSh->GetRefDev();
+ nZoom = pSh->GetViewOptions()->GetZoom();
+ }
+
+ SwFntObj *pFntObj;
+ if ( bCheck )
+ {
+ pFntObj = Get();
+ if ( ( pFntObj->GetZoom( ) == nZoom ) &&
+ ( pFntObj->m_pPrinter == pOut ) &&
+ pFntObj->GetPropWidth() ==
+ static_cast<SwSubFont const *>(pOwn)->GetPropWidth() )
+ {
+ return; // result of Check: Drucker+Zoom okay.
+ }
+ pFntObj->Unlock(); // forget this object, printer/zoom differs
+ m_pObj = nullptr;
+ }
+
+ // Search by font comparison, quite expensive!
+ // Look for same font and same printer
+ pFntObj = pFntCache->First();
+ while ( pFntObj && !( pFntObj->m_aFont == *static_cast<vcl::Font const *>(pOwn) &&
+ pFntObj->GetZoom() == nZoom &&
+ pFntObj->GetPropWidth() ==
+ static_cast<SwSubFont const *>(pOwn)->GetPropWidth() &&
+ ( !pFntObj->m_pPrinter || pFntObj->m_pPrinter == pOut ) ) )
+ pFntObj = SwFntCache::Next( pFntObj );
+
+ if( pFntObj && pFntObj->m_pPrinter.get() != pOut )
+ {
+ // found one without printer, let's see if there is one with
+ // the same printer as well
+ SwFntObj *pTmpObj = pFntObj;
+ while( pTmpObj && !( pTmpObj->m_aFont == *static_cast<vcl::Font const *>(pOwn) &&
+ pTmpObj->GetZoom()==nZoom && pTmpObj->m_pPrinter==pOut &&
+ pTmpObj->GetPropWidth() ==
+ static_cast<SwSubFont const *>(pOwn)->GetPropWidth() ) )
+ pTmpObj = SwFntCache::Next( pTmpObj );
+ if( pTmpObj )
+ pFntObj = pTmpObj;
+ }
+
+ if ( !pFntObj ) // Font has not been found, create one
+ {
+ // Have to create new Object, hence Owner must be a SwFont, later
+ // the Owner will be the "MagicNumber"
+ SwCacheAccess::m_pOwner = pOwn;
+ pFntObj = Get(); // will create via NewObj() and lock
+ OSL_ENSURE(pFntObj, "No Font, no Fun.");
+ }
+ else // Font has been found, so we lock it.
+ {
+ pFntObj->Lock();
+ if (pFntObj->m_pPrinter.get() != pOut) // if no printer is known by now
+ {
+ OSL_ENSURE( !pFntObj->m_pPrinter, "SwFntAccess: Printer Changed" );
+ pFntObj->CreatePrtFont( *pOut );
+ pFntObj->m_pPrinter = pOut;
+ pFntObj->m_pScrFont = nullptr;
+ pFntObj->m_nGuessedLeading = USHRT_MAX;
+ pFntObj->m_nExtLeading = USHRT_MAX;
+ pFntObj->m_nPrtAscent = USHRT_MAX;
+ pFntObj->m_nPrtHeight = USHRT_MAX;
+ }
+ m_pObj = pFntObj;
+ }
+
+ // no matter if new or found, now the Owner of the Object is a
+ // MagicNumber, and will be given to the SwFont, as well as the Index
+ // for later direct access
+ rnFontCacheId = reinterpret_cast<void*>(reinterpret_cast<sal_IntPtr>(pFntObj->GetOwner()));
+ SwCacheAccess::m_pOwner = pFntObj->GetOwner();
+ rIndex = pFntObj->GetCachePos();
+ }
+}
+
+SwCacheObj *SwFntAccess::NewObj( )
+{
+ // "MagicNumber" used to identify Fonts
+ static std::uintptr_t fontCacheIdCounter = 0;
+ // a new Font, a new "MagicNumber".
+ return new SwFntObj( *static_cast<SwSubFont const *>(m_pOwner), ++fontCacheIdCounter, m_pShell );
+}
+
+TextFrameIndex SwFont::GetTextBreak(SwDrawTextInfo const & rInf, tools::Long nTextWidth)
+{
+ ChgFnt( rInf.GetShell(), rInf.GetOut() );
+
+ const bool bCompress = rInf.GetKanaComp() && rInf.GetLen() &&
+ SwFontScript::CJK == GetActual() &&
+ rInf.GetScriptInfo() &&
+ rInf.GetScriptInfo()->CountCompChg() &&
+ lcl_IsMonoSpaceFont( rInf.GetOut() );
+
+ OSL_ENSURE( !bCompress || ( rInf.GetScriptInfo() && rInf.GetScriptInfo()->
+ CountCompChg()), "Compression without info" );
+
+ TextFrameIndex nTextBreak(0);
+ tools::Long nKern = 0;
+
+ TextFrameIndex nLn = rInf.GetLen() == TextFrameIndex(COMPLETE_STRING)
+ ? TextFrameIndex(rInf.GetText().getLength()) : rInf.GetLen();
+
+ if ( rInf.GetFrame() && nLn && rInf.SnapToGrid() &&
+ rInf.GetFont() && SwFontScript::CJK == rInf.GetFont()->GetActual() )
+ {
+ SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame()));
+ if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() )
+ {
+ const SwDoc* pDoc = rInf.GetShell()->GetDoc();
+ const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc);
+
+ KernArray aKernArray;
+ GetTextArray( rInf.GetOut(), rInf.GetText(), aKernArray,
+ sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
+
+ if (pGrid->IsSnapToChars())
+ {
+ sw::Justify::SnapToGrid(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()),
+ sal_Int32(rInf.GetLen()), nGridWidth, true);
+ }
+ else
+ {
+ // use 0 to calculate raw width without rInf.GetSpace().
+ sw::Justify::SnapToGridEdge(aKernArray, sal_Int32(rInf.GetLen()), nGridWidth,
+ 0, rInf.GetKern());
+ }
+
+ while(nTextBreak < rInf.GetLen() && aKernArray[sal_Int32(nTextBreak)] <= nTextWidth)
+ ++nTextBreak;
+
+ return nTextBreak + rInf.GetIdx();
+ }
+ }
+
+ if( m_aSub[m_nActual].IsCapital() && nLn )
+ {
+ nTextBreak = GetCapitalBreak( rInf.GetShell(), rInf.GetpOut(),
+ rInf.GetScriptInfo(), rInf.GetText(), nTextWidth, rInf.GetIdx(),
+ nLn );
+ }
+ else
+ {
+ nKern = CheckKerning();
+
+ const OUString* pTmpText;
+ OUString aTmpText;
+ TextFrameIndex nTmpIdx;
+ TextFrameIndex nTmpLen;
+ bool bTextReplaced = false;
+
+ if ( !m_aSub[m_nActual].IsCaseMap() )
+ {
+ pTmpText = &rInf.GetText();
+ nTmpIdx = rInf.GetIdx();
+ nTmpLen = nLn;
+ }
+ else
+ {
+ const OUString aSnippet(rInf.GetText().copy(sal_Int32(rInf.GetIdx()), sal_Int32(nLn)));
+ aTmpText = m_aSub[m_nActual].CalcCaseMap( aSnippet );
+ const bool bTitle = SvxCaseMap::Capitalize == m_aSub[m_nActual].GetCaseMap();
+
+ // Uaaaaahhhh!!! In title case mode, we would get wrong results
+ if ( bTitle && nLn )
+ {
+ // check if rInf.GetIdx() is begin of word
+ if ( !g_pBreakIt->GetBreakIter()->isBeginWord(
+ rInf.GetText(), sal_Int32(rInf.GetIdx()),
+ g_pBreakIt->GetLocale( m_aSub[m_nActual].GetLanguage() ),
+ i18n::WordType::ANYWORD_IGNOREWHITESPACES ) )
+ {
+ // In this case, the beginning of aTmpText is wrong.
+ OUString aSnippetTmp(aSnippet.copy(0, 1));
+ aSnippetTmp = m_aSub[m_nActual].CalcCaseMap( aSnippetTmp );
+ aTmpText = aTmpText.replaceAt( 0, aSnippetTmp.getLength(), rtl::OUStringChar(aSnippet[0]) );
+ }
+ }
+
+ pTmpText = &aTmpText;
+ nTmpIdx = TextFrameIndex(0);
+ nTmpLen = TextFrameIndex(aTmpText.getLength());
+ bTextReplaced = true;
+ }
+
+ if( rInf.GetHyphPos() ) {
+ sal_Int32 nHyphPos = sal_Int32(*rInf.GetHyphPos());
+ const SalLayoutGlyphs* pGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
+ &rInf.GetOut(), *pTmpText, nTmpIdx.get(), nTmpLen.get(), 0, rInf.GetVclCache());
+ nTextBreak = TextFrameIndex(rInf.GetOut().GetTextBreak(
+ *pTmpText, nTextWidth,
+ u'-', nHyphPos,
+ sal_Int32(nTmpIdx), sal_Int32(nTmpLen),
+ nKern, rInf.GetVclCache(), pGlyphs));
+ *rInf.GetHyphPos() = TextFrameIndex((nHyphPos == -1) ? COMPLETE_STRING : nHyphPos);
+ }
+ else
+ {
+ SwFntAccess aFntAccess(m_aSub[m_nActual].m_nFontCacheId, m_aSub[m_nActual].m_nFontIndex,
+ &m_aSub[m_nActual], rInf.GetShell());
+ const SalLayoutGlyphs* pGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(&rInf.GetOut(),
+ *pTmpText, nTmpIdx.get(), nTmpLen.get(), 0, rInf.GetVclCache());
+ nTextBreak = TextFrameIndex(rInf.GetOut().GetTextBreak(
+ *pTmpText, nTextWidth,
+ sal_Int32(nTmpIdx), sal_Int32(nTmpLen),
+ nKern, rInf.GetVclCache(), pGlyphs));
+ }
+
+ if (bTextReplaced && sal_Int32(nTextBreak) != -1)
+ {
+ if ( nTmpLen != nLn )
+ nTextBreak = sw_CalcCaseMap( *this, rInf.GetText(),
+ rInf.GetIdx(), nLn, nTextBreak );
+ else
+ nTextBreak = nTextBreak + rInf.GetIdx();
+ }
+ }
+
+ TextFrameIndex nTextBreak2 = sal_Int32(nTextBreak) == -1
+ ? TextFrameIndex(COMPLETE_STRING)
+ : nTextBreak;
+
+ // tdf112290 tdf136588 Break the line correctly only if there is an image inline,
+ // and the image wider than the line...
+ if (GetCaseMap() == SvxCaseMap::SmallCaps && TextFrameIndex(COMPLETE_STRING) == nTextBreak2 &&
+ ! bCompress && nTextWidth == 0)
+ // If nTextWidth == 0 means the line is full, we have to break it
+ nTextBreak2 = TextFrameIndex(1);
+
+ if ( ! bCompress )
+ return nTextBreak2;
+
+ nTextBreak2 = nTextBreak2 - rInf.GetIdx();
+
+ if( nTextBreak2 < nLn )
+ {
+ if( !nTextBreak2 && nLn )
+ nLn = TextFrameIndex(1);
+ else if (nLn > nTextBreak2 + nTextBreak2)
+ nLn = nTextBreak2 + nTextBreak2;
+ KernArray aKernArray;
+ GetTextArray( rInf.GetOut(), rInf.GetText(), aKernArray,
+ sal_Int32(rInf.GetIdx()), sal_Int32(nLn));
+ if( rInf.GetScriptInfo()->Compress( aKernArray, rInf.GetIdx(), nLn,
+ rInf.GetKanaComp(), o3tl::narrowing<sal_uInt16>(GetHeight( m_nActual )),
+ lcl_IsFullstopCentered( rInf.GetOut() ) ) )
+ {
+ tools::Long nKernAdd = nKern;
+ TextFrameIndex const nTmpBreak = nTextBreak2;
+ if( nKern && nTextBreak2 )
+ nKern *= sal_Int32(nTextBreak2) - 1;
+ while (nTextBreak2 < nLn && nTextWidth >= aKernArray[sal_Int32(nTextBreak2)] + nKern)
+ {
+ nKern += nKernAdd;
+ ++nTextBreak2;
+ }
+ if( rInf.GetHyphPos() )
+ *rInf.GetHyphPos() += nTextBreak2 - nTmpBreak; // It's not perfect
+ }
+ }
+ nTextBreak2 = nTextBreak2 + rInf.GetIdx();
+
+ return nTextBreak2;
+}
+
+bool SwDrawTextInfo::ApplyAutoColor( vcl::Font* pFont )
+{
+ const vcl::Font& rFnt = pFont ? *pFont : GetOut().GetFont();
+ Color nNewColor = COL_BLACK;
+ bool bChgFntColor = false;
+ bool bChgLineColor = false;
+
+ const SwViewShell *pVSh = GetShell();
+ const bool bOutputToWindow(pVSh && (pVSh->GetWin() || pVSh->isOutputToWindow()));
+
+ if (pVSh && !bOutputToWindow && pVSh->GetViewOptions()->IsBlackFont())
+ {
+ if ( COL_BLACK != rFnt.GetColor() )
+ bChgFntColor = true;
+
+ if ( (COL_BLACK != GetOut().GetLineColor()) ||
+ (COL_BLACK != GetOut().GetOverlineColor()) )
+ bChgLineColor = true;
+ }
+ else
+ {
+ // FontColor has to be changed if:
+ // 1. FontColor = AUTO or 2. IsAlwaysAutoColor is set
+ // LineColor has to be changed if:
+ // 1. IsAlwaysAutoColor is set
+
+ bChgLineColor = pVSh && bOutputToWindow &&
+ pVSh->GetAccessibilityOptions()->IsAlwaysAutoColor();
+
+ bChgFntColor = COL_AUTO == rFnt.GetColor() || bChgLineColor;
+
+ if ( bChgFntColor )
+ {
+ // check if current background has a user defined setting
+ std::optional<Color> pCol;
+ if (GetFont())
+ pCol = GetFont()->GetBackColor();
+ if( ! pCol || COL_TRANSPARENT == *pCol )
+ {
+ const SvxBrushItem* pItem;
+ SwRect aOrigBackRect;
+ drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes;
+
+ /// OD 21.08.2002
+ /// consider, that [GetBackgroundBrush(...)] can set <pCol>
+ /// - see implementation in /core/layout/paintfrm.cxx
+ /// OD 21.08.2002 #99657#
+ /// There is a user defined setting for the background, if there
+ /// is a background brush and its color is *not* "no fill"/"auto fill".
+ if( GetFrame()->GetBackgroundBrush( aFillAttributes, pItem, pCol, aOrigBackRect, false, /*bConsiderTextBox=*/true ) )
+ {
+ if (aFillAttributes && aFillAttributes->isUsed())
+ {
+ // First see if fill attributes provide a color.
+ pCol = Color(aFillAttributes->getAverageColor(aGlobalRetoucheColor.getBColor()));
+ }
+
+ // If not, then fall back to the old brush item.
+ if ( !pCol )
+ {
+ pCol = pItem->GetColor();
+ }
+
+ /// OD 30.08.2002 #99657#
+ /// determined color <pCol> can be <COL_TRANSPARENT>. Thus, check it.
+ if ( *pCol == COL_TRANSPARENT)
+ pCol.reset();
+ }
+ else
+ pCol.reset();
+ }
+
+ // no user defined color at paragraph or font background
+ if ( ! pCol )
+ pCol = aGlobalRetoucheColor;
+
+ if (pVSh && bOutputToWindow)
+ {
+ // here we determine the preferred window text color for painting
+ const SwViewOption* pViewOption = pVSh->GetViewOptions();
+ if(pViewOption->IsPagePreview() &&
+ !officecfg::Office::Common::Accessibility::IsForPagePreviews::get())
+ nNewColor = COL_BLACK;
+ else
+ // we take the font color from the appearance page
+ nNewColor = pViewOption->GetFontColor();
+ }
+
+ // change painting color depending of dark/bright background
+ Color aTmpColor( nNewColor );
+ if ( pCol->IsDark() && aTmpColor.IsDark() )
+ nNewColor = COL_WHITE;
+ else if ( pCol->IsBright() && aTmpColor.IsBright() )
+ nNewColor = COL_BLACK;
+ }
+ }
+
+ if ( bChgFntColor || bChgLineColor )
+ {
+ Color aNewColor( nNewColor );
+
+ if ( bChgFntColor )
+ {
+ if ( pFont && aNewColor != pFont->GetColor() )
+ {
+ // only set the new color at the font passed as argument
+ pFont->SetColor( aNewColor );
+ }
+ else if ( aNewColor != GetOut().GetFont().GetColor() )
+ {
+ // set new font with new color at output device
+ vcl::Font aFont( rFnt );
+ aFont.SetColor( aNewColor );
+ GetOut().SetFont( aFont );
+ }
+ }
+
+ // the underline and overline colors have to be set separately
+ if ( bChgLineColor )
+ {
+ // get current font color or color set at output device
+ aNewColor = pFont ? pFont->GetColor() : GetOut().GetFont().GetColor();
+ if ( aNewColor != GetOut().GetLineColor() )
+ GetOut().SetLineColor( aNewColor );
+ if ( aNewColor != GetOut().GetOverlineColor() )
+ GetOut().SetOverlineColor( aNewColor );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/fntcap.cxx b/sw/source/core/txtnode/fntcap.cxx
new file mode 100644
index 0000000000..c4802af5a0
--- /dev/null
+++ b/sw/source/core/txtnode/fntcap.cxx
@@ -0,0 +1,782 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <editeng/svxfont.hxx>
+
+#include <vcl/outdev.hxx>
+#include <com/sun/star/i18n/CharType.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+
+#include <fntcache.hxx>
+#include <swfont.hxx>
+#include <breakit.hxx>
+#include <txtfrm.hxx>
+#include <scriptinfo.hxx>
+#include <fntcap.hxx>
+
+using namespace ::com::sun::star::i18n;
+
+namespace {
+
+// The information encapsulated in SwCapitalInfo is required
+// by the ::Do functions. They contain the information about
+// the original string, whereas rDo.GetInf() contains information
+// about the display string.
+class SwCapitalInfo
+{
+public:
+ explicit SwCapitalInfo( const OUString& rOrigText ) :
+ rString( rOrigText ), nIdx( 0 ), nLen( 0 ) {};
+ const OUString& rString;
+ TextFrameIndex nIdx;
+ TextFrameIndex nLen;
+};
+
+}
+
+// rFnt: required for CalcCaseMap
+// rOrigString: The original string
+// nOfst: Position of the substring in rOrigString
+// nLen: Length if the substring in rOrigString
+// nIdx: Refers to a position in the display string and should be mapped
+// to a position in rOrigString
+TextFrameIndex sw_CalcCaseMap(const SwFont& rFnt,
+ const OUString& rOrigString,
+ TextFrameIndex const nOfst,
+ TextFrameIndex const nLen,
+ TextFrameIndex const nIdx)
+{
+ int j = 0;
+ const TextFrameIndex nEnd = nOfst + nLen;
+ OSL_ENSURE( sal_Int32(nEnd) <= rOrigString.getLength(), "sw_CalcCaseMap: Wrong parameters" );
+
+ // special case for title case:
+ const bool bTitle = SvxCaseMap::Capitalize == rFnt.GetCaseMap();
+ for (TextFrameIndex i = nOfst; i < nEnd; ++i)
+ {
+ OUString aTmp(rOrigString.copy(sal_Int32(i), 1));
+
+ if ( !bTitle ||
+ g_pBreakIt->GetBreakIter()->isBeginWord(
+ rOrigString, sal_Int32(i),
+ g_pBreakIt->GetLocale( rFnt.GetLanguage() ),
+ WordType::ANYWORD_IGNOREWHITESPACES ) )
+ aTmp = rFnt.GetActualFont().CalcCaseMap( aTmp );
+
+ j += aTmp.getLength();
+
+ if (TextFrameIndex(j) > nIdx)
+ return i;
+ }
+
+ return nOfst + nLen;
+}
+
+class SwDoCapitals
+{
+protected:
+ SwDrawTextInfo &m_rInf;
+ SwCapitalInfo* m_pCapInf; // refers to additional information
+ // required by the ::Do function
+ explicit SwDoCapitals ( SwDrawTextInfo &rInfo ) : m_rInf( rInfo ), m_pCapInf( nullptr ) { }
+ ~SwDoCapitals() {}
+public:
+ virtual void Init( SwFntObj *pUpperFont, SwFntObj *pLowerFont ) = 0;
+ virtual void Do() = 0;
+ OutputDevice& GetOut() { return m_rInf.GetOut(); }
+ SwDrawTextInfo& GetInf() { return m_rInf; }
+ SwCapitalInfo* GetCapInf() const { return m_pCapInf; }
+ void SetCapInf( SwCapitalInfo& rNew ) { m_pCapInf = &rNew; }
+};
+
+namespace {
+
+class SwDoGetCapitalSize : public SwDoCapitals
+{
+protected:
+ Size m_aTextSize;
+
+public:
+ explicit SwDoGetCapitalSize( SwDrawTextInfo &rInfo ) : SwDoCapitals ( rInfo ) { }
+ virtual ~SwDoGetCapitalSize() {}
+ virtual void Init( SwFntObj *pUpperFont, SwFntObj *pLowerFont ) override;
+ virtual void Do() override;
+ const Size& GetSize() const { return m_aTextSize; }
+};
+
+}
+
+void SwDoGetCapitalSize::Init( SwFntObj *, SwFntObj * )
+{
+ m_aTextSize.setHeight(0);
+ m_aTextSize.setWidth(0);
+}
+
+void SwDoGetCapitalSize::Do()
+{
+ m_aTextSize.AdjustWidth(m_rInf.GetSize().Width());
+ if( m_rInf.GetUpper() )
+ m_aTextSize.setHeight(m_rInf.GetSize().Height());
+}
+
+Size SwSubFont::GetCapitalSize( SwDrawTextInfo& rInf )
+{
+ // Start:
+ const tools::Long nOldKern = rInf.GetKern();
+ rInf.SetKern( CheckKerning() );
+ rInf.SetPos( Point() );
+ rInf.SetSpace( 0 );
+ rInf.SetDrawSpace( false );
+ SwDoGetCapitalSize aDo( rInf );
+ DoOnCapitals( aDo );
+ Size aTextSize( aDo.GetSize() );
+
+ // End:
+ if( !aTextSize.Height() )
+ {
+ SV_STAT( nGetTextSize );
+ aTextSize.setHeight( short ( rInf.GetpOut()->GetTextHeight() ) );
+ }
+ rInf.SetKern( nOldKern );
+ return aTextSize;
+}
+
+namespace {
+
+class SwDoGetCapitalBreak : public SwDoCapitals
+{
+protected:
+ tools::Long m_nTextWidth;
+ TextFrameIndex m_nBreak;
+
+public:
+ SwDoGetCapitalBreak(SwDrawTextInfo& rInfo, tools::Long const nWidth)
+ : SwDoCapitals(rInfo)
+ , m_nTextWidth(nWidth)
+ , m_nBreak(-1)
+ { }
+ virtual ~SwDoGetCapitalBreak() {}
+ virtual void Init( SwFntObj *pUpperFont, SwFntObj *pLowerFont ) override;
+ virtual void Do() override;
+ TextFrameIndex getBreak() const { return m_nBreak; }
+};
+
+}
+
+void SwDoGetCapitalBreak::Init( SwFntObj *, SwFntObj * )
+{
+}
+
+void SwDoGetCapitalBreak::Do()
+{
+ if (!m_nTextWidth)
+ return;
+
+ if (m_rInf.GetSize().Width() < m_nTextWidth)
+ m_nTextWidth -= m_rInf.GetSize().Width();
+ else
+ {
+ TextFrameIndex nEnd = m_rInf.GetEnd();
+ m_nBreak = TextFrameIndex(GetOut().GetTextBreak(m_rInf.GetText(), m_nTextWidth,
+ sal_Int32(m_rInf.GetIdx()),
+ sal_Int32(m_rInf.GetLen()), m_rInf.GetKern()));
+
+ if (m_nBreak > nEnd || m_nBreak < TextFrameIndex(0))
+ m_nBreak = nEnd;
+
+ // m_nBreak may be relative to the display string. It has to be
+ // calculated relative to the original string:
+ if ( GetCapInf() )
+ {
+ if ( GetCapInf()->nLen != m_rInf.GetLen() )
+ m_nBreak = sw_CalcCaseMap( *m_rInf.GetFont(),
+ GetCapInf()->rString,
+ GetCapInf()->nIdx,
+ GetCapInf()->nLen, m_nBreak );
+ else
+ m_nBreak = m_nBreak + GetCapInf()->nIdx;
+ }
+
+ m_nTextWidth = 0;
+ }
+}
+
+TextFrameIndex SwFont::GetCapitalBreak( SwViewShell const * pSh, const OutputDevice* pOut,
+ const SwScriptInfo* pScript, const OUString& rText, tools::Long const nTextWidth,
+ TextFrameIndex const nIdx, TextFrameIndex const nLen)
+{
+ // Start:
+ Point aPos( 0, 0 );
+ SwDrawTextInfo aInfo(pSh, *const_cast<OutputDevice*>(pOut), pScript, rText, nIdx, nLen,
+ 0, false);
+ aInfo.SetPos( aPos );
+ aInfo.SetSpace( 0 );
+ aInfo.SetWrong( nullptr );
+ aInfo.SetGrammarCheck( nullptr );
+ aInfo.SetSmartTags( nullptr );
+ aInfo.SetDrawSpace( false );
+ aInfo.SetKern( CheckKerning() );
+ aInfo.SetKanaComp( pScript ? 0 : 100 );
+ aInfo.SetFont( this );
+
+ SwDoGetCapitalBreak aDo(aInfo, nTextWidth);
+ DoOnCapitals( aDo );
+ return aDo.getBreak();
+}
+
+namespace {
+
+class SwDoDrawCapital : public SwDoCapitals
+{
+protected:
+ SwFntObj* m_pUpperFnt;
+ SwFntObj* m_pLowerFnt;
+
+public:
+ explicit SwDoDrawCapital(SwDrawTextInfo& rInfo)
+ : SwDoCapitals(rInfo)
+ , m_pUpperFnt(nullptr)
+ , m_pLowerFnt(nullptr)
+ { }
+ virtual ~SwDoDrawCapital() {}
+ virtual void Init( SwFntObj *pUpperFont, SwFntObj *pLowerFont ) override;
+ virtual void Do() override;
+ void DrawSpace( Point &rPos );
+};
+
+}
+
+void SwDoDrawCapital::Init( SwFntObj *pUpperFont, SwFntObj *pLowerFont )
+{
+ m_pUpperFnt = pUpperFont;
+ m_pLowerFnt = pLowerFont;
+}
+
+void SwDoDrawCapital::Do()
+{
+ SV_STAT( nDrawText );
+ const sal_uInt16 nOrgWidth = m_rInf.GetWidth();
+ m_rInf.SetWidth( sal_uInt16(m_rInf.GetSize().Width()) );
+ if ( m_rInf.GetUpper() )
+ m_pUpperFnt->DrawText(m_rInf);
+ else
+ {
+ bool bOldBullet = m_rInf.GetBullet();
+ m_rInf.SetBullet( false );
+ m_pLowerFnt->DrawText(m_rInf);
+ m_rInf.SetBullet( bOldBullet );
+ }
+
+ OSL_ENSURE(m_pUpperFnt, "No upper font, dying soon!");
+ m_rInf.Shift(m_pUpperFnt->GetFont().GetOrientation());
+ m_rInf.SetWidth( nOrgWidth );
+}
+
+void SwDoDrawCapital::DrawSpace( Point &rPos )
+{
+ tools::Long nDiff = m_rInf.GetPos().X() - rPos.X();
+
+ Point aPos( rPos );
+ const bool bSwitchL2R = m_rInf.GetFrame()->IsRightToLeft() &&
+ ! m_rInf.IsIgnoreFrameRTL();
+
+ if ( bSwitchL2R )
+ m_rInf.GetFrame()->SwitchLTRtoRTL( aPos );
+
+ const vcl::text::ComplexTextLayoutFlags nMode = m_rInf.GetpOut()->GetLayoutMode();
+ const bool bBidiPor = ( bSwitchL2R !=
+ ( vcl::text::ComplexTextLayoutFlags::Default != ( vcl::text::ComplexTextLayoutFlags::BiDiRtl & nMode ) ) );
+
+ if ( bBidiPor )
+ nDiff = -nDiff;
+
+ if ( m_rInf.GetFrame()->IsVertical() )
+ m_rInf.GetFrame()->SwitchHorizontalToVertical( aPos );
+
+ if ( nDiff )
+ {
+ m_rInf.ApplyAutoColor();
+ GetOut().DrawStretchText( aPos, nDiff,
+ " ", 0, 2 );
+ }
+ rPos.setX( m_rInf.GetPos().X() + m_rInf.GetWidth() );
+}
+
+void SwSubFont::DrawCapital( SwDrawTextInfo &rInf )
+{
+ // Precondition: rInf.GetPos() has already been calculated
+
+ rInf.SetDrawSpace( GetUnderline() != LINESTYLE_NONE ||
+ GetOverline() != LINESTYLE_NONE ||
+ GetStrikeout() != STRIKEOUT_NONE );
+ SwDoDrawCapital aDo( rInf );
+ DoOnCapitals( aDo );
+}
+
+namespace {
+
+class SwDoCapitalCursorOfst : public SwDoCapitals
+{
+protected:
+ SwFntObj* m_pUpperFnt;
+ SwFntObj* m_pLowerFnt;
+ TextFrameIndex m_nCursor;
+ sal_uInt16 m_nOfst;
+
+public:
+ SwDoCapitalCursorOfst(SwDrawTextInfo& rInfo, const sal_uInt16 nOfs)
+ : SwDoCapitals(rInfo)
+ , m_pUpperFnt(nullptr)
+ , m_pLowerFnt(nullptr)
+ , m_nCursor(0)
+ , m_nOfst(nOfs)
+ { }
+ virtual ~SwDoCapitalCursorOfst() {}
+ virtual void Init( SwFntObj *pUpperFont, SwFntObj *pLowerFont ) override;
+ virtual void Do() override;
+
+ TextFrameIndex GetCursor() const { return m_nCursor; }
+};
+
+}
+
+void SwDoCapitalCursorOfst::Init( SwFntObj *pUpperFont, SwFntObj *pLowerFont )
+{
+ m_pUpperFnt = pUpperFont;
+ m_pLowerFnt = pLowerFont;
+}
+
+void SwDoCapitalCursorOfst::Do()
+{
+ if (!m_nOfst)
+ return;
+
+ if (static_cast<tools::Long>(m_nOfst) > m_rInf.GetSize().Width())
+ {
+ m_nOfst -= m_rInf.GetSize().Width();
+ m_nCursor = m_nCursor + m_rInf.GetLen();
+ }
+ else
+ {
+ SwDrawTextInfo aDrawInf( m_rInf.GetShell(), *m_rInf.GetpOut(),
+ m_rInf.GetScriptInfo(),
+ m_rInf.GetText(),
+ m_rInf.GetIdx(),
+ m_rInf.GetLen(), 0, false );
+ aDrawInf.SetOffset(m_nOfst);
+ aDrawInf.SetKern( m_rInf.GetKern() );
+ aDrawInf.SetKanaComp( m_rInf.GetKanaComp() );
+ aDrawInf.SetFrame( m_rInf.GetFrame() );
+ aDrawInf.SetFont( m_rInf.GetFont() );
+
+ if ( m_rInf.GetUpper() )
+ {
+ aDrawInf.SetSpace( 0 );
+ m_nCursor = m_nCursor + m_pUpperFnt->GetModelPositionForViewPoint(aDrawInf);
+ }
+ else
+ {
+ aDrawInf.SetSpace( m_rInf.GetSpace() );
+ m_nCursor = m_nCursor + m_pLowerFnt->GetModelPositionForViewPoint(aDrawInf);
+ }
+ m_nOfst = 0;
+ }
+}
+
+TextFrameIndex SwSubFont::GetCapitalCursorOfst( SwDrawTextInfo& rInf )
+{
+ const tools::Long nOldKern = rInf.GetKern();
+ rInf.SetKern( CheckKerning() );
+ SwDoCapitalCursorOfst aDo( rInf, rInf.GetOffset() );
+ rInf.SetPos( Point() );
+ rInf.SetDrawSpace( false );
+ DoOnCapitals( aDo );
+ rInf.SetKern( nOldKern );
+ return aDo.GetCursor();
+}
+
+namespace {
+
+class SwDoDrawStretchCapital : public SwDoDrawCapital
+{
+ const TextFrameIndex m_nStrLen;
+ const sal_uInt16 m_nCapWidth;
+ const sal_uInt16 m_nOrgWidth;
+
+public:
+ virtual void Do() override;
+
+ SwDoDrawStretchCapital(SwDrawTextInfo& rInfo, const sal_uInt16 nCapitalWidth)
+ : SwDoDrawCapital(rInfo)
+ , m_nStrLen(rInfo.GetLen())
+ , m_nCapWidth(nCapitalWidth)
+ , m_nOrgWidth(rInfo.GetWidth())
+ { }
+};
+
+}
+
+void SwDoDrawStretchCapital::Do()
+{
+ SV_STAT( nDrawStretchText );
+ tools::Long nPartWidth = m_rInf.GetSize().Width();
+
+ if( m_rInf.GetLen() )
+ {
+ // small caps and kerning
+ tools::Long nDiff = tools::Long(m_nOrgWidth) - tools::Long(m_nCapWidth);
+ if( nDiff )
+ {
+ nDiff *= sal_Int32(m_rInf.GetLen());
+ nDiff /= sal_Int32(m_nStrLen);
+ nDiff += nPartWidth;
+ if( 0 < nDiff )
+ nPartWidth = nDiff;
+ }
+
+ m_rInf.ApplyAutoColor();
+
+ Point aPos( m_rInf.GetPos() );
+ const bool bSwitchL2R = m_rInf.GetFrame()->IsRightToLeft() &&
+ ! m_rInf.IsIgnoreFrameRTL();
+
+ if ( bSwitchL2R )
+ m_rInf.GetFrame()->SwitchLTRtoRTL( aPos );
+
+ if ( m_rInf.GetFrame()->IsVertical() )
+ m_rInf.GetFrame()->SwitchHorizontalToVertical( aPos );
+
+ // Optimise:
+ if (TextFrameIndex(1) >= m_rInf.GetLen())
+ GetOut().DrawText(aPos, m_rInf.GetText(), sal_Int32(m_rInf.GetIdx()),
+ sal_Int32(m_rInf.GetLen()));
+ else
+ GetOut().DrawStretchText(aPos, nPartWidth, m_rInf.GetText(),
+ sal_Int32(m_rInf.GetIdx()), sal_Int32(m_rInf.GetLen()));
+ }
+ const_cast<Point&>(m_rInf.GetPos()).AdjustX(nPartWidth );
+}
+
+void SwSubFont::DrawStretchCapital( SwDrawTextInfo &rInf )
+{
+ // Precondition: rInf.GetPos() has already been calculated
+
+ if (rInf.GetLen() == TextFrameIndex(COMPLETE_STRING))
+ rInf.SetLen(TextFrameIndex(rInf.GetText().getLength()));
+
+ const Point aOldPos = rInf.GetPos();
+ const sal_uInt16 nCapWidth = o3tl::narrowing<sal_uInt16>( GetCapitalSize( rInf ).Width() );
+ rInf.SetPos(aOldPos);
+
+ rInf.SetDrawSpace( GetUnderline() != LINESTYLE_NONE ||
+ GetOverline() != LINESTYLE_NONE ||
+ GetStrikeout() != STRIKEOUT_NONE );
+ SwDoDrawStretchCapital aDo( rInf, nCapWidth );
+ DoOnCapitals( aDo );
+}
+
+void SwSubFont::DoOnCapitals( SwDoCapitals &rDo )
+{
+ OSL_ENSURE( pLastFont, "SwFont::DoOnCapitals: No LastFont?!" );
+
+ tools::Long nKana = 0;
+ const OUString aText( CalcCaseMap( rDo.GetInf().GetText() ) );
+ TextFrameIndex nMaxPos = std::min(
+ TextFrameIndex(rDo.GetInf().GetText().getLength()) - rDo.GetInf().GetIdx(),
+ rDo.GetInf().GetLen() );
+ rDo.GetInf().SetLen( nMaxPos );
+
+ const OUString oldText = rDo.GetInf().GetText();
+ rDo.GetInf().SetText( aText );
+ TextFrameIndex nPos = rDo.GetInf().GetIdx();
+ TextFrameIndex nOldPos = nPos;
+ nMaxPos = nMaxPos + nPos;
+
+ // Look if the length of the original text and the ToUpper-converted
+ // text is different. If yes, do special handling.
+ SwCapitalInfo aCapInf(oldText);
+ bool bCaseMapLengthDiffers(aText.getLength() != oldText.getLength());
+ if ( bCaseMapLengthDiffers )
+ rDo.SetCapInf( aCapInf );
+
+ SwFntObj *pOldLast = pLastFont;
+ std::optional<SwFntAccess> oBigFontAccess;
+ SwFntObj *pBigFont;
+ std::optional<SwFntAccess> oSpaceFontAccess;
+ SwFntObj *pSpaceFont = nullptr;
+
+ const void* nFontCacheId2 = nullptr;
+ sal_uInt16 nIndex2 = 0;
+ SwSubFont aFont( *this );
+ Point aStartPos( rDo.GetInf().GetPos() );
+
+ const bool bTextLines = aFont.GetUnderline() != LINESTYLE_NONE
+ || aFont.GetOverline() != LINESTYLE_NONE
+ || aFont.GetStrikeout() != STRIKEOUT_NONE;
+ const bool bWordWise = bTextLines && aFont.IsWordLineMode() &&
+ rDo.GetInf().GetDrawSpace();
+ const tools::Long nTmpKern = rDo.GetInf().GetKern();
+
+ if ( bTextLines )
+ {
+ if ( bWordWise )
+ {
+ aFont.SetWordLineMode( false );
+ oSpaceFontAccess.emplace( nFontCacheId2, nIndex2, &aFont,
+ rDo.GetInf().GetShell() );
+ pSpaceFont = oSpaceFontAccess->Get();
+ }
+ else
+ pSpaceFont = pLastFont;
+
+ // Construct a font for the capitals:
+ aFont.SetUnderline( LINESTYLE_NONE );
+ aFont.SetOverline( LINESTYLE_NONE );
+ aFont.SetStrikeout( STRIKEOUT_NONE );
+ nFontCacheId2 = nullptr;
+ nIndex2 = 0;
+ oBigFontAccess.emplace( nFontCacheId2, nIndex2, &aFont,
+ rDo.GetInf().GetShell() );
+ pBigFont = oBigFontAccess->Get();
+ }
+ else
+ pBigFont = pLastFont;
+
+ // Older LO versions had 66 as the small caps percentage size, later changed to 80,
+ // therefore a backwards compatibility option is kept (otherwise layout is changed).
+ // NOTE: There are more uses of SMALL_CAPS_PERCENTAGE in editeng, but it seems they
+ // do not matter for Writer (and if they did it'd be pretty ugly to propagate
+ // the option there).
+ int smallCapsPercentage = m_bSmallCapsPercentage66 ? 66 : SMALL_CAPS_PERCENTAGE;
+ aFont.SetProportion( (aFont.GetPropr() * smallCapsPercentage ) / 100 );
+ nFontCacheId2 = nullptr;
+ nIndex2 = 0;
+ std::optional<SwFntAccess> oSmallFontAccess( std::in_place, nFontCacheId2, nIndex2, &aFont,
+ rDo.GetInf().GetShell() );
+ SwFntObj *pSmallFont = oSmallFontAccess->Get();
+
+ rDo.Init( pBigFont, pSmallFont );
+ OutputDevice* pOutSize = pSmallFont->GetPrt();
+ if( !pOutSize )
+ pOutSize = &rDo.GetOut();
+ OutputDevice* pOldOut = &rDo.GetOut();
+
+ const LanguageType eLng = LANGUAGE_DONTKNOW == GetLanguage()
+ ? LANGUAGE_SYSTEM : GetLanguage();
+
+ if( nPos < nMaxPos )
+ {
+ nPos = TextFrameIndex(g_pBreakIt->GetBreakIter()->endOfCharBlock(
+ oldText, sal_Int32(nPos),
+ g_pBreakIt->GetLocale( eLng ), CharType::LOWERCASE_LETTER));
+ if (nPos < TextFrameIndex(0))
+ nPos = nOldPos;
+ else if( nPos > nMaxPos )
+ nPos = nMaxPos;
+ }
+
+ while( nOldPos < nMaxPos )
+ {
+
+ // The lower ones...
+ if( nOldPos != nPos )
+ {
+ SV_STAT( nGetTextSize );
+ pLastFont = pSmallFont;
+ pLastFont->SetDevFont( rDo.GetInf().GetShell(), rDo.GetOut() );
+
+ // #107816#, #i14820#
+ if( bCaseMapLengthDiffers )
+ {
+ // Build an own 'changed' string for the given part of the
+ // source string and use it. That new string may differ in length
+ // from the source string.
+ const OUString aNewText = CalcCaseMap(
+ oldText.copy(sal_Int32(nOldPos), sal_Int32(nPos-nOldPos)));
+ aCapInf.nIdx = nOldPos;
+ aCapInf.nLen = nPos - nOldPos;
+ rDo.GetInf().SetTextIdxLen(aNewText, TextFrameIndex(0), TextFrameIndex(aNewText.getLength()));
+ }
+ else
+ {
+ rDo.GetInf().SetIdxLen(nOldPos, nPos - nOldPos);
+ }
+
+ rDo.GetInf().SetUpper( false );
+ rDo.GetInf().SetOut( *pOutSize );
+ Size aPartSize = pSmallFont->GetTextSize( rDo.GetInf() );
+ nKana += rDo.GetInf().GetKanaDiff();
+ rDo.GetInf().SetOut( *pOldOut );
+ if( nTmpKern && nPos < nMaxPos )
+ aPartSize.AdjustWidth(nTmpKern );
+ rDo.GetInf().SetSize( aPartSize );
+ rDo.Do();
+ nOldPos = nPos;
+ }
+ nPos = TextFrameIndex(g_pBreakIt->GetBreakIter()->nextCharBlock(
+ oldText, sal_Int32(nPos),
+ g_pBreakIt->GetLocale( eLng ), CharType::LOWERCASE_LETTER));
+ if (nPos < TextFrameIndex(0) || nPos > nMaxPos)
+ nPos = nMaxPos;
+ OSL_ENSURE( nPos, "nextCharBlock not implemented?" );
+#if OSL_DEBUG_LEVEL > 1
+ if( !nPos )
+ nPos = nMaxPos;
+#endif
+ // The upper ones...
+ if( nOldPos != nPos )
+ {
+ const tools::Long nSpaceAdd = rDo.GetInf().GetSpace() / SPACING_PRECISION_FACTOR;
+
+ do
+ {
+ rDo.GetInf().SetUpper( true );
+ pLastFont = pBigFont;
+ pLastFont->SetDevFont( rDo.GetInf().GetShell(), rDo.GetOut() );
+ TextFrameIndex nTmp;
+ if( bWordWise )
+ {
+ nTmp = nOldPos;
+ while (nTmp < nPos && CH_BLANK == oldText[sal_Int32(nTmp)])
+ ++nTmp;
+ if( nOldPos < nTmp )
+ {
+ pLastFont = pSpaceFont;
+ pLastFont->SetDevFont( rDo.GetInf().GetShell(),
+ rDo.GetOut() );
+ static_cast<SwDoDrawCapital&>(rDo).DrawSpace( aStartPos );
+ pLastFont = pBigFont;
+ pLastFont->SetDevFont( rDo.GetInf().GetShell(),
+ rDo.GetOut() );
+
+ // #107816#, #i14820#
+ if( bCaseMapLengthDiffers )
+ {
+ // Build an own 'changed' string for the given part of the
+ // source string and use it. That new string may differ in length
+ // from the source string.
+ const OUString aNewText = CalcCaseMap(
+ oldText.copy(sal_Int32(nOldPos), sal_Int32(nTmp-nOldPos)));
+ aCapInf.nIdx = nOldPos;
+ aCapInf.nLen = nTmp - nOldPos;
+ rDo.GetInf().SetIdx(TextFrameIndex(0));
+ rDo.GetInf().SetLen(TextFrameIndex(aNewText.getLength()));
+ rDo.GetInf().SetText( aNewText );
+ }
+ else
+ {
+ rDo.GetInf().SetIdx( nOldPos );
+ rDo.GetInf().SetLen( nTmp - nOldPos );
+ }
+
+ rDo.GetInf().SetOut( *pOutSize );
+ Size aPartSize = pBigFont->GetTextSize( rDo.GetInf() );
+ nKana += rDo.GetInf().GetKanaDiff();
+ rDo.GetInf().SetOut( *pOldOut );
+ if( nSpaceAdd )
+ aPartSize.AdjustWidth(nSpaceAdd * sal_Int32(nTmp - nOldPos));
+ if( nTmpKern && nPos < nMaxPos )
+ aPartSize.AdjustWidth(nTmpKern );
+ rDo.GetInf().SetSize( aPartSize );
+ rDo.Do();
+ aStartPos = rDo.GetInf().GetPos();
+ nOldPos = nTmp;
+ }
+
+ while (nTmp < nPos && CH_BLANK != oldText[sal_Int32(nTmp)])
+ ++nTmp;
+ }
+ else
+ nTmp = nPos;
+ if( nTmp > nOldPos )
+ {
+ // #107816#, #i14820#
+ if( bCaseMapLengthDiffers )
+ {
+ // Build an own 'changed' string for the given part of the
+ // source string and use it. That new string may differ in length
+ // from the source string.
+ const OUString aNewText = CalcCaseMap(
+ oldText.copy(sal_Int32(nOldPos), sal_Int32(nTmp-nOldPos)));
+ aCapInf.nIdx = nOldPos;
+ aCapInf.nLen = nTmp - nOldPos;
+ rDo.GetInf().SetTextIdxLen( aNewText, TextFrameIndex(0), TextFrameIndex(aNewText.getLength()));
+ }
+ else
+ {
+ rDo.GetInf().SetIdxLen( nOldPos, nTmp - nOldPos );
+ }
+
+ rDo.GetInf().SetOut( *pOutSize );
+ Size aPartSize = pBigFont->GetTextSize( rDo.GetInf() );
+ nKana += rDo.GetInf().GetKanaDiff();
+ rDo.GetInf().SetOut( *pOldOut );
+ if( !bWordWise && rDo.GetInf().GetSpace() )
+ {
+ for (TextFrameIndex nI = nOldPos; nI < nPos; ++nI)
+ {
+ if (CH_BLANK == oldText[sal_Int32(nI)])
+ aPartSize.AdjustWidth(nSpaceAdd );
+ }
+ }
+ if( nTmpKern && nPos < nMaxPos )
+ aPartSize.AdjustWidth(nTmpKern );
+ rDo.GetInf().SetSize( aPartSize );
+ rDo.Do();
+ nOldPos = nTmp;
+ }
+ } while( nOldPos != nPos );
+ }
+ nPos = TextFrameIndex(g_pBreakIt->GetBreakIter()->endOfCharBlock(
+ oldText, sal_Int32(nPos),
+ g_pBreakIt->GetLocale( eLng ), CharType::LOWERCASE_LETTER));
+ if (nPos < TextFrameIndex(0) || nPos > nMaxPos)
+ nPos = nMaxPos;
+ OSL_ENSURE( nPos, "endOfCharBlock not implemented?" );
+#if OSL_DEBUG_LEVEL > 1
+ if( !nPos )
+ nPos = nMaxPos;
+#endif
+ }
+
+ // clean up:
+ if( pBigFont != pOldLast )
+ oBigFontAccess.reset();
+
+ if( bTextLines )
+ {
+ if( rDo.GetInf().GetDrawSpace() )
+ {
+ pLastFont = pSpaceFont;
+ pLastFont->SetDevFont( rDo.GetInf().GetShell(), rDo.GetOut() );
+ static_cast<SwDoDrawCapital&>( rDo ).DrawSpace( aStartPos );
+ }
+ if ( bWordWise )
+ oSpaceFontAccess.reset();
+ }
+ pLastFont = pOldLast;
+ pLastFont->SetDevFont( rDo.GetInf().GetShell(), rDo.GetOut() );
+
+ oSmallFontAccess.reset();
+ rDo.GetInf().SetText(oldText);
+ rDo.GetInf().SetKanaDiff( nKana );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/justify.cxx b/sw/source/core/txtnode/justify.cxx
new file mode 100644
index 0000000000..40ded5663f
--- /dev/null
+++ b/sw/source/core/txtnode/justify.cxx
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <vector>
+#include <sal/types.h>
+#include <vcl/kernarray.hxx>
+#include <swfont.hxx>
+#include "justify.hxx"
+
+namespace
+{
+enum class IdeographicPunctuationClass
+{
+ NONE,
+ OPEN_BRACKET,
+ CLOSE_BRACKET,
+ COMMA_OR_FULLSTOP
+};
+
+IdeographicPunctuationClass lcl_WhichPunctuationClass(sal_Unicode cChar)
+{
+ if ((cChar < 0x3001 || cChar > 0x3002) && (cChar < 0x3008 || cChar > 0x3011)
+ && (cChar < 0x3014 || cChar > 0x301F) && 0xFF62 != cChar && 0xFF63 != cChar)
+ return IdeographicPunctuationClass::NONE;
+ else if (0x3001 == cChar || 0x3002 == cChar)
+ return IdeographicPunctuationClass::COMMA_OR_FULLSTOP;
+ else if (0x3009 == cChar || 0x300B == cChar || 0x300D == cChar || 0x300F == cChar
+ || 0x3011 == cChar || 0x3015 == cChar || 0x3017 == cChar || 0x3019 == cChar
+ || 0x301B == cChar || 0x301E == cChar || 0x301F == cChar || 0xFF63 == cChar)
+ // right punctuation
+ return IdeographicPunctuationClass::CLOSE_BRACKET;
+
+ return IdeographicPunctuationClass::OPEN_BRACKET;
+}
+
+tools::Long lcl_MinGridWidth(tools::Long nGridWidth, tools::Long nCharWidth)
+{
+ tools::Long nCount = nCharWidth > nGridWidth ? (nCharWidth - 1) / nGridWidth + 1 : 1;
+ return nCount * nGridWidth;
+}
+
+tools::Long lcl_OffsetFromGridEdge(tools::Long nMinWidth, tools::Long nCharWidth, sal_Unicode cChar,
+ bool bForceLeft)
+{
+ if (bForceLeft)
+ return 0;
+
+ tools::Long nOffset = 0;
+
+ switch (lcl_WhichPunctuationClass(cChar))
+ {
+ case IdeographicPunctuationClass::NONE:
+ // Centered
+ nOffset = (nMinWidth - nCharWidth) / 2;
+ break;
+ case IdeographicPunctuationClass::OPEN_BRACKET:
+ // Align to next edge, closer to next ideograph
+ nOffset = nMinWidth - nCharWidth;
+ break;
+ default:
+ // CLOSE_BRACKET or COMMA_OR_FULLSTOP:
+ // Align to previous edge, closer to previous ideograph.
+ break;
+ }
+ return nOffset;
+}
+}
+
+namespace sw::Justify
+{
+sal_Int32 GetModelPosition(const KernArray& rKernArray, sal_Int32 nLen, tools::Long nX)
+{
+ tools::Long nLeft = 0, nRight = 0;
+ sal_Int32 nLast = 0, nIdx = 0;
+
+ do
+ {
+ nRight = rKernArray[nLast];
+ ++nIdx;
+ while (nIdx < nLen && rKernArray[nIdx] == rKernArray[nLast])
+ ++nIdx;
+
+ if (nIdx < nLen)
+ {
+ if (nX < nRight)
+ return (nX - nLeft < nRight - nX) ? nLast : nIdx;
+
+ nLeft = nRight;
+ nLast = nIdx;
+ }
+ } while (nIdx < nLen);
+ return nIdx;
+}
+
+void SpaceDistribution(KernArray& rKernArray, std::u16string_view aText, sal_Int32 nStt,
+ sal_Int32 nLen, tools::Long nSpaceAdd, tools::Long nKern, bool bNoHalfSpace)
+{
+ assert(nStt + nLen <= sal_Int32(aText.size()));
+ assert(nLen <= sal_Int32(rKernArray.size()));
+ // nSpaceSum contains the sum of the intermediate space distributed
+ // among Spaces by the Justification.
+ // The Spaces themselves will be positioned in the middle of the
+ // intermediate space, hence the nSpace/2.
+ // In case of word-by-word underlining they have to be positioned
+ // at the beginning of the intermediate space, so that the space
+ // is not underlined.
+ // A Space at the beginning or end of the text must be positioned
+ // before (resp. after) the whole intermediate space, otherwise
+ // the underline/strike-through would have gaps.
+ tools::Long nSpaceSum = 0;
+ // in word line mode and for Arabic, we disable the half space trick:
+ const tools::Long nHalfSpace = bNoHalfSpace ? 0 : nSpaceAdd / 2;
+ const tools::Long nOtherHalf = nSpaceAdd - nHalfSpace;
+ tools::Long nKernSum = nKern;
+ sal_Unicode cChPrev = aText[nStt];
+
+ if (nSpaceAdd && (cChPrev == CH_BLANK))
+ nSpaceSum = nHalfSpace;
+
+ sal_Int32 nPrevIdx = 0;
+
+ for (sal_Int32 i = 1; i < nLen; ++i, nKernSum += nKern)
+ {
+ // Find the beginning of the next cluster that has a different kern value.
+ while (i < nLen && rKernArray[i] == rKernArray[nPrevIdx])
+ ++i;
+
+ if (i == nLen)
+ break;
+
+ sal_Unicode nCh = aText[nStt + i];
+
+ // Apply SpaceSum
+ if (cChPrev == CH_BLANK)
+ {
+ // no Pixel is lost:
+ nSpaceSum += nOtherHalf;
+ }
+
+ if (nCh == CH_BLANK)
+ {
+ if (i + 1 == nLen)
+ nSpaceSum += nSpaceAdd;
+ else
+ nSpaceSum += nHalfSpace;
+ }
+
+ cChPrev = nCh;
+ rKernArray.adjust(nPrevIdx, nKernSum + nSpaceSum);
+ // In word line mode and for Arabic, we disabled the half space trick. If a portion
+ // ends with a blank, the full nSpaceAdd value has been added to the character in
+ // front of the blank. This leads to painting artifacts, therefore we remove the
+ // nSpaceAdd value again:
+ if (bNoHalfSpace && i + 1 == nLen && nCh == CH_BLANK)
+ rKernArray.adjust(nPrevIdx, -nSpaceAdd);
+
+ // Advance nPrevIdx and assign kern values to previous cluster.
+ for (tools::Long nValue = rKernArray[nPrevIdx++]; nPrevIdx < i; ++nPrevIdx)
+ rKernArray.set(nPrevIdx, nValue);
+ }
+
+ // the layout engine requires the total width of the output
+ while (nPrevIdx < nLen)
+ {
+ rKernArray.adjust(nPrevIdx, nKernSum + nSpaceSum);
+ ++nPrevIdx;
+ }
+}
+
+tools::Long SnapToGrid(KernArray& rKernArray, std::u16string_view aText, sal_Int32 nStt,
+ sal_Int32 nLen, tools::Long nGridWidth, bool bForceLeft)
+{
+ assert(nStt + nLen <= sal_Int32(aText.size()));
+ assert(nLen <= sal_Int32(rKernArray.size()));
+
+ tools::Long nCharWidth = rKernArray[0];
+ tools::Long nMinWidth = lcl_MinGridWidth(nGridWidth, nCharWidth);
+ tools::Long nDelta = lcl_OffsetFromGridEdge(nMinWidth, nCharWidth, aText[nStt], bForceLeft);
+ tools::Long nEdge = nMinWidth - nDelta;
+
+ sal_Int32 nLast = 0;
+
+ for (sal_Int32 i = 1; i < nLen; ++i)
+ {
+ if (rKernArray[i] == rKernArray[nLast])
+ continue;
+
+ nCharWidth = rKernArray[i] - rKernArray[nLast];
+ nMinWidth = lcl_MinGridWidth(nGridWidth, nCharWidth);
+ tools::Long nX
+ = nEdge + lcl_OffsetFromGridEdge(nMinWidth, nCharWidth, aText[nStt + i], bForceLeft);
+ nEdge += nMinWidth;
+
+ while (nLast < i)
+ {
+ rKernArray.set(nLast, nX);
+ ++nLast;
+ }
+ }
+
+ while (nLast < nLen)
+ {
+ rKernArray.set(nLast, nEdge);
+ ++nLast;
+ }
+
+ return nDelta;
+}
+
+void SnapToGridEdge(KernArray& rKernArray, sal_Int32 nLen, tools::Long nGridWidth,
+ tools::Long nSpace, tools::Long nKern)
+{
+ assert(nLen <= sal_Int32(rKernArray.size()));
+
+ tools::Long nCharWidth = rKernArray[0];
+ tools::Long nEdge = lcl_MinGridWidth(nGridWidth, nCharWidth + nKern) + nSpace;
+
+ sal_Int32 nLast = 0;
+
+ for (sal_Int32 i = 1; i < nLen; ++i)
+ {
+ if (rKernArray[i] == rKernArray[nLast])
+ continue;
+
+ nCharWidth = rKernArray[i] - rKernArray[nLast];
+ tools::Long nMinWidth = lcl_MinGridWidth(nGridWidth, nCharWidth + nKern);
+ while (nLast < i)
+ {
+ rKernArray.set(nLast, nEdge);
+ ++nLast;
+ }
+
+ nEdge += nMinWidth + nSpace;
+ }
+
+ while (nLast < nLen)
+ {
+ rKernArray.set(nLast, nEdge);
+ ++nLast;
+ }
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/justify.hxx b/sw/source/core/txtnode/justify.hxx
new file mode 100644
index 0000000000..454b6c2590
--- /dev/null
+++ b/sw/source/core/txtnode/justify.hxx
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+#include <sal/types.h>
+
+namespace sw::Justify
+{
+/// Get model position base on given kern array.
+/// @param rKernArray text positions from OutDev::GetTextArray().
+/// @param nLen number of elements to process in rKernArray.
+/// @param nX the visual position
+SW_DLLPUBLIC sal_Int32 GetModelPosition(const KernArray& rKernArray, sal_Int32 nLen,
+ tools::Long nX);
+/// Distribute space between words and letters.
+/// @param[in,out] rKernArray text positions from OutDev::GetTextArray().
+/// @param aText string used to determine where space and kern are inserted.
+/// @param nStt starting index of rText.
+/// @param nLen number of elements to process in rKernArray and rText.
+/// @param nSpaceAdd amount of space to insert for each CH_BLANK.
+/// @param nKern amount of space to insert between letters.
+/// @param bNoHalfSpace whether to split the space into two halves.
+/// Split spaces are inserted before and after CH_BLANK.
+/// Set to true in word line mode and for Arabic text to avoid splitting.
+SW_DLLPUBLIC void SpaceDistribution(KernArray& rKernArray, std::u16string_view aText,
+ sal_Int32 nStt, sal_Int32 nLen, tools::Long nSpaceAdd,
+ tools::Long nKern, bool bNoHalfSpace);
+
+/// Snap ideographs to text grids:
+/// a) Ideographic open brackets are aligned to the rightmost edge of spanned grids so that
+// they can be closer to the next ideograph.
+/// b) Ideographic close brackets, ideographic comma, and ideographic fullstop are aligned
+/// to the leftmost edge of spanned grids so that they can be closer to the previous
+/// ideograph.
+/// c) Other ideographs are aligned to the center of the spanned grids.
+/// @param[in,out] rKernArray text positions from OutDev::GetTextArray().
+/// @param aText string used to determine where space and kern are inserted.
+/// @param nStt starting index of rText.
+/// @param nLen number of elements to process in rKernArray and rText.
+/// @param nGridWidth width of a text grid
+/// @param bForceLeft for align to the left edge of the grid disregard of the punctuation type.
+/// This is useful for calculate text width, line break, and conversion model position.
+/// @return the delta offset of first glyph so text origin can be updated accordingly.
+SW_DLLPUBLIC tools::Long SnapToGrid(KernArray& rKernArray, std::u16string_view aText,
+ sal_Int32 nStt, sal_Int32 nLen, tools::Long nGridWidth,
+ bool bForceLeft);
+
+/// Snap ideographs to text grids edge ( used when snap to char is off ):
+/// space will be distributed ( in case that alignment is set to justify. ).
+/// @param[in,out] rKernArray text positions from OutDev::GetTextArray().
+/// @param nLen number of elements to process in rKernArray and rText.
+/// @param nGridWidth width of a text grid
+/// @param nSpace amount of space distributed under justify text alignment mode.
+/// @param nKern letter spacing.
+SW_DLLPUBLIC void SnapToGridEdge(KernArray& rKernArray, sal_Int32 nLen, tools::Long nGridWidth,
+ tools::Long nSpace, tools::Long nKern);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/modeltoviewhelper.cxx b/sw/source/core/txtnode/modeltoviewhelper.cxx
new file mode 100644
index 0000000000..4ce9f73098
--- /dev/null
+++ b/sw/source/core/txtnode/modeltoviewhelper.cxx
@@ -0,0 +1,468 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/multisel.hxx>
+#include <doc.hxx>
+#include <IMark.hxx>
+#include <fldbas.hxx>
+#include <fmtfld.hxx>
+#include <fmtftn.hxx>
+#include <modeltoviewhelper.hxx>
+#include <ndtxt.hxx>
+#include <pam.hxx>
+#include <txatbase.hxx>
+#include <txtfld.hxx>
+#include <txtftn.hxx>
+#include <scriptinfo.hxx>
+#include <IDocumentMarkAccess.hxx>
+#include <bookmark.hxx>
+#include <o3tl/sorted_vector.hxx>
+#include <deque>
+#include <vector>
+
+namespace {
+
+struct FieldResult
+{
+ sal_Int32 m_nFieldPos;
+ OUString m_sExpand;
+ enum { NONE, FIELD, FOOTNOTE } m_eType;
+ explicit FieldResult(sal_Int32 const nPos)
+ : m_nFieldPos(nPos), m_eType(NONE)
+ { }
+};
+
+class sortfieldresults
+{
+public:
+ bool operator()(const FieldResult &rOne, const FieldResult &rTwo) const
+ {
+ return rOne.m_nFieldPos < rTwo.m_nFieldPos;
+ }
+};
+
+}
+
+namespace {
+
+struct block
+{
+ sal_Int32 m_nStart;
+ sal_Int32 m_nLen;
+ bool m_bVisible;
+ o3tl::sorted_vector<FieldResult, sortfieldresults> m_aAttrs;
+ block(sal_Int32 nStart, sal_Int32 nLen, bool bVisible)
+ : m_nStart(nStart), m_nLen(nLen), m_bVisible(bVisible)
+ {
+ }
+};
+
+struct containsPos
+{
+ const sal_Int32 m_nPos;
+ explicit containsPos(const sal_Int32 nPos)
+ : m_nPos(nPos)
+ {
+ }
+ bool operator() (const block& rIn) const
+ {
+ return m_nPos >= rIn.m_nStart && m_nPos < rIn.m_nStart + rIn.m_nLen;
+ }
+};
+
+}
+
+ModelToViewHelper::ModelToViewHelper(const SwTextNode &rNode,
+ SwRootFrame const*const pLayout, ExpandMode eMode)
+{
+ const OUString& rNodeText = rNode.GetText();
+ m_aRetText = rNodeText;
+
+ if (eMode == ExpandMode::PassThrough)
+ return;
+
+ Range aRange( 0, rNodeText.isEmpty() ? 0 : rNodeText.getLength() - 1);
+ MultiSelection aHiddenMulti(aRange);
+
+ if (eMode & ExpandMode::HideInvisible)
+ SwScriptInfo::selectHiddenTextProperty(rNode, aHiddenMulti, nullptr);
+
+ if (eMode & ExpandMode::HideDeletions)
+ SwScriptInfo::selectRedLineDeleted(rNode, aHiddenMulti);
+
+ if (eMode & ExpandMode::HideFieldmarkCommands)
+ {
+ // hide fieldmark commands
+ IDocumentMarkAccess const& rIDMA(*rNode.GetDoc().getIDocumentMarkAccess());
+ ::std::deque<::std::pair<sw::mark::IFieldmark const*, bool>> startedFields;
+ SwPaM cursor(rNode, 0);
+ while (true)
+ {
+ sw::mark::IFieldmark const* pFieldMark(nullptr);
+ while (true) // loop to skip NonTextFieldmarks, those are handled later
+ {
+ pFieldMark = rIDMA.getInnerFieldmarkFor(*cursor.GetPoint());
+ if (pFieldMark == nullptr
+ || pFieldMark->GetMarkStart().GetNode().GetTextNode()->GetText()[
+ pFieldMark->GetMarkStart().GetContentIndex()]
+ != CH_TXT_ATR_FORMELEMENT)
+ {
+ break;
+ }
+ pFieldMark = nullptr;
+ if (!cursor.Move(fnMoveBackward, GoInContent))
+ {
+ break;
+ }
+ }
+ if (!pFieldMark)
+ {
+ break;
+ }
+ assert(pFieldMark->GetMarkStart().GetNode().GetTextNode()->GetText()[pFieldMark->GetMarkStart().GetContentIndex()] != CH_TXT_ATR_FORMELEMENT);
+ // getInnerFieldmarkFor may also return one that starts at rNode,0 -
+ // skip it, must be handled in loop below
+ if (pFieldMark->GetMarkStart().GetNode() < rNode)
+ {
+ // this can be a nested field's end - skip over those!
+ if (pFieldMark->GetMarkEnd().GetNode() < rNode)
+ {
+ assert(cursor.GetPoint()->GetNode().GetTextNode()->GetText()[cursor.GetPoint()->GetContentIndex()] == CH_TXT_ATR_FIELDEND);
+ }
+ else
+ {
+ SwPosition const sepPos(::sw::mark::FindFieldSep(*pFieldMark));
+ startedFields.emplace_front(pFieldMark, sepPos.GetNode() < rNode);
+ }
+ *cursor.GetPoint() = pFieldMark->GetMarkStart();
+ }
+ if (!cursor.Move(fnMoveBackward, GoInContent))
+ {
+ break;
+ }
+ }
+ ::std::optional<sal_Int32> oStartHidden;
+ if (!::std::all_of(startedFields.begin(), startedFields.end(),
+ [](auto const& it) { return it.second; }))
+ {
+ oStartHidden.emplace(0); // node starts out hidden as field command
+ }
+ for (sal_Int32 i = 0; i < rNode.GetText().getLength(); ++i)
+ {
+ switch (rNode.GetText()[i])
+ {
+ case CH_TXT_ATR_FIELDSTART:
+ {
+ auto const pFieldMark(rIDMA.getFieldmarkAt(SwPosition(rNode, i)));
+ assert(pFieldMark);
+ startedFields.emplace_back(pFieldMark, false);
+ if (!oStartHidden)
+ {
+ oStartHidden.emplace(i);
+ }
+ break;
+ }
+ case CH_TXT_ATR_FIELDSEP:
+ {
+ assert(startedFields.back().first->IsCoveringPosition(SwPosition(rNode, i)));
+ startedFields.back().second = true;
+ assert(oStartHidden);
+ if (::std::all_of(startedFields.begin(), startedFields.end(),
+ [](auto const& it) { return it.second; }))
+ {
+// prevent -Werror=maybe-uninitialized under gcc 11.2.0
+#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+ // i is still hidden but the Range end is oddly "-1"
+ aHiddenMulti.Select({*oStartHidden, i}, true);
+ oStartHidden.reset();
+#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12
+#pragma GCC diagnostic pop
+#endif
+ }
+ break;
+ }
+ case CH_TXT_ATR_FIELDEND:
+ {
+ assert(startedFields.back().first == rIDMA.getFieldmarkAt(SwPosition(rNode, i)));
+ startedFields.pop_back();
+ aHiddenMulti.Select({i, i}, true);
+ break;
+ }
+ }
+ }
+ if (oStartHidden && rNode.Len() != 0)
+ {
+ aHiddenMulti.Select({*oStartHidden, rNode.Len() - 1}, true);
+ }
+ }
+ else if (eMode & ExpandMode::ExpandFields) // subset: only hide dummy chars
+ {
+ for (sal_Int32 i = 0; i < rNode.GetText().getLength(); ++i)
+ {
+ switch (rNode.GetText()[i])
+ {
+ case CH_TXT_ATR_FIELDSTART:
+ case CH_TXT_ATR_FIELDSEP:
+ case CH_TXT_ATR_FIELDEND:
+ {
+ aHiddenMulti.Select({i, i}, true);
+ break;
+ }
+ }
+ }
+ }
+
+ std::vector<block> aBlocks;
+
+ sal_Int32 nShownStart = 0;
+ for (sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i)
+ {
+ const Range& rRange = aHiddenMulti.GetRange(i);
+ const sal_Int32 nHiddenStart = rRange.Min();
+ const sal_Int32 nHiddenEnd = rRange.Max() + 1;
+ const sal_Int32 nHiddenLen = nHiddenEnd - nHiddenStart;
+
+ const sal_Int32 nShownEnd = nHiddenStart;
+ const sal_Int32 nShownLen = nShownEnd - nShownStart;
+
+ if (nShownLen)
+ aBlocks.emplace_back(nShownStart, nShownLen, true);
+
+ if (nHiddenLen)
+ aBlocks.emplace_back(nHiddenStart, nHiddenLen, false);
+
+ nShownStart = nHiddenEnd;
+ }
+
+ sal_Int32 nTrailingShownLen = rNodeText.getLength() - nShownStart;
+ if (nTrailingShownLen)
+ aBlocks.emplace_back(nShownStart, nTrailingShownLen, true);
+
+ if (eMode & ExpandMode::ExpandFields || eMode & ExpandMode::ExpandFootnote)
+ {
+ //first the normal fields, get their position in the node and what the text they expand
+ //to is
+ const SwpHints* pSwpHints2 = rNode.GetpSwpHints();
+ for ( size_t i = 0; pSwpHints2 && i < pSwpHints2->Count(); ++i )
+ {
+ const SwTextAttr* pAttr = pSwpHints2->Get(i);
+ if (pAttr->HasDummyChar())
+ {
+ const sal_Int32 nDummyCharPos = pAttr->GetStart();
+ if (aHiddenMulti.IsSelected(nDummyCharPos))
+ continue;
+ std::vector<block>::iterator aFind = std::find_if(aBlocks.begin(),
+ aBlocks.end(), containsPos(nDummyCharPos));
+ if (aFind != aBlocks.end())
+ {
+ FieldResult aFieldResult(nDummyCharPos);
+ switch (pAttr->Which())
+ {
+ case RES_TXTATR_ANNOTATION:
+ if (eMode & ExpandMode::ExpandFields)
+ {
+ // this uses CH_TXTATR_INWORD so replace with nothing
+ aFieldResult.m_eType = FieldResult::FIELD;
+ }
+ break;
+ case RES_TXTATR_FIELD:
+ if (eMode & ExpandMode::ExpandFields)
+ {
+ // add a ZWSP before the expanded field in replace mode
+ aFieldResult.m_sExpand = ((eMode & ExpandMode::ReplaceMode)
+ ? OUString(CHAR_ZWSP) : OUString("")) +
+ static_txtattr_cast<SwTextField const*>(pAttr)->
+ GetFormatField().GetField()->ExpandField(true, pLayout);
+ aFieldResult.m_eType = FieldResult::FIELD;
+ }
+ break;
+ case RES_TXTATR_FTN:
+ if (eMode & ExpandMode::ExpandFootnote)
+ {
+ const SwFormatFootnote& rFootnote = static_cast<SwTextFootnote const*>(pAttr)->GetFootnote();
+ const SwDoc& rDoc = rNode.GetDoc();
+ aFieldResult.m_sExpand = (eMode & ExpandMode::ReplaceMode)
+ ? OUString(CHAR_ZWSP)
+ : rFootnote.GetViewNumStr(rDoc, pLayout);
+ aFieldResult.m_eType = FieldResult::FOOTNOTE;
+ }
+ break;
+ default:
+ break;
+ }
+ aFind->m_aAttrs.insert(aFieldResult);
+ }
+ }
+ }
+
+ if (eMode & ExpandMode::ExpandFields)
+ {
+ //now get the dropdown formfields, get their position in the node and what the text they expand
+ //to is
+ SwPaM aPaM(rNode, 0, rNode, rNode.Len());
+ std::vector<sw::mark::IFieldmark*> aNoTextFieldmarks =
+ rNode.GetDoc().getIDocumentMarkAccess()->getNoTextFieldmarksIn(aPaM);
+
+ for (sw::mark::IFieldmark *const pMark : aNoTextFieldmarks)
+ {
+ const sal_Int32 nDummyCharPos = pMark->GetMarkStart().GetContentIndex();
+ if (aHiddenMulti.IsSelected(nDummyCharPos))
+ continue;
+ std::vector<block>::iterator aFind = std::find_if(aBlocks.begin(), aBlocks.end(),
+ containsPos(nDummyCharPos));
+ if (aFind != aBlocks.end())
+ {
+ FieldResult aFieldResult(nDummyCharPos);
+ aFieldResult.m_sExpand = (eMode & ExpandMode::ReplaceMode)
+ ? OUString(CHAR_ZWSP)
+ : sw::mark::ExpandFieldmark(pMark);
+ aFieldResult.m_eType = FieldResult::FIELD;
+ aFind->m_aAttrs.insert(aFieldResult);
+ }
+ }
+ }
+ }
+
+ //store the end of each range in the model and where that end of range
+ //maps to in the view
+ sal_Int32 nOffset = 0;
+ for (const auto& rBlock : aBlocks)
+ {
+ const sal_Int32 nBlockLen = rBlock.m_nLen;
+ if (!nBlockLen)
+ continue;
+ const sal_Int32 nBlockStart = rBlock.m_nStart;
+ const sal_Int32 nBlockEnd = nBlockStart + nBlockLen;
+
+ if (!rBlock.m_bVisible)
+ {
+ sal_Int32 const modelBlockPos(nBlockEnd);
+ sal_Int32 const viewBlockPos(nBlockStart + nOffset);
+ m_aMap.emplace_back(modelBlockPos, viewBlockPos, false);
+
+ m_aRetText = m_aRetText.replaceAt(nOffset + nBlockStart, nBlockLen, u"");
+ nOffset -= nBlockLen;
+ }
+ else
+ {
+ for (const auto& rAttr : rBlock.m_aAttrs)
+ {
+ sal_Int32 const modelFieldPos(rAttr.m_nFieldPos);
+ sal_Int32 const viewFieldPos(rAttr.m_nFieldPos + nOffset);
+ m_aMap.emplace_back(modelFieldPos, viewFieldPos, true );
+
+ m_aRetText = m_aRetText.replaceAt(viewFieldPos, 1, rAttr.m_sExpand);
+ nOffset += rAttr.m_sExpand.getLength() - 1;
+
+ switch (rAttr.m_eType)
+ {
+ case FieldResult::FIELD:
+ m_FieldPositions.push_back(viewFieldPos);
+ break;
+ case FieldResult::FOOTNOTE:
+ m_FootnotePositions.push_back(viewFieldPos);
+ break;
+ case FieldResult::NONE: /*ignore*/
+ break;
+ }
+ }
+
+ sal_Int32 const modelEndBlock(nBlockEnd);
+ sal_Int32 const viewFieldPos(nBlockEnd + nOffset);
+ m_aMap.emplace_back(modelEndBlock, viewFieldPos, true);
+ }
+ }
+}
+
+/** Converts a model position into a view position
+*/
+sal_Int32 ModelToViewHelper::ConvertToViewPosition( sal_Int32 nModelPos ) const
+{
+ // Search for entry after nPos:
+ auto aIter = std::find_if(m_aMap.begin(), m_aMap.end(),
+ [nModelPos](const ConversionMapEntry& rEntry) { return rEntry.m_nModelPos >= nModelPos; });
+ if (aIter != m_aMap.end())
+ {
+ //if it's an invisible portion, map all contained positions
+ //to the anchor viewpos
+ if (!aIter->m_bVisible)
+ return aIter->m_nViewPos;
+
+ //if it's a visible portion, then the view position is the anchor
+ //viewpos - the offset of the input modelpos from the anchor
+ //modelpos
+ const sal_Int32 nOffsetFromEnd = aIter->m_nModelPos - nModelPos;
+ return aIter->m_nViewPos - nOffsetFromEnd;
+ }
+
+ return nModelPos;
+}
+
+/** Converts a view position into a model position
+*/
+ModelToViewHelper::ModelPosition ModelToViewHelper::ConvertToModelPosition( sal_Int32 nViewPos ) const
+{
+ ModelPosition aRet;
+ aRet.mnPos = nViewPos;
+
+ // Search for entry after nPos:
+ auto aIter = std::find_if(m_aMap.begin(), m_aMap.end(),
+ [nViewPos](const ConversionMapEntry& rEntry) { return rEntry.m_nViewPos > nViewPos; });
+
+ // If nViewPos is in front of first field, we are finished.
+ if (aIter != m_aMap.end() && aIter != m_aMap.begin())
+ {
+ const sal_Int32 nPosModel = aIter->m_nModelPos;
+ const sal_Int32 nPosExpand = aIter->m_nViewPos;
+
+ --aIter;
+
+ // nPrevPosModel is the field position
+ const sal_Int32 nPrevPosModel = aIter->m_nModelPos;
+ const sal_Int32 nPrevPosExpand = aIter->m_nViewPos;
+
+ const sal_Int32 nLengthModel = nPosModel - nPrevPosModel;
+ const sal_Int32 nLengthExpand = nPosExpand - nPrevPosExpand;
+
+ const sal_Int32 nFieldLengthExpand = nLengthExpand - nLengthModel + 1;
+ const sal_Int32 nFieldEndExpand = nPrevPosExpand + nFieldLengthExpand;
+
+ // Check if nPos is outside of field:
+ if ( nFieldEndExpand <= nViewPos )
+ {
+ // nPos is outside of field:
+ const sal_Int32 nDistToField = nViewPos - nFieldEndExpand + 1;
+ aRet.mnPos = nPrevPosModel + nDistToField;
+ }
+ else
+ {
+ // nViewPos is inside a field:
+ aRet.mnPos = nPrevPosModel;
+ aRet.mnSubPos = nViewPos - nPrevPosExpand;
+ aRet.mbIsField = true;
+ }
+ }
+
+ return aRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/ndhints.cxx b/sw/source/core/txtnode/ndhints.cxx
new file mode 100644
index 0000000000..a9b9d4b6c5
--- /dev/null
+++ b/sw/source/core/txtnode/ndhints.cxx
@@ -0,0 +1,486 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <editeng/rsiditem.hxx>
+#include <sal/log.hxx>
+#include <txatbase.hxx>
+#include <ndhints.hxx>
+#include <txtatr.hxx>
+
+#ifdef DBG_UTIL
+#include <pam.hxx>
+#include <fmtautofmt.hxx>
+#endif
+
+/// sort order: Start, End (reverse), Which (reverse),
+/// (char style: sort number), at last the pointer
+static bool CompareSwpHtStart( const SwTextAttr* lhs, const SwTextAttr* rhs )
+{
+ const SwTextAttr &rHt1 = *lhs;
+ const SwTextAttr &rHt2 = *rhs;
+ if ( rHt1.GetStart() == rHt2.GetStart() )
+ {
+ const sal_Int32 nHt1 = rHt1.GetAnyEnd();
+ const sal_Int32 nHt2 = rHt2.GetAnyEnd();
+ if ( nHt1 == nHt2 )
+ {
+ const sal_uInt16 nWhich1 = rHt1.Which();
+ const sal_uInt16 nWhich2 = rHt2.Which();
+ if ( nWhich1 == nWhich2 )
+ {
+ if ( RES_TXTATR_CHARFMT == nWhich1 )
+ {
+ const sal_uInt16 nS1 =
+ static_txtattr_cast<const SwTextCharFormat&>(rHt1).GetSortNumber();
+ const sal_uInt16 nS2 =
+ static_txtattr_cast<const SwTextCharFormat&>(rHt2).GetSortNumber();
+ if ( nS1 != nS2 ) // robust
+ return nS1 < nS2;
+ }
+
+ return reinterpret_cast<sal_IntPtr>(&rHt1) < reinterpret_cast<sal_IntPtr>(&rHt2);
+ }
+ // order is important! for requirements see hintids.hxx
+ return ( nWhich1 > nWhich2 );
+ }
+ return ( nHt1 > nHt2 );
+ }
+ return ( rHt1.GetStart() < rHt2.GetStart() );
+}
+
+/// sort order: Which, Start, End(reverse) at last the pointer
+bool CompareSwpHtWhichStart::operator()( const SwTextAttr* lhs, const sal_uInt16 nWhich ) const
+{
+ return lhs->Which() < nWhich;
+}
+bool CompareSwpHtWhichStart::operator()( const SwTextAttr* lhs, const SwTextAttr* rhs ) const
+{
+ const SwTextAttr &rHt1 = *lhs;
+ const SwTextAttr &rHt2 = *rhs;
+ const sal_uInt16 nWhich1 = rHt1.Which();
+ const sal_uInt16 nWhich2 = rHt2.Which();
+ if ( nWhich1 < nWhich2 )
+ return true;
+ if ( nWhich1 > nWhich2 )
+ return false;
+ if (rHt1.GetStart() < rHt2.GetStart())
+ return true;
+ if (rHt1.GetStart() > rHt2.GetStart())
+ return false;
+ if ( RES_TXTATR_CHARFMT == nWhich1 )
+ {
+ const sal_uInt16 nS1 =
+ static_txtattr_cast<const SwTextCharFormat&>(rHt1).GetSortNumber();
+ const sal_uInt16 nS2 =
+ static_txtattr_cast<const SwTextCharFormat&>(rHt2).GetSortNumber();
+ if ( nS1 != nS2 ) // robust
+ return nS1 < nS2;
+ }
+ const sal_Int32 nEnd1 = rHt1.GetAnyEnd();
+ const sal_Int32 nEnd2 = rHt2.GetAnyEnd();
+ if ( nEnd1 > nEnd2 )
+ return true;
+ if ( nEnd1 < nEnd2 )
+ return false;
+ return reinterpret_cast<sal_IntPtr>(&rHt1) < reinterpret_cast<sal_IntPtr>(&rHt2);
+}
+
+/// sort order: End, Start(reverse), Which
+/// (char style: sort number), at last the pointer(reverse)
+bool CompareSwpHtEnd::operator()( sal_Int32 nEndPos, const SwTextAttr* rhs ) const
+{
+ return nEndPos < rhs->GetAnyEnd();
+}
+bool CompareSwpHtEnd::operator()( const SwTextAttr* lhs, const SwTextAttr* rhs ) const
+{
+ const SwTextAttr &rHt1 = *lhs;
+ const SwTextAttr &rHt2 = *rhs;
+ const sal_Int32 nHt1 = rHt1.GetAnyEnd();
+ const sal_Int32 nHt2 = rHt2.GetAnyEnd();
+ if ( nHt1 == nHt2 )
+ {
+ if ( rHt1.GetStart() == rHt2.GetStart() )
+ {
+ const sal_uInt16 nWhich1 = rHt1.Which();
+ const sal_uInt16 nWhich2 = rHt2.Which();
+ if ( nWhich1 == nWhich2 )
+ {
+ if ( RES_TXTATR_CHARFMT == nWhich1 )
+ {
+ const sal_uInt16 nS1 =
+ static_txtattr_cast<const SwTextCharFormat&>(rHt1).GetSortNumber();
+ const sal_uInt16 nS2 =
+ static_txtattr_cast<const SwTextCharFormat&>(rHt2).GetSortNumber();
+ if ( nS1 != nS2 ) // robust
+ return nS1 > nS2;
+ }
+
+ return reinterpret_cast<sal_IntPtr>(&rHt1) > reinterpret_cast<sal_IntPtr>(&rHt2);
+ }
+ // order is important! for requirements see hintids.hxx
+ return ( nWhich1 < nWhich2 );
+ }
+ else
+ return ( rHt1.GetStart() > rHt2.GetStart() );
+ }
+ return ( nHt1 < nHt2 );
+}
+
+void SwpHints::Insert(SwTextAttr* pHt)
+{
+ assert(std::find(m_HintsByStart.begin(), m_HintsByStart.end(), pHt)
+ == m_HintsByStart.end()); // "Insert: hint already in HtStart"
+ assert( pHt->m_pHints == nullptr );
+ pHt->m_pHints = this;
+
+ if (m_bStartMapNeedsSorting)
+ ResortStartMap();
+ if (m_bEndMapNeedsSorting)
+ ResortEndMap();
+ if (m_bWhichMapNeedsSorting)
+ ResortWhichMap();
+
+ auto it1 = std::lower_bound(m_HintsByStart.begin(), m_HintsByStart.end(), pHt, CompareSwpHtStart);
+ m_HintsByStart.insert(it1, pHt);
+
+ auto it2 = std::lower_bound(m_HintsByEnd.begin(), m_HintsByEnd.end(), pHt, CompareSwpHtEnd());
+ m_HintsByEnd.insert(it2, pHt);
+
+ auto it3 = std::lower_bound(m_HintsByWhichAndStart.begin(), m_HintsByWhichAndStart.end(), pHt, CompareSwpHtWhichStart());
+ m_HintsByWhichAndStart.insert(it3, pHt);
+}
+
+bool SwpHints::Contains( const SwTextAttr *pHt ) const
+{
+ // DO NOT use find() or CHECK here!
+ // if called from SwTextNode::InsertItem, pHt has already been deleted,
+ // so it cannot be dereferenced
+ return std::find(m_HintsByStart.begin(), m_HintsByStart.end(), pHt)
+ != m_HintsByStart.end();
+}
+
+#ifdef DBG_UTIL
+
+#define CHECK_ERR(cond, text) \
+ if(!(cond)) \
+ { \
+ SAL_WARN("sw.core", text); \
+ Resort(); \
+ return false; \
+ }
+
+bool SwpHints::Check(bool bPortionsMerged) const
+{
+ // 1) both arrays have same size
+ CHECK_ERR( m_HintsByStart.size() == m_HintsByEnd.size(),
+ "HintsCheck: wrong sizes" );
+ sal_Int32 nLastStart = 0;
+ sal_Int32 nLastEnd = 0;
+
+ const SwTextAttr *pLastStart = nullptr;
+ const SwTextAttr *pLastEnd = nullptr;
+ o3tl::sorted_vector<SwTextAttr const*> RsidOnlyAutoFormats;
+ if (bPortionsMerged)
+ {
+ for (size_t i = 0; i < Count(); ++i)
+ {
+ SwTextAttr const*const pHint(m_HintsByStart[i]);
+ if (RES_TXTATR_AUTOFMT == pHint->Which())
+ {
+ std::shared_ptr<SfxItemSet> const & pSet(
+ pHint->GetAutoFormat().GetStyleHandle());
+ if (pSet->Count() == 1 && pSet->GetItem(RES_CHRATR_RSID, false))
+ {
+ RsidOnlyAutoFormats.insert(pHint);
+ }
+ }
+ }
+ }
+
+ // --- cross checks ---
+ // same pointers in both arrays
+ auto tmpHintsByEnd = m_HintsByEnd;
+ std::sort(tmpHintsByEnd.begin(), tmpHintsByEnd.end(), CompareSwpHtStart);
+ CHECK_ERR( tmpHintsByEnd == m_HintsByStart, "HintsCheck: the two arrays do not contain the same set of pointers" );
+
+ for( size_t i = 0; i < Count(); ++i )
+ {
+ // --- check Starts ---
+
+ // 2a) valid pointer? depends on overwriting freed mem with 0xFF
+ const SwTextAttr *pHt = m_HintsByStart[i];
+ CHECK_ERR( 0xFF != *reinterpret_cast<unsigned char const *>(pHt), "HintsCheck: start ptr was deleted" );
+
+ // 3a) start sort order?
+ sal_Int32 nIdx = pHt->GetStart();
+ CHECK_ERR( nIdx >= nLastStart, "HintsCheck: starts are unsorted" );
+
+ // 4a) IsLessStart consistency
+ if( pLastStart )
+ CHECK_ERR( CompareSwpHtStart( pLastStart, pHt ), "HintsCheck: IsLastStart" );
+
+ nLastStart = nIdx;
+ pLastStart = pHt;
+
+ // --- check Ends ---
+
+ // 2b) valid pointer? see DELETEFF
+ const SwTextAttr *pHtEnd = m_HintsByEnd[i];
+ CHECK_ERR( 0xFF != *reinterpret_cast<unsigned char const *>(pHtEnd), "HintsCheck: end ptr was deleted" );
+
+ // 3b) end sort order?
+ nIdx = pHtEnd->GetAnyEnd();
+ CHECK_ERR( nIdx >= nLastEnd, "HintsCheck: ends are unsorted" );
+
+ // 4b) IsLessEnd consistency
+ if( pLastEnd )
+ CHECK_ERR( CompareSwpHtEnd()( pLastEnd, pHtEnd ), "HintsCheck: IsLastEnd" );
+
+ nLastEnd = nIdx;
+ pLastEnd = pHtEnd;
+
+ CHECK_ERR( COMPLETE_STRING != nIdx, "HintsCheck: no GetEndOf" );
+
+ // 7a) character attributes in array?
+ sal_uInt16 nWhich = pHt->Which();
+ CHECK_ERR( !isCHRATR(nWhich),
+ "HintsCheck: Character attribute in start array" );
+
+ // 7b) character attributes in array?
+ nWhich = pHtEnd->Which();
+ CHECK_ERR( !isCHRATR(nWhich),
+ "HintsCheck: Character attribute in end array" );
+
+ // 8) style portion check
+ const SwTextAttr* pHtThis = m_HintsByStart[i];
+ const SwTextAttr* pHtLast = i > 0 ? m_HintsByStart[i-1] : nullptr;
+ CHECK_ERR( (0 == i)
+ || ( (RES_TXTATR_CHARFMT != pHtLast->Which())
+ && (RES_TXTATR_AUTOFMT != pHtLast->Which()))
+ || ( (RES_TXTATR_CHARFMT != pHtThis->Which())
+ && (RES_TXTATR_AUTOFMT != pHtThis->Which()))
+ || (pHtThis->GetStart() >= *pHtLast->End()) // no overlap
+ || ( ( (pHtThis->GetStart() == pHtLast->GetStart())
+ && (*pHtThis->End() == *pHtLast->End())
+ ) // same range
+ && ( (pHtThis->Which() != RES_TXTATR_AUTOFMT)
+ || (pHtLast->Which() != RES_TXTATR_AUTOFMT)
+ ) // never two AUTOFMT on same range
+ && ( (pHtThis->Which() != RES_TXTATR_CHARFMT)
+ || (pHtLast->Which() != RES_TXTATR_CHARFMT)
+ || (static_txtattr_cast<const SwTextCharFormat *>(pHtThis)
+ ->GetSortNumber() !=
+ static_txtattr_cast<const SwTextCharFormat *>(pHtLast)
+ ->GetSortNumber())
+ ) // multiple CHARFMT on same range need distinct sorter
+ )
+ || (pHtThis->GetStart() == *pHtThis->End()), // this empty
+ "HintsCheck: Portion inconsistency. "
+ "This can be temporarily ok during undo operations" );
+
+ // 8 1/2) format ignore start/end flag check
+ // (problems because MergePortions buggy or not called)
+ if (bPortionsMerged)
+ {
+ if (RES_TXTATR_AUTOFMT == pHt->Which() ||
+ RES_TXTATR_CHARFMT == pHt->Which())
+ {
+ // mostly ignore the annoying no-length hints
+ // BuildPortions inserts these in the middle of an existing one
+ bool const bNoLength(pHt->GetStart() == *pHt->End());
+ bool bNeedContinuation(!bNoLength && pHt->IsFormatIgnoreEnd());
+ bool bForbidContinuation(!bNoLength && !bNeedContinuation);
+ if (RES_TXTATR_AUTOFMT == pHt->Which())
+ {
+ if (RsidOnlyAutoFormats.find(pHt) != RsidOnlyAutoFormats.end())
+ {
+ assert(pHt->IsFormatIgnoreStart());
+ bNeedContinuation = false;
+ // don't forbid continuation - may be other hint here!
+ }
+ }
+ if (bNeedContinuation || bForbidContinuation)
+ {
+ bool bFound(false);
+ for (size_t j = i + 1; j < Count(); ++j)
+ {
+ SwTextAttr *const pOther(m_HintsByStart[j]);
+ if (pOther->GetStart() > *pHt->End())
+ {
+ break; // done
+ }
+ else if (pOther->GetStart() == pOther->GetAnyEnd())
+ {
+ continue; // empty hint: ignore
+ }
+ else if (pOther->GetStart() == *pHt->End())
+ {
+ if (RES_TXTATR_AUTOFMT == pOther->Which() ||
+ RES_TXTATR_CHARFMT == pOther->Which())
+ { // multiple charfmt on same range must all match
+ if (bNeedContinuation)
+ {
+ assert(pOther->IsFormatIgnoreStart());
+ bFound = true;
+ }
+ else if (bForbidContinuation &&
+ (RsidOnlyAutoFormats.find(pOther) ==
+ RsidOnlyAutoFormats.end()))
+ {
+ assert(!pOther->IsFormatIgnoreStart());
+ }
+ }
+ }
+ }
+ if (bNeedContinuation)
+ {
+ assert(bFound); // ? can this happen temp. during undo?
+ }
+ }
+ }
+ else
+ {
+ assert(!pHt->IsFormatIgnoreStart());
+ assert(!pHt->IsFormatIgnoreEnd());
+ }
+ }
+
+ // 9) nesting portion check
+ if (pHtThis->IsNesting())
+ {
+ for (size_t j = 0; j < i; ++j)
+ {
+ SwTextAttr const * const pOther( m_HintsByStart[j] );
+ if (pOther->IsNesting())
+ {
+ SwComparePosition cmp = ComparePosition(
+ pHtThis->GetStart(), *pHtThis->End(),
+ pOther->GetStart(), *pOther->End());
+ CHECK_ERR( (SwComparePosition::OverlapBefore != cmp) &&
+ (SwComparePosition::OverlapBehind != cmp),
+ "HintsCheck: overlapping nesting hints!!!" );
+ }
+ }
+ }
+
+ // 10) dummy char check (unfortunately cannot check SwTextNode::m_Text)
+ if (pHtThis->HasDummyChar())
+ {
+ for ( size_t j = 0; j < i; ++j )
+ {
+ SwTextAttr const * const pOther( m_HintsByStart[j] );
+ if (pOther->HasDummyChar())
+ {
+ CHECK_ERR( (pOther->GetStart() != pHtThis->GetStart()),
+ "HintsCheck: multiple hints claim same CH_TXTATR!");
+ }
+ }
+ }
+ }
+ return true;
+}
+
+#endif /* DBG_UTIL */
+
+// Resort() is called before every Insert and Delete.
+// Various SwTextNode methods modify hints in a way that violates the
+// sort order of the m_HintsByStart, m_HintsByEnd arrays, so this method is needed
+// to restore the order.
+
+void SwpHints::Resort() const
+{
+ if (m_bStartMapNeedsSorting)
+ {
+ auto & rStartMap = const_cast<SwpHints*>(this)->m_HintsByStart;
+ std::sort(rStartMap.begin(), rStartMap.end(), CompareSwpHtStart);
+ m_bStartMapNeedsSorting = false;
+ }
+ if (m_bEndMapNeedsSorting)
+ {
+ auto & rEndMap = const_cast<SwpHints*>(this)->m_HintsByEnd;
+ std::sort(rEndMap.begin(), rEndMap.end(), CompareSwpHtEnd());
+ m_bEndMapNeedsSorting = false;
+ }
+ if (m_bWhichMapNeedsSorting)
+ {
+ auto & rWhichStartMap = const_cast<SwpHints*>(this)->m_HintsByWhichAndStart;
+ std::sort(rWhichStartMap.begin(), rWhichStartMap.end(), CompareSwpHtWhichStart());
+ m_bWhichMapNeedsSorting = false;
+ }
+}
+
+void SwpHints::ResortStartMap() const
+{
+ if (m_bStartMapNeedsSorting)
+ {
+ auto & rStartMap = const_cast<SwpHints*>(this)->m_HintsByStart;
+ std::sort(rStartMap.begin(), rStartMap.end(), CompareSwpHtStart);
+ m_bStartMapNeedsSorting = false;
+ }
+}
+
+void SwpHints::ResortEndMap() const
+{
+ if (m_bEndMapNeedsSorting)
+ {
+ auto & rEndMap = const_cast<SwpHints*>(this)->m_HintsByEnd;
+ std::sort(rEndMap.begin(), rEndMap.end(), CompareSwpHtEnd());
+ m_bEndMapNeedsSorting = false;
+ }
+}
+
+void SwpHints::ResortWhichMap() const
+{
+ if (m_bWhichMapNeedsSorting)
+ {
+ auto & rWhichStartMap = const_cast<SwpHints*>(this)->m_HintsByWhichAndStart;
+ std::sort(rWhichStartMap.begin(), rWhichStartMap.end(), CompareSwpHtWhichStart());
+ m_bWhichMapNeedsSorting = false;
+ }
+}
+
+size_t SwpHints::GetFirstPosSortedByWhichAndStart( sal_uInt16 nWhich ) const
+{
+ if (m_bWhichMapNeedsSorting)
+ ResortWhichMap();
+ auto it = std::lower_bound(m_HintsByWhichAndStart.begin(), m_HintsByWhichAndStart.end(), nWhich, CompareSwpHtWhichStart());
+ if ( it == m_HintsByWhichAndStart.end() )
+ return SAL_MAX_SIZE;
+ return it - m_HintsByWhichAndStart.begin();
+}
+
+int SwpHints::GetLastPosSortedByEnd( sal_Int32 nEndPos ) const
+{
+ if (m_bEndMapNeedsSorting)
+ ResortEndMap();
+ auto it = std::upper_bound(m_HintsByEnd.begin(), m_HintsByEnd.end(), nEndPos, CompareSwpHtEnd());
+ return it - m_HintsByEnd.begin() - 1;
+}
+
+size_t SwpHints::GetIndexOf( const SwTextAttr *pHt ) const
+{
+ if (m_bStartMapNeedsSorting)
+ ResortStartMap();
+ auto it = std::lower_bound(m_HintsByStart.begin(), m_HintsByStart.end(), const_cast<SwTextAttr*>(pHt), CompareSwpHtStart);
+ if ( it == m_HintsByStart.end() || *it != pHt )
+ return SAL_MAX_SIZE;
+ return it - m_HintsByStart.begin();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/ndtxt.cxx b/sw/source/core/txtnode/ndtxt.cxx
new file mode 100644
index 0000000000..47bff5e08b
--- /dev/null
+++ b/sw/source/core/txtnode/ndtxt.cxx
@@ -0,0 +1,5587 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <hintids.hxx>
+#include <hints.hxx>
+
+#include <comphelper/lok.hxx>
+#include <comphelper/string.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/rsiditem.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <anchoredobject.hxx>
+#include <txtfld.hxx>
+#include <txtinet.hxx>
+#include <fmtanchr.hxx>
+#include <fmtinfmt.hxx>
+#include <fmtrfmrk.hxx>
+#include <txttxmrk.hxx>
+#include <fchrfmt.hxx>
+#include <txtftn.hxx>
+#include <fmtflcnt.hxx>
+#include <fmtfld.hxx>
+#include <frmatr.hxx>
+#include <ftnidx.hxx>
+#include <ftninfo.hxx>
+#include <fmtftn.hxx>
+#include <charfmt.hxx>
+#include <ndtxt.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <IDocumentListsAccess.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <IDocumentLayoutAccess.hxx>
+#include <docary.hxx>
+#include <pam.hxx>
+#include <fldbas.hxx>
+#include <paratr.hxx>
+#include <txtfrm.hxx>
+#include <ftnfrm.hxx>
+#include <pagefrm.hxx>
+#include <rootfrm.hxx>
+#include <expfld.hxx>
+#include <section.hxx>
+#include <mvsave.hxx>
+#include <SwGrammarMarkUp.hxx>
+#include <redline.hxx>
+#include <IMark.hxx>
+#include <scriptinfo.hxx>
+#include <istyleaccess.hxx>
+#include <SwStyleNameMapper.hxx>
+#include <numrule.hxx>
+#include <docsh.hxx>
+#include <SwNodeNum.hxx>
+#include <svl/grabbagitem.hxx>
+#include <svl/intitem.hxx>
+#include <sortedobjs.hxx>
+#include <calbck.hxx>
+#include <attrhint.hxx>
+#include <memory>
+#include <unoparagraph.hxx>
+#include <unotext.hxx>
+#include <wrtsh.hxx>
+#include <fmtpdsc.hxx>
+#include <svx/sdr/attribute/sdrallfillattributeshelper.hxx>
+#include <svl/itemiter.hxx>
+#include <undobj.hxx>
+#include <formatflysplit.hxx>
+
+using namespace ::com::sun::star;
+
+typedef std::vector<SwTextAttr*> SwpHts;
+
+namespace sw {
+ class TextNodeNotificationSuppressor {
+ SwTextNode& m_rNode;
+ bool m_bWasNotifiable;
+ public:
+ TextNodeNotificationSuppressor(SwTextNode& rNode)
+ : m_rNode(rNode)
+ , m_bWasNotifiable(rNode.m_bNotifiable)
+ {
+ m_rNode.m_bNotifiable = false;
+ }
+ ~TextNodeNotificationSuppressor()
+ {
+ m_rNode.m_bNotifiable = m_bWasNotifiable;
+ }
+ };
+}
+
+// unfortunately everyone can change Hints without ensuring order or the linking between them
+#ifdef DBG_UTIL
+#define CHECK_SWPHINTS(pNd) { if( pNd->GetpSwpHints() && \
+ !pNd->GetDoc().IsInReading() ) \
+ pNd->GetpSwpHints()->Check(true); }
+#define CHECK_SWPHINTS_IF_FRM(pNd) { if( pNd->GetpSwpHints() && \
+ !pNd->GetDoc().IsInReading() ) \
+ pNd->GetpSwpHints()->Check(getLayoutFrame(nullptr, nullptr, nullptr) != nullptr); }
+#else
+#define CHECK_SWPHINTS(pNd)
+#define CHECK_SWPHINTS_IF_FRM(pNd)
+#endif
+
+SwTextNode *SwNodes::MakeTextNode( SwNode& rWhere,
+ SwTextFormatColl *pColl, bool const bNewFrames)
+{
+ OSL_ENSURE( pColl, "Collection pointer is 0." );
+
+ SwTextNode *pNode = new SwTextNode( rWhere, pColl, nullptr );
+
+ SwNodeIndex aIdx( *pNode );
+
+ // if there is no layout or it is in a hidden section, MakeFrames is not needed
+ const SwSectionNode* pSectNd;
+ if (!bNewFrames ||
+ !GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell() ||
+ ( nullptr != (pSectNd = pNode->FindSectionNode()) &&
+ pSectNd->GetSection().IsHiddenFlag() ))
+ return pNode;
+
+ SwNodeIndex aTmp( rWhere );
+ do {
+ // max. 2 loops:
+ // 1. take the successor
+ // 2. take the predecessor
+
+ SwNode * pNd = & aTmp.GetNode();
+ switch (pNd->GetNodeType())
+ {
+ case SwNodeType::Table:
+ static_cast<SwTableNode*>(pNd)->MakeFramesForAdjacentContentNode(aIdx);
+ return pNode;
+
+ case SwNodeType::Section:
+ if( static_cast<SwSectionNode*>(pNd)->GetSection().IsHidden() ||
+ static_cast<SwSectionNode*>(pNd)->IsContentHidden() )
+ {
+ pNd = FindPrvNxtFrameNode( *pNode, pNode );
+ if( !pNd )
+ return pNode;
+ aTmp = *pNd;
+ break;
+ }
+ static_cast<SwSectionNode*>(pNd)->MakeFramesForAdjacentContentNode(aIdx);
+ return pNode;
+
+ case SwNodeType::Text:
+ case SwNodeType::Grf:
+ case SwNodeType::Ole:
+ static_cast<SwContentNode*>(pNd)->MakeFramesForAdjacentContentNode(*pNode);
+ return pNode;
+
+ case SwNodeType::End:
+ if( pNd->StartOfSectionNode()->IsSectionNode() &&
+ aTmp.GetIndex() < rWhere.GetIndex() )
+ {
+ if( pNd->StartOfSectionNode()->GetSectionNode()->GetSection().IsHiddenFlag())
+ {
+ if( !GoPrevSection( &aTmp, true, false ) ||
+ aTmp.GetNode().FindTableNode() !=
+ pNode->FindTableNode() )
+ return pNode;
+ }
+ else
+ aTmp = *pNd->StartOfSectionNode();
+ break;
+ }
+ else if( pNd->StartOfSectionNode()->IsTableNode() &&
+ aTmp.GetIndex() < rWhere.GetIndex() )
+ {
+ // after a table node
+ aTmp = *pNd->StartOfSectionNode();
+ break;
+ }
+ [[fallthrough]];
+ default:
+ if( &rWhere == &aTmp.GetNode() )
+ aTmp -= SwNodeOffset(2);
+ else
+ return pNode;
+ break;
+ }
+ } while( true );
+}
+
+SwTextNode::SwTextNode( SwNode& rWhere, SwTextFormatColl *pTextColl, const SfxItemSet* pAutoAttr )
+: SwContentNode( rWhere, SwNodeType::Text, pTextColl ),
+ m_bContainsHiddenChars(false),
+ m_bHiddenCharsHidePara(false),
+ m_bRecalcHiddenCharFlags(false),
+ m_bLastOutlineState( false ),
+ m_bNotifiable( true ),
+ mbEmptyListStyleSetDueToSetOutlineLevelAttr( false ),
+ mbInSetOrResetAttr( false ),
+ m_bInUndo(false)
+{
+ {
+ sw::TextNodeNotificationSuppressor(*this);
+
+ if( pAutoAttr )
+ SetAttr( *pAutoAttr );
+
+ if (!IsInList() && GetNumRule() && !GetListId().isEmpty())
+ {
+ // #i101516#
+ // apply paragraph style's assigned outline style list level as
+ // list level of the paragraph, if it has none set already.
+ if ( !HasAttrListLevel() &&
+ pTextColl && pTextColl->IsAssignedToListLevelOfOutlineStyle() )
+ {
+ SetAttrListLevel( pTextColl->GetAssignedOutlineStyleLevel() );
+ }
+ AddToList();
+ }
+
+ // call method <UpdateOutlineNode(..)> only for the document nodes array
+ if (GetNodes().IsDocNodes())
+ GetNodes().UpdateOutlineNode(*this);
+ }
+
+ m_bContainsHiddenChars = m_bHiddenCharsHidePara = false;
+ m_bRecalcHiddenCharFlags = true;
+}
+
+SwTextNode::~SwTextNode()
+{
+ // delete only removes the pointer not the array elements!
+ if ( m_pSwpHints )
+ {
+ // do not delete attributes twice when those delete their content
+ std::unique_ptr<SwpHints> pTmpHints(std::move(m_pSwpHints));
+
+ for( size_t j = pTmpHints->Count(); j; )
+ {
+ // first remove the attribute from the array otherwise
+ // if would delete itself
+ DestroyAttr( pTmpHints->Get( --j ) );
+ }
+ }
+
+ // must be removed from outline nodes by now
+#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
+ SwOutlineNodes::size_type foo;
+ assert(!GetNodes().GetOutLineNds().Seek_Entry(this, &foo));
+#endif
+
+ RemoveFromList();
+
+ DelFrames(nullptr); // must be called here while it's still a SwTextNode
+ DelFrames_TextNodePart();
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+ if (!GetDoc().IsInDtor())
+ ResetAttr(RES_PAGEDESC);
+#else
+ ResetAttr(RES_PAGEDESC);
+#endif
+ InvalidateInSwCache(RES_OBJECTDYING);
+}
+
+void SwTextNode::FileLoadedInitHints()
+{
+ if (m_pSwpHints)
+ {
+ m_pSwpHints->MergePortions(*this);
+ }
+}
+
+SwContentFrame *SwTextNode::MakeFrame( SwFrame* pSib )
+{
+ SwContentFrame *pFrame = sw::MakeTextFrame(*this, pSib, sw::FrameMode::New);
+ return pFrame;
+}
+
+sal_Int32 SwTextNode::Len() const
+{
+ return m_Text.getLength();
+}
+
+// After a split node, it's necessary to actualize the ref-pointer of the ftnfrms.
+static void lcl_ChangeFootnoteRef( SwTextNode &rNode )
+{
+ SwpHints *pSwpHints = rNode.GetpSwpHints();
+ if( !(pSwpHints && rNode.GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell()) )
+ return;
+
+ SwContentFrame* pFrame = nullptr;
+ // OD 07.11.2002 #104840# - local variable to remember first footnote
+ // of node <rNode> in order to invalidate position of its first content.
+ // Thus, in its <MakeAll()> it will checked its position relative to its reference.
+ SwFootnoteFrame* pFirstFootnoteOfNode = nullptr;
+ for( size_t j = pSwpHints->Count(); j; )
+ {
+ SwTextAttr* pHt = pSwpHints->Get(--j);
+ if (RES_TXTATR_FTN == pHt->Which())
+ {
+ if( !pFrame )
+ {
+ pFrame = SwIterator<SwContentFrame, SwTextNode, sw::IteratorMode::UnwrapMulti>(rNode).First();
+ if (!pFrame)
+ return;
+ }
+ SwTextFootnote *pAttr = static_cast<SwTextFootnote*>(pHt);
+ OSL_ENSURE( pAttr->GetStartNode(), "FootnoteAtr without StartNode." );
+ SwNodeIndex aIdx( *pAttr->GetStartNode(), 1 );
+ SwContentNode *pNd = aIdx.GetNode().GetContentNode();
+ if ( !pNd )
+ pNd = pFrame->GetAttrSet()->GetDoc()->
+ GetNodes().GoNextSection( &aIdx, true, false );
+ if ( !pNd )
+ continue;
+
+ SwIterator<SwContentFrame, SwContentNode, sw::IteratorMode::UnwrapMulti> aIter(*pNd);
+ SwContentFrame* pContent = aIter.First();
+ if( pContent )
+ {
+ OSL_ENSURE( pContent->getRootFrame() == pFrame->getRootFrame(),
+ "lcl_ChangeFootnoteRef: Layout double?" );
+ SwFootnoteFrame *pFootnote = pContent->FindFootnoteFrame();
+ if( pFootnote && pFootnote->GetAttr() == pAttr )
+ {
+ while( pFootnote->GetMaster() )
+ pFootnote = pFootnote->GetMaster();
+ // #104840# - remember footnote frame
+ pFirstFootnoteOfNode = pFootnote;
+ while ( pFootnote )
+ {
+ pFootnote->SetRef( pFrame );
+ pFootnote = pFootnote->GetFollow();
+ static_cast<SwTextFrame*>(pFrame)->SetFootnote( true );
+ }
+ }
+#if OSL_DEBUG_LEVEL > 0
+ while( nullptr != (pContent = aIter.Next()) )
+ {
+ SwFootnoteFrame *pDbgFootnote = pContent->FindFootnoteFrame();
+ OSL_ENSURE( !pDbgFootnote || pDbgFootnote->GetRef() == pFrame,
+ "lcl_ChangeFootnoteRef: Who's that guy?" );
+ }
+#endif
+ }
+ }
+ } // end of for-loop on <SwpHints>
+ // #104840# - invalidate
+ if ( pFirstFootnoteOfNode )
+ {
+ SwContentFrame* pContent = pFirstFootnoteOfNode->ContainsContent();
+ if ( pContent )
+ {
+ pContent->InvalidatePos_();
+ }
+ }
+}
+
+namespace sw {
+
+// check if there are flys on the existing frames (now on "pNode")
+// that need to be moved to the new frames of "this"
+void MoveMergedFlysAndFootnotes(std::vector<SwTextFrame*> const& rFrames,
+ SwTextNode const& rFirstNode, SwTextNode & rSecondNode,
+ bool isSplitNode)
+{
+ if (!isSplitNode)
+ {
+ lcl_ChangeFootnoteRef(rSecondNode);
+ }
+ for (SwNodeOffset nIndex = rSecondNode.GetIndex() + 1; ; ++nIndex)
+ {
+ SwNode *const pTmp(rSecondNode.GetNodes()[nIndex]);
+ if (pTmp->IsCreateFrameWhenHidingRedlines() || pTmp->IsEndNode())
+ {
+ break;
+ }
+ else if (pTmp->IsStartNode())
+ {
+ nIndex = pTmp->EndOfSectionIndex();
+ }
+ else if (pTmp->GetRedlineMergeFlag() == SwNode::Merge::NonFirst
+ && pTmp->IsTextNode())
+ {
+ lcl_ChangeFootnoteRef(*pTmp->GetTextNode());
+ }
+ }
+ for (SwTextFrame *const pFrame : rFrames)
+ {
+ if (SwSortedObjs *const pObjs = pFrame->GetDrawObjs())
+ {
+ std::vector<SwAnchoredObject*> objs;
+ objs.reserve(pObjs->size());
+ for (SwAnchoredObject *const pObj : *pObjs)
+ {
+ objs.push_back(pObj);
+ }
+ for (SwAnchoredObject *const pObj : objs)
+ {
+ SwFrameFormat & rFormat(pObj->GetFrameFormat());
+ SwFormatAnchor const& rAnchor(rFormat.GetAnchor());
+ if (rFirstNode.GetIndex() < rAnchor.GetAnchorNode()->GetIndex())
+ {
+ // move it to the new frame of "this"
+ rFormat.CallSwClientNotify(sw::LegacyModifyHint(&rAnchor, &rAnchor));
+ // note pObjs will be deleted if it becomes empty
+ assert(!pFrame->GetDrawObjs() || !pObjs->Contains(*pObj));
+ }
+ }
+ }
+ }
+}
+
+} // namespace
+
+SwTextNode *SwTextNode::SplitContentNode(const SwPosition & rPos,
+ std::function<void (SwTextNode *, sw::mark::RestoreMode, bool AtStart)> const*const pContentIndexRestore)
+{
+ bool isHide(false);
+ SwNode::Merge const eOldMergeFlag(GetRedlineMergeFlag());
+ bool parentIsOutline = IsOutline();
+
+ // create a node "in front" of me
+ const sal_Int32 nSplitPos = rPos.GetContentIndex();
+ const sal_Int32 nTextLen = m_Text.getLength();
+ SwTextNode* const pNode =
+ MakeNewTextNode( rPos.GetNode(), false, nSplitPos==nTextLen );
+
+ // the first paragraph gets the XmlId,
+ // _except_ if it is empty and the second is not empty
+ if (nSplitPos != 0) {
+ pNode->RegisterAsCopyOf(*this, true);
+ if (nSplitPos == nTextLen)
+ {
+ RemoveMetadataReference();
+ // NB: SwUndoSplitNode will call pNode->JoinNext,
+ // which is sufficient even in this case!
+ }
+ }
+
+ ResetAttr( RES_PARATR_LIST_ISRESTART );
+ ResetAttr( RES_PARATR_LIST_RESTARTVALUE );
+ ResetAttr( RES_PARATR_LIST_ISCOUNTED );
+ if ( GetNumRule() == nullptr || (parentIsOutline && !IsOutline()) )
+ {
+ ResetAttr( RES_PARATR_LIST_ID );
+ ResetAttr( RES_PARATR_LIST_LEVEL );
+ }
+
+ bool bSplitFly = false;
+ std::optional<std::vector<SwFrameFormat*>> oFlys = sw::GetFlysAnchoredAt(GetDoc(), GetIndex());
+ if (oFlys.has_value())
+ {
+ // See if one of the flys is a split fly. If so, we need to keep
+ // the potentially split text frames unchanged and create a new
+ // text frame at the end.
+ for (const auto& rFly : *oFlys)
+ {
+ if (rFly->GetFlySplit().GetValue())
+ {
+ bSplitFly = true;
+ break;
+ }
+ }
+ }
+
+ if ( HasWriterListeners() && !m_Text.isEmpty() && ((nTextLen / 2) < nSplitPos || bSplitFly) )
+ {
+ // optimization for SplitNode: If a split is at the end of a node then
+ // move the frames from the current to the new one and create new ones
+ // for the current one.
+
+ // If fly frames are moved, they don't need to destroy their layout
+ // frames. Set a flag that is checked in SwTextFlyCnt::SetAnchor.
+ if ( HasHints() )
+ {
+ pNode->GetOrCreateSwpHints().SetInSplitNode(true);
+ }
+
+ // Move the first part of the content to the new node and delete
+ // it in the old node.
+ SwContentIndex aIdx( this );
+ CutText( pNode, aIdx, nSplitPos );
+
+ if( GetWrong() )
+ {
+ pNode->SetWrong( GetWrong()->SplitList( nSplitPos ) );
+ }
+ SetWrongDirty(sw::WrongState::TODO);
+
+ if( GetGrammarCheck() )
+ {
+ pNode->SetGrammarCheck( GetGrammarCheck()->SplitGrammarList( nSplitPos ) );
+ }
+ SetGrammarCheckDirty( true );
+
+ SetWordCountDirty( true );
+
+ if( GetSmartTags() )
+ {
+ pNode->SetSmartTags( GetSmartTags()->SplitList( nSplitPos ) );
+ }
+ SetSmartTagDirty( true );
+
+ resetAndQueueAccessibilityCheck();
+ pNode->resetAndQueueAccessibilityCheck();
+
+ if ( pNode->HasHints() )
+ {
+ if ( pNode->m_pSwpHints->CanBeDeleted() )
+ {
+ pNode->m_pSwpHints.reset();
+ }
+ else
+ {
+ pNode->m_pSwpHints->SetInSplitNode(false);
+ }
+
+ // All fly frames anchored as char that are moved to the new
+ // node must have their layout frames deleted.
+ // This comment is sort of silly because we actually delete the
+ // layout frames of those which were not moved?
+ // JP 01.10.96: delete all empty and not-to-be-expanded attributes
+ if ( HasHints() )
+ {
+ for ( size_t j = m_pSwpHints->Count(); j; )
+ {
+ SwTextAttr* const pHt = m_pSwpHints->Get( --j );
+ if ( RES_TXTATR_FLYCNT == pHt ->Which() )
+ {
+ pHt->GetFlyCnt().GetFrameFormat()->DelFrames();
+ }
+ else if ( pHt->DontExpand() )
+ {
+ const sal_Int32* const pEnd = pHt->GetEnd();
+ if (pEnd && pHt->GetStart() == *pEnd )
+ {
+ // delete it!
+ m_pSwpHints->DeleteAtPos( j );
+ DestroyAttr( pHt );
+ }
+ }
+ }
+ }
+
+ }
+
+ if (pContentIndexRestore)
+ { // call before making frames and before RegisterToNode
+ (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::NonFlys, false);
+ }
+ if (eOldMergeFlag != SwNode::Merge::None)
+ { // clear before making frames and before RegisterToNode
+ SetRedlineMergeFlag(SwNode::Merge::None);
+ } // now RegisterToNode will set merge flags in both nodes properly!
+
+ std::vector<SwTextFrame*> frames;
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this);
+ for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
+ {
+ if (pFrame->getRootFrame()->HasMergedParas())
+ {
+ isHide = true;
+ }
+ frames.push_back(pFrame);
+ }
+ for (SwTextFrame * pFrame : frames)
+ {
+ pFrame->RegisterToNode( *pNode );
+ if (!pFrame->IsFollow() && pFrame->GetOffset())
+ {
+ pFrame->SetOffset( TextFrameIndex(0) );
+ }
+ }
+
+ InvalidateInSwCache(RES_ATTRSET_CHG);
+
+ if ( HasHints() )
+ {
+ MoveTextAttr_To_AttrSet();
+ }
+ // in case there are frames, the RegisterToNode has set the merge flag
+ pNode->MakeFramesForAdjacentContentNode(*this);
+ lcl_ChangeFootnoteRef( *this );
+ if (pContentIndexRestore)
+ { // call after making frames; listeners will take care of adding to the right frame
+ (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::Flys, false);
+ }
+ if (eOldMergeFlag != SwNode::Merge::None)
+ {
+ MoveMergedFlysAndFootnotes(frames, *pNode, *this, true);
+ }
+ }
+ else
+ {
+ std::unique_ptr<SwWrongList> pList = ReleaseWrong();
+ SetWrongDirty(sw::WrongState::TODO);
+
+ std::unique_ptr<SwGrammarMarkUp> pList3 = ReleaseGrammarCheck();
+ SetGrammarCheckDirty( true );
+
+ SetWordCountDirty( true );
+
+ std::unique_ptr<SwWrongList> pList2 = ReleaseSmartTags();
+ SetSmartTagDirty( true );
+
+ SwContentIndex aIdx( this );
+ CutText( pNode, aIdx, nSplitPos );
+
+ // JP 01.10.96: delete all empty and not-to-be-expanded attributes
+ if ( HasHints() )
+ {
+ for ( size_t j = m_pSwpHints->Count(); j; )
+ {
+ SwTextAttr* const pHt = m_pSwpHints->Get( --j );
+ const sal_Int32* const pEnd = pHt->GetEnd();
+ if ( pHt->DontExpand() && pEnd && (pHt->GetStart() == *pEnd) )
+ {
+ // delete it!
+ m_pSwpHints->DeleteAtPos( j );
+ DestroyAttr( pHt );
+ }
+ }
+ MoveTextAttr_To_AttrSet();
+ }
+
+ if( pList )
+ {
+ pNode->SetWrong( pList->SplitList( nSplitPos ) );
+ SetWrong( std::move(pList) );
+ }
+
+ if( pList3 )
+ {
+ pNode->SetGrammarCheck( pList3->SplitGrammarList( nSplitPos ) );
+ SetGrammarCheck( std::move(pList3) );
+ }
+
+ if( pList2 )
+ {
+ pNode->SetSmartTags( pList2->SplitList( nSplitPos ) );
+ SetSmartTags( std::move(pList2) );
+ }
+
+ resetAndQueueAccessibilityCheck();
+ pNode->resetAndQueueAccessibilityCheck();
+
+ if (pContentIndexRestore)
+ { // call before making frames and before RegisterToNode
+ (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::NonFlys, false);
+ }
+
+ std::vector<SwTextFrame*> frames;
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this);
+ for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
+ {
+ frames.push_back(pFrame);
+ if (pFrame->getRootFrame()->HasMergedParas())
+ {
+ isHide = true;
+ }
+ }
+ bool bNonMerged(false);
+ bool bRecreateThis(false);
+ for (SwTextFrame * pFrame : frames)
+ {
+ // sw_redlinehide: for this to work properly with hidden nodes,
+ // the frame needs to listen on them too.
+ // also: have to check the frame; this->GetRedlineMergeFlag()
+ // is None in case there's a delete redline inside the paragraph,
+ // but that could still result in a merged frame after split...
+ if (pFrame->GetMergedPara())
+ {
+ // Can't special case this == First here - that could (if
+ // both nodes are still merged by redline) lead to
+ // duplicate frames on "this".
+ // Update the extents with new node; also inits merge flag,
+ // so the MakeFramesForAdjacentContentNode below respects it
+ pFrame->RegisterToNode(*pNode);
+ if (nSplitPos == 0)
+ {
+ // in this case, it was not
+ // invalidated because Cut didn't sent it any hints,
+ // so we have to invalidate it here!
+ pFrame->Prepare(PrepareHint::Clear, nullptr, false);
+ }
+ if (!pFrame->GetMergedPara() ||
+ !pFrame->GetMergedPara()->listener.IsListeningTo(this))
+ {
+ // it's no longer listening - need to recreate frame
+ // (note this is idempotent, can be done once per frame)
+ SetRedlineMergeFlag(SwNode::Merge::None);
+ bRecreateThis = true;
+ }
+ }
+ else
+ {
+ bNonMerged = true;
+ }
+ }
+ assert(!(bNonMerged && bRecreateThis)); // 2 layouts not handled yet - maybe best to simply use the other branch then?
+ if (!frames.empty() && bNonMerged)
+ {
+ // the existing frame on "this" should have been updated by Cut
+ MakeFramesForAdjacentContentNode(*pNode);
+ lcl_ChangeFootnoteRef(*pNode);
+ }
+ else if (bRecreateThis)
+ {
+ assert(pNode->HasWriterListeners()); // was just moved there
+ pNode->MakeFramesForAdjacentContentNode(*this);
+ lcl_ChangeFootnoteRef(*this);
+ }
+
+ if (pContentIndexRestore)
+ { // call after making frames; listeners will take care of adding to the right frame
+ (*pContentIndexRestore)(pNode, sw::mark::RestoreMode::Flys, nSplitPos == 0);
+ }
+
+ if (bRecreateThis)
+ {
+ MoveMergedFlysAndFootnotes(frames, *pNode, *this, true);
+ }
+ }
+
+ // pNode is the previous node, 'this' is the next node from the split.
+ if (nSplitPos == nTextLen && m_pSwpHints)
+ {
+ // We just created an empty next node: avoid unwanted superscript in the new node if it's
+ // there.
+ for (size_t i = 0; i < m_pSwpHints->Count(); ++i)
+ {
+ SwTextAttr* pHt = m_pSwpHints->Get(i);
+ if (pHt->Which() != RES_TXTATR_AUTOFMT)
+ {
+ continue;
+ }
+
+ const sal_Int32* pEnd = pHt->GetEnd();
+ if (!pEnd || pHt->GetStart() != *pEnd)
+ {
+ continue;
+ }
+
+ const std::shared_ptr<SfxItemSet>& pSet = pHt->GetAutoFormat().GetStyleHandle();
+ if (!pSet || pSet->Count() != 1 || !pSet->HasItem(RES_CHRATR_ESCAPEMENT))
+ {
+ continue;
+ }
+
+ m_pSwpHints->DeleteAtPos(i);
+ SwTextAttr::Destroy(pHt, GetDoc().GetAttrPool());
+ --i;
+ }
+ }
+
+#ifndef NDEBUG
+ if (isHide) // otherwise flags won't be set anyway
+ {
+ // First
+ // -> First,NonFirst
+ // -> First,Hidden
+ // -> None,First
+ // Hidden
+ // -> Hidden,Hidden (if still inside merge rl)
+ // -> NonFirst,First (if redline was split)
+ // NonFirst
+ // -> NonFirst,First (if split after end of "incoming" redline &
+ // before start of "outgoing" redline)
+ // -> NonFirst,None (if split after end of "incoming" redline)
+ // -> NonFirst,Hidden (if split after start of "outgoing" redline)
+ // -> Hidden, NonFirst (if split before end of "incoming" redline)
+ // None
+ // -> None,None
+ // -> First,NonFirst (if splitting inside a delete redline)
+ SwNode::Merge const eFirst(pNode->GetRedlineMergeFlag());
+ SwNode::Merge const eSecond(GetRedlineMergeFlag());
+ switch (eOldMergeFlag)
+ {
+ case Merge::First:
+ assert((eFirst == Merge::First && eSecond == Merge::NonFirst)
+ || (eFirst == Merge::First && eSecond == Merge::Hidden)
+ || (eFirst == Merge::None && eSecond == Merge::First));
+ break;
+ case Merge::Hidden:
+ assert((eFirst == Merge::Hidden && eSecond == Merge::Hidden)
+ || (eFirst == Merge::NonFirst && eSecond == Merge::First)
+ // next ones can happen temp. in UndoDelete :(
+ || (eFirst == Merge::Hidden && eSecond == Merge::NonFirst)
+ || (eFirst == Merge::NonFirst && eSecond == Merge::None));
+ break;
+ case Merge::NonFirst:
+ assert((eFirst == Merge::NonFirst && eSecond == Merge::First)
+ || (eFirst == Merge::NonFirst && eSecond == Merge::None)
+ || (eFirst == Merge::NonFirst && eSecond == Merge::Hidden)
+ || (eFirst == Merge::Hidden && eSecond == Merge::NonFirst));
+ break;
+ case Merge::None:
+ assert((eFirst == Merge::None && eSecond == Merge::None)
+ || (eFirst == Merge::First && eSecond == Merge::NonFirst));
+ break;
+ }
+ }
+#else
+ (void) isHide;
+#endif
+
+ {
+ // Send Hint for PageDesc. This should be done in the Layout when
+ // pasting the frames, but that causes other problems that look
+ // expensive to solve.
+ const SwFormatPageDesc *pItem;
+ if(HasWriterListeners() && (pItem = pNode->GetSwAttrSet().GetItemIfSet(RES_PAGEDESC)))
+ pNode->TriggerNodeUpdate(sw::LegacyModifyHint(pItem, pItem));
+ }
+ return pNode;
+}
+
+void SwTextNode::MoveTextAttr_To_AttrSet()
+{
+ OSL_ENSURE( m_pSwpHints, "MoveTextAttr_To_AttrSet without SwpHints?" );
+ for ( size_t i = 0; m_pSwpHints && i < m_pSwpHints->Count(); ++i )
+ {
+ SwTextAttr *pHt = m_pSwpHints->Get(i);
+
+ if( pHt->GetStart() )
+ break;
+
+ const sal_Int32* pHtEndIdx = pHt->GetEnd();
+
+ if( !pHtEndIdx )
+ continue;
+
+ if (*pHtEndIdx < m_Text.getLength() || pHt->IsCharFormatAttr())
+ break;
+
+ if( !pHt->IsDontMoveAttr() &&
+ SetAttr( pHt->GetAttr() ) )
+ {
+ m_pSwpHints->DeleteAtPos(i);
+ DestroyAttr( pHt );
+ --i;
+ }
+ }
+
+}
+
+namespace sw {
+
+/// if first node is deleted & second survives, then the first node's frame
+/// will be deleted too; prevent this by moving the frame to the second node
+/// if necessary.
+void MoveDeletedPrevFrames(const SwTextNode & rDeletedPrev, SwTextNode & rNode)
+{
+ std::vector<SwTextFrame*> frames;
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rDeletedPrev);
+ for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
+ {
+ if (pFrame->getRootFrame()->HasMergedParas())
+ {
+ frames.push_back(pFrame);
+ }
+ }
+ {
+ auto frames2(frames);
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIt(rNode);
+ for (SwTextFrame* pFrame = aIt.First(); pFrame; pFrame = aIt.Next())
+ {
+ if (pFrame->getRootFrame()->HasMergedParas())
+ {
+ auto const it(std::find(frames2.begin(), frames2.end(), pFrame));
+ assert(it != frames2.end());
+ frames2.erase(it);
+ }
+ }
+ assert(frames2.empty());
+ }
+ for (SwTextFrame *const pFrame : frames)
+ {
+ pFrame->RegisterToNode(rNode, true);
+ }
+}
+
+// typical Join:
+// None,Node->None
+// None,First->First
+// First,NonFirst->First
+// NonFirst,First->NonFirst
+// NonFirst,None->NonFirst
+
+/// if first node is First, its frames may need to be moved, never deleted.
+/// if first node is NonFirst, second node's own frames (First/None) must be deleted
+void CheckResetRedlineMergeFlag(SwTextNode & rNode, Recreate const eRecreateMerged)
+{
+ if (eRecreateMerged != sw::Recreate::No)
+ {
+ SwTextNode * pMergeNode(&rNode);
+ if (eRecreateMerged == sw::Recreate::Predecessor
+ // tdf#135018 check that there is a predecessor node, i.e. rNode
+ // isn't the first node after the body start node
+ && rNode.GetNodes()[rNode.GetIndex() - 1]->StartOfSectionIndex() != SwNodeOffset(0))
+ {
+ for (SwNodeOffset i = rNode.GetIndex() - 1; ; --i)
+ {
+ SwNode *const pNode(rNode.GetNodes()[i]);
+ assert(!pNode->IsStartNode());
+ if (pNode->IsEndNode())
+ {
+ i = pNode->StartOfSectionIndex();
+ }
+ else if (pNode->IsTextNode())
+ {
+ pMergeNode = pNode->GetTextNode(); // use predecessor to merge
+ break;
+ }
+ }
+ }
+ std::vector<SwTextFrame*> frames;
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pMergeNode);
+ for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
+ {
+ if (pFrame->getRootFrame()->HasMergedParas())
+ {
+ frames.push_back(pFrame);
+ }
+ }
+ auto eMode(sw::FrameMode::Existing);
+ for (SwTextFrame * pFrame : frames)
+ {
+ SwTextNode & rFirstNode(pFrame->GetMergedPara()
+ ? *pFrame->GetMergedPara()->pFirstNode
+ : *pMergeNode);
+ assert(rFirstNode.GetIndex() <= rNode.GetIndex());
+ pFrame->SetMergedPara(sw::CheckParaRedlineMerge(
+ *pFrame, rFirstNode, eMode));
+ // there is no merged para in case the deleted node had one but
+ // nothing was actually hidden
+ if (pFrame->GetMergedPara())
+ {
+ assert(pFrame->GetMergedPara()->listener.IsListeningTo(&rNode));
+ assert(rNode.GetIndex() <= pFrame->GetMergedPara()->pLastNode->GetIndex());
+ // tdf#135978 Join: recreate fly frames anchored to subsequent nodes
+ if (eRecreateMerged == sw::Recreate::ThisNode)
+ {
+ AddRemoveFlysAnchoredToFrameStartingAtNode(*pFrame, rNode, nullptr);
+ }
+ }
+ eMode = sw::FrameMode::New; // Existing is not idempotent!
+ }
+ }
+ else if (rNode.GetRedlineMergeFlag() != SwNode::Merge::None)
+ {
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rNode);
+ for (SwTextFrame * pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
+ {
+ if (auto const pMergedPara = pFrame->GetMergedPara())
+ {
+ if (pMergedPara->pFirstNode == pMergedPara->pLastNode)
+ {
+ assert(pMergedPara->pFirstNode == &rNode);
+ rNode.SetRedlineMergeFlag(SwNode::Merge::None);
+ }
+ break; // checking once is enough
+ }
+ else if (pFrame->getRootFrame()->HasMergedParas())
+ {
+ rNode.SetRedlineMergeFlag(SwNode::Merge::None);
+ break; // checking once is enough
+ }
+ }
+ }
+}
+
+bool HasNumberingWhichNeedsLayoutUpdate(const SwTextNode& rTextNode)
+{
+ const SwNodeNum* pNodeNum = rTextNode.GetNum();
+ if (!pNodeNum)
+ {
+ return false;
+ }
+
+ const SwNumRule* pNumRule = pNodeNum->GetNumRule();
+ if (!pNumRule)
+ {
+ return false;
+ }
+
+ const SwNumFormat* pFormat
+ = pNumRule->GetNumFormat(o3tl::narrowing<sal_uInt16>(rTextNode.GetAttrListLevel()));
+ if (!pFormat)
+ {
+ return false;
+ }
+
+ switch (pFormat->GetNumberingType())
+ {
+ case SVX_NUM_NUMBER_NONE:
+ case SVX_NUM_CHAR_SPECIAL:
+ case SVX_NUM_BITMAP:
+ return false;
+ default:
+ return true;
+ }
+}
+} // namespace
+
+SwContentNode *SwTextNode::JoinNext()
+{
+ SwNodes& rNds = GetNodes();
+ SwNodeIndex aIdx( *this );
+ if( SwContentNode::CanJoinNext( &aIdx ) )
+ {
+ SwDoc& rDoc = rNds.GetDoc();
+ const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
+ pContentStore->Save(rDoc, aIdx.GetIndex(), SAL_MAX_INT32);
+ SwTextNode *pTextNode = aIdx.GetNode().GetTextNode();
+ sal_Int32 nOldLen = m_Text.getLength();
+
+ // METADATA: merge
+ JoinMetadatable(*pTextNode, !Len(), !pTextNode->Len());
+
+ std::unique_ptr<SwWrongList> pList = ReleaseWrong();
+ if( pList )
+ {
+ pList->JoinList( pTextNode->GetWrong(), nOldLen );
+ SetWrongDirty(sw::WrongState::TODO);
+ }
+ else
+ {
+ pList = pTextNode->ReleaseWrong();
+ if( pList )
+ {
+ pList->Move( 0, nOldLen );
+ SetWrongDirty(sw::WrongState::TODO);
+ }
+ }
+
+ std::unique_ptr<SwGrammarMarkUp> pList3 = ReleaseGrammarCheck();
+ if( pList3 )
+ {
+ pList3->JoinGrammarList( pTextNode->GetGrammarCheck(), nOldLen );
+ SetGrammarCheckDirty( true );
+ }
+ else
+ {
+ pList3 = pTextNode->ReleaseGrammarCheck();
+ if( pList3 )
+ {
+ pList3->MoveGrammar( 0, nOldLen );
+ SetGrammarCheckDirty( true );
+ }
+ }
+
+ std::unique_ptr<SwWrongList> pList2 = ReleaseSmartTags();
+ if( pList2 )
+ {
+ pList2->JoinList( pTextNode->GetSmartTags(), nOldLen );
+ SetSmartTagDirty( true );
+ }
+ else
+ {
+ pList2 = pTextNode->ReleaseSmartTags();
+ if( pList2 )
+ {
+ pList2->Move( 0, nOldLen );
+ SetSmartTagDirty( true );
+ }
+ }
+
+ { // scope for SwContentIndex
+ pTextNode->CutText( this, SwContentIndex(pTextNode), pTextNode->Len() );
+ }
+ // move all Bookmarks/TOXMarks
+ if( !pContentStore->Empty())
+ pContentStore->Restore( rDoc, GetIndex(), nOldLen );
+
+ if( pTextNode->HasAnyIndex() )
+ {
+ // move all ShellCursor/StackCursor/UnoCursor out of delete range
+ rDoc.CorrAbs( aIdx.GetNode(), SwPosition( *this ), nOldLen, true );
+ }
+ SwNode::Merge const eOldMergeFlag(pTextNode->GetRedlineMergeFlag());
+ auto eRecreateMerged(eOldMergeFlag == SwNode::Merge::First
+ ? sw::Recreate::ThisNode
+ : sw::Recreate::No);
+ if (eRecreateMerged == sw::Recreate::No)
+ {
+ // tdf#137318 if a delete is inside one node, flag is still None!
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*pTextNode);
+ for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next())
+ {
+ if (pFrame->GetMergedPara())
+ {
+ eRecreateMerged = sw::Recreate::ThisNode;
+ break;
+ }
+ }
+ }
+ bool bOldHasNumberingWhichNeedsLayoutUpdate = HasNumberingWhichNeedsLayoutUpdate(*pTextNode);
+
+ rNds.Delete(aIdx);
+ SetWrong( std::move(pList) );
+ SetGrammarCheck( std::move(pList3) );
+ SetSmartTags( std::move(pList2) );
+
+ resetAndQueueAccessibilityCheck();
+
+ if (bOldHasNumberingWhichNeedsLayoutUpdate || HasNumberingWhichNeedsLayoutUpdate(*this))
+ {
+ // Repaint all text frames that belong to this numbering to avoid outdated generated
+ // numbers.
+ InvalidateNumRule();
+ }
+
+ CheckResetRedlineMergeFlag(*this, eRecreateMerged);
+ }
+ else {
+ OSL_FAIL( "No TextNode." );
+ }
+
+ return this;
+}
+
+void SwTextNode::JoinPrev()
+{
+ SwNodes& rNds = GetNodes();
+ SwNodeIndex aIdx( *this );
+ if( SwContentNode::CanJoinPrev( &aIdx ) )
+ {
+ SwDoc& rDoc = rNds.GetDoc();
+ const std::shared_ptr<sw::mark::ContentIdxStore> pContentStore(sw::mark::ContentIdxStore::Create());
+ pContentStore->Save( rDoc, aIdx.GetIndex(), SAL_MAX_INT32);
+ SwTextNode *pTextNode = aIdx.GetNode().GetTextNode();
+ const sal_Int32 nLen = pTextNode->Len();
+
+ std::unique_ptr<SwWrongList> pList = pTextNode->ReleaseWrong();
+ if( pList )
+ {
+ pList->JoinList( GetWrong(), Len() );
+ SetWrongDirty(sw::WrongState::TODO);
+ ClearWrong();
+ }
+ else
+ {
+ pList = ReleaseWrong();
+ if( pList )
+ {
+ pList->Move( 0, nLen );
+ SetWrongDirty(sw::WrongState::TODO);
+ }
+ }
+
+ std::unique_ptr<SwGrammarMarkUp> pList3 = pTextNode->ReleaseGrammarCheck();
+ if( pList3 )
+ {
+ pList3->JoinGrammarList( GetGrammarCheck(), Len() );
+ SetGrammarCheckDirty( true );
+ ClearGrammarCheck();
+ }
+ else
+ {
+ pList3 = ReleaseGrammarCheck();
+ if( pList3 )
+ {
+ pList3->MoveGrammar( 0, nLen );
+ SetGrammarCheckDirty( true );
+ }
+ }
+
+ std::unique_ptr<SwWrongList> pList2 = pTextNode->ReleaseSmartTags();
+ if( pList2 )
+ {
+ pList2->JoinList( GetSmartTags(), Len() );
+ SetSmartTagDirty( true );
+ ClearSmartTags();
+ }
+ else
+ {
+ pList2 = ReleaseSmartTags();
+ if( pList2 )
+ {
+ pList2->Move( 0, nLen );
+ SetSmartTagDirty( true );
+ }
+ }
+
+ { // scope for SwContentIndex
+ pTextNode->CutText( this, SwContentIndex(this), SwContentIndex(pTextNode), nLen );
+ }
+ // move all Bookmarks/TOXMarks
+ if( !pContentStore->Empty() )
+ pContentStore->Restore( rDoc, GetIndex() );
+
+ if( pTextNode->HasAnyIndex() )
+ {
+ // move all ShellCursor/StackCursor/UnoCursor out of delete range
+ rDoc.CorrAbs( aIdx.GetNode(), SwPosition( *this ), nLen, true );
+ }
+ SwNode::Merge const eOldMergeFlag(pTextNode->GetRedlineMergeFlag());
+ if (eOldMergeFlag == SwNode::Merge::First
+ && !IsCreateFrameWhenHidingRedlines())
+ {
+ sw::MoveDeletedPrevFrames(*pTextNode, *this);
+ }
+ rNds.Delete(aIdx);
+ SetWrong( std::move(pList) );
+ SetGrammarCheck( std::move(pList3) );
+ SetSmartTags( std::move(pList2) );
+ resetAndQueueAccessibilityCheck();
+ InvalidateNumRule();
+ sw::CheckResetRedlineMergeFlag(*this,
+ eOldMergeFlag == SwNode::Merge::NonFirst
+ ? sw::Recreate::Predecessor
+ : sw::Recreate::No);
+ }
+ else {
+ OSL_FAIL( "No TextNode." );
+ }
+}
+
+// create an AttrSet with ranges for Frame-/Para/Char-attributes
+void SwTextNode::NewAttrSet( SwAttrPool& rPool )
+{
+ OSL_ENSURE( !mpAttrSet, "AttrSet is set after all" );
+ SwAttrSet aNewAttrSet( rPool, aTextNodeSetRange );
+
+ // put names of parent style and conditional style:
+ const SwFormatColl* pAnyFormatColl = &GetAnyFormatColl();
+ const SwFormatColl* pFormatColl = GetFormatColl();
+ OUString sVal;
+ SwStyleNameMapper::FillProgName( pAnyFormatColl->GetName(), sVal, SwGetPoolIdFromName::TxtColl );
+ SfxStringItem aAnyFormatColl( RES_FRMATR_STYLE_NAME, sVal );
+ if ( pFormatColl != pAnyFormatColl )
+ SwStyleNameMapper::FillProgName( pFormatColl->GetName(), sVal, SwGetPoolIdFromName::TxtColl );
+ SfxStringItem aFormatColl( RES_FRMATR_CONDITIONAL_STYLE_NAME, sVal );
+ aNewAttrSet.Put( aAnyFormatColl );
+ aNewAttrSet.Put( aFormatColl );
+
+ aNewAttrSet.SetParent( &pAnyFormatColl->GetAttrSet() );
+ mpAttrSet = GetDoc().GetIStyleAccess().getAutomaticStyle( aNewAttrSet, IStyleAccess::AUTO_STYLE_PARA, &sVal );
+}
+
+namespace
+{
+class SwContentNodeTmp : public SwContentNode
+{
+public:
+ SwContentNodeTmp() : SwContentNode() {}
+ virtual void NewAttrSet(SwAttrPool&) override {}
+ virtual SwContentFrame *MakeFrame(SwFrame*) override { return nullptr; }
+ virtual SwContentNode* MakeCopy(SwDoc&, SwNode&, bool /*bNewFrames*/) const override { return nullptr; };
+};
+};
+
+// override SwContentIndexReg::Update => text hints do not need SwContentIndex for start/end!
+void SwTextNode::Update(
+ SwContentIndex const & rPos,
+ const sal_Int32 nChangeLen,
+ UpdateMode const eMode)
+{
+ assert(rPos.GetContentNode() == this);
+ SetAutoCompleteWordDirty( true );
+
+ std::unique_ptr<SwpHts> pCollector;
+ const sal_Int32 nChangePos = rPos.GetIndex();
+
+ if ( HasHints() )
+ {
+ if (eMode & UpdateMode::Negative)
+ {
+ std::vector<SwTextInputField*> aTextInputFields;
+
+ const sal_Int32 nChangeEnd = nChangePos + nChangeLen;
+ for ( size_t n = 0; n < m_pSwpHints->Count(); ++n )
+ {
+ bool bTextAttrChanged = false;
+ bool bStartOfTextAttrChanged = false;
+ SwTextAttr * const pHint = m_pSwpHints->GetWithoutResorting(n);
+ if ( pHint->GetStart() > nChangePos )
+ {
+ if ( pHint->GetStart() > nChangeEnd )
+ {
+ pHint->SetStart( pHint->GetStart() - nChangeLen );
+ }
+ else
+ {
+ pHint->SetStart( nChangePos );
+ }
+ bStartOfTextAttrChanged = true;
+ }
+
+ const sal_Int32 * const pEnd = pHint->GetEnd();
+ if (pEnd && *pEnd > nChangePos )
+ {
+ if( *pEnd > nChangeEnd )
+ {
+ pHint->SetEnd(*pEnd - nChangeLen);
+ }
+ else
+ {
+ pHint->SetEnd(nChangePos);
+ }
+ bTextAttrChanged = !bStartOfTextAttrChanged;
+ }
+
+ if ( bTextAttrChanged
+ && pHint->Which() == RES_TXTATR_INPUTFIELD )
+ {
+ SwTextInputField* pTextInputField = dynamic_cast<SwTextInputField*>(pHint);
+ if ( pTextInputField )
+ aTextInputFields.push_back(pTextInputField);
+ }
+ }
+
+ //wait until all the attribute positions are correct
+ //before updating the field contents
+ for (SwTextInputField* pTextInputField : aTextInputFields)
+ {
+ pTextInputField->UpdateFieldContent();
+ }
+
+ m_pSwpHints->MergePortions( *this );
+ }
+ else
+ {
+ bool bNoExp = false;
+ bool bResort = false;
+ bool bMergePortionsNeeded = false;
+ const int coArrSz = RES_TXTATR_WITHEND_END - RES_CHRATR_BEGIN;
+ std::vector<SwTextInputField*> aTextInputFields;
+
+ bool aDontExp[ coArrSz ] = {};
+
+ for ( size_t n = 0; n < m_pSwpHints->Count(); ++n )
+ {
+ bool bTextAttrChanged = false;
+ SwTextAttr * const pHint = m_pSwpHints->GetWithoutResorting(n);
+ const sal_Int32 * const pEnd = pHint->GetEnd();
+ if ( pHint->GetStart() >= nChangePos )
+ {
+ pHint->SetStart( pHint->GetStart() + nChangeLen );
+ if ( pEnd )
+ {
+ pHint->SetEnd(*pEnd + nChangeLen);
+ }
+ }
+ else if ( pEnd && (*pEnd >= nChangePos) )
+ {
+ if ( (*pEnd > nChangePos) || IsIgnoreDontExpand() )
+ {
+ pHint->SetEnd(*pEnd + nChangeLen);
+ bTextAttrChanged = true;
+ }
+ else // *pEnd == nChangePos
+ {
+ const sal_uInt16 nWhich = pHint->Which();
+
+ OSL_ENSURE(!isCHRATR(nWhich), "Update: char attr hint?");
+ if (!(isCHRATR(nWhich) || isTXTATR_WITHEND(nWhich)))
+ continue;
+
+ const sal_uInt16 nWhPos = nWhich - RES_CHRATR_BEGIN;
+
+ if( aDontExp[ nWhPos ] )
+ continue;
+
+ if ( pHint->DontExpand() )
+ {
+ pHint->SetDontExpand( false );
+ bResort = true;
+ // could have a continuation with IgnoreStart()...
+ if (pHint->IsFormatIgnoreEnd())
+ {
+ bMergePortionsNeeded = true;
+ }
+ if ( pHint->IsCharFormatAttr() )
+ {
+ bNoExp = true;
+ aDontExp[ RES_TXTATR_CHARFMT - RES_CHRATR_BEGIN ] = true;
+ aDontExp[ RES_TXTATR_INETFMT - RES_CHRATR_BEGIN ] = true;
+ }
+ else
+ aDontExp[ nWhPos ] = true;
+ }
+ else if( bNoExp )
+ {
+ if (!pCollector)
+ {
+ pCollector.reset( new SwpHts );
+ }
+ auto it = std::find_if(pCollector->begin(), pCollector->end(),
+ [nWhich](const SwTextAttr *pTmp) { return nWhich == pTmp->Which(); });
+ if (it != pCollector->end())
+ {
+ SwTextAttr *pTmp = *it;
+ pCollector->erase( it );
+ SwTextAttr::Destroy( pTmp, GetDoc().GetAttrPool() );
+ }
+ SwTextAttr * const pTmp =
+ MakeTextAttr( GetDoc(),
+ pHint->GetAttr(), nChangePos, nChangePos + nChangeLen);
+ pCollector->push_back( pTmp );
+ }
+ else
+ {
+ pHint->SetEnd(*pEnd + nChangeLen);
+ bTextAttrChanged = true;
+ }
+ }
+ }
+
+ if ( bTextAttrChanged
+ && pHint->Which() == RES_TXTATR_INPUTFIELD )
+ {
+ SwTextInputField* pTextInputField = dynamic_cast<SwTextInputField*>(pHint);
+ if ( pTextInputField )
+ aTextInputFields.push_back(pTextInputField);
+ }
+ }
+
+ //wait until all the attribute positions are correct
+ //before updating the field contents
+ for (SwTextInputField* pTextInputField : aTextInputFields)
+ {
+ pTextInputField->UpdateFieldContent();
+ }
+
+ if (bMergePortionsNeeded)
+ {
+ m_pSwpHints->MergePortions(*this); // does Resort too
+ }
+ else if (bResort)
+ {
+ m_pSwpHints->Resort();
+ }
+ }
+ }
+
+ bool bSortMarks = false;
+ SwContentNodeTmp aTmpIdxReg;
+ if (!(eMode & UpdateMode::Negative) && !(eMode & UpdateMode::Delete))
+ {
+ std::vector<SwRangeRedline*> vMyRedlines;
+ // walk the list of SwIndex attached to me and see if any of them are redlines
+ const SwContentIndex* pContentNodeIndex = GetFirstIndex();
+ while (pContentNodeIndex)
+ {
+ SwRangeRedline* pRedl = pContentNodeIndex->GetRedline();
+ if (pRedl)
+ vMyRedlines.push_back(pRedl);
+ pContentNodeIndex = pContentNodeIndex->GetNext();
+ }
+ std::sort(vMyRedlines.begin(), vMyRedlines.end());
+ vMyRedlines.erase( std::unique( vMyRedlines.begin(), vMyRedlines.end() ), vMyRedlines.end() );
+ for (SwRangeRedline* pRedl : vMyRedlines)
+ {
+ if ( pRedl->HasMark() )
+ {
+ SwPosition* const pEnd = pRedl->End();
+ if ( *this == pEnd->GetNode() &&
+ *pRedl->GetPoint() != *pRedl->GetMark() )
+ {
+ SwContentIndex & rIdx = pEnd->nContent;
+ if (nChangePos == rIdx.GetIndex())
+ {
+ rIdx.Assign( &aTmpIdxReg, rIdx.GetIndex() );
+ }
+ }
+ }
+ else if ( this == &pRedl->GetPoint()->GetNode() )
+ {
+ SwContentIndex & rIdx = pRedl->GetPoint()->nContent;
+ if (nChangePos == rIdx.GetIndex())
+ {
+ rIdx.Assign( &aTmpIdxReg, rIdx.GetIndex() );
+ }
+ // the unused position must not be on a SwTextNode
+ bool const isOneUsed(&pRedl->GetBound() == pRedl->GetPoint());
+ assert(!pRedl->GetBound(!isOneUsed).GetNode().IsTextNode());
+ assert(!pRedl->GetBound(!isOneUsed).GetContentNode()); (void)isOneUsed;
+ }
+ }
+
+ // Bookmarks must never grow to either side, when editing (directly)
+ // to the left or right (i#29942)! Exception: if the bookmark has
+ // 2 positions and start == end, then expand it (tdf#96479)
+ if (!(eMode & UpdateMode::Replace)) // Exception: Replace
+ {
+ bool bAtLeastOneBookmarkMoved = false;
+ bool bAtLeastOneExpandedBookmarkAtInsertionPosition = false;
+ // A text node already knows its marks via its SwContentIndexes.
+ o3tl::sorted_vector<const sw::mark::IMark*> aSeenMarks;
+ const SwContentIndex* next;
+ for (const SwContentIndex* pIndex = GetFirstIndex(); pIndex; pIndex = next )
+ {
+ next = pIndex->GetNext();
+ const sw::mark::IMark* pMark = pIndex->GetMark();
+ if (!pMark)
+ continue;
+ // Only handle bookmarks once, if they start and end at this node as well.
+ if (!aSeenMarks.insert(pMark).second)
+ continue;
+ const SwPosition* pEnd = &pMark->GetMarkEnd();
+ SwContentIndex & rEndIdx = const_cast<SwContentIndex&>(pEnd->nContent);
+ if( *this == pEnd->GetNode() &&
+ rPos.GetIndex() == rEndIdx.GetIndex() )
+ {
+ if (&rEndIdx == next) // nasty corner case:
+ { // don't switch to iterating aTmpIdxReg!
+ next = rEndIdx.GetNext();
+ }
+ // tdf#96479: if start == end, ignore the other position
+ // so it is moved!
+ rEndIdx.Assign( &aTmpIdxReg, rEndIdx.GetIndex() );
+ bAtLeastOneBookmarkMoved = true;
+ }
+ else if ( !bAtLeastOneExpandedBookmarkAtInsertionPosition )
+ {
+ if ( pMark->IsExpanded() )
+ {
+ const SwPosition* pStart = &pMark->GetMarkStart();
+ if ( this == &pStart->GetNode()
+ && rPos.GetIndex() == pStart->GetContentIndex() )
+ {
+ bAtLeastOneExpandedBookmarkAtInsertionPosition = true;
+ }
+ }
+ }
+ }
+
+ bSortMarks = bAtLeastOneBookmarkMoved && bAtLeastOneExpandedBookmarkAtInsertionPosition;
+ }
+
+ // at-char anchored flys shouldn't be moved, either.
+ if (!m_bInUndo)
+ {
+ std::vector<SwFrameFormat*> const& rFlys(GetAnchoredFlys());
+ for (size_t i = 0; i != rFlys.size(); ++i)
+ {
+ SwFrameFormat const*const pFormat = rFlys[i];
+ const SwFormatAnchor& rAnchor = pFormat->GetAnchor();
+ const SwNode* pAnchorNode = rAnchor.GetAnchorNode();
+ if (rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR && pAnchorNode)
+ {
+ // The fly is at-char anchored and has an anchor position.
+ SwContentIndex& rEndIdx = const_cast<SwContentIndex&>(rAnchor.GetContentAnchor()->nContent);
+ if (*pAnchorNode == *this && rEndIdx.GetIndex() == rPos.GetIndex())
+ {
+ // The anchor position is exactly our insert position.
+ rEndIdx.Assign(&aTmpIdxReg, rEndIdx.GetIndex());
+ }
+ }
+ }
+ }
+
+ // The cursors of other shells shouldn't be moved, either.
+ if (SwDocShell* pDocShell = GetDoc().GetDocShell())
+ {
+ if (pDocShell->GetWrtShell())
+ {
+ for (SwViewShell& rShell : pDocShell->GetWrtShell()->GetRingContainer())
+ {
+ auto pWrtShell = dynamic_cast<SwWrtShell*>(&rShell);
+ if (!pWrtShell || pWrtShell == pDocShell->GetWrtShell())
+ continue;
+
+ SwShellCursor* pCursor = pWrtShell->GetCursor_();
+ if (!pCursor)
+ continue;
+
+ SwContentIndex& rIndex = pCursor->Start()->nContent;
+ if (pCursor->Start()->GetNode() == *this && rIndex.GetIndex() == rPos.GetIndex())
+ {
+ // The cursor position of this other shell is exactly our insert position.
+ rIndex.Assign(&aTmpIdxReg, rIndex.GetIndex());
+ }
+ }
+ }
+ }
+ }
+
+ // base class
+ SwContentIndexReg::Update(rPos, nChangeLen, eMode);
+
+ if (pCollector)
+ {
+ const size_t nCount = pCollector->size();
+ for ( size_t i = 0; i < nCount; ++i )
+ {
+ m_pSwpHints->TryInsertHint( (*pCollector)[ i ], *this );
+ }
+ }
+
+ aTmpIdxReg.MoveTo( *this );
+ if ( bSortMarks )
+ {
+ getIDocumentMarkAccess()->assureSortedMarkContainers();
+ }
+
+ //Any drawing objects anchored into this text node may be sorted by their
+ //anchor position which may have changed here, so resort them
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> iter(*this);
+ for (SwTextFrame* pFrame = iter.First(); pFrame; pFrame = iter.Next())
+ {
+ SwSortedObjs * pSortedObjs(pFrame->GetDrawObjs());
+ if (pSortedObjs)
+ {
+ pSortedObjs->UpdateAll();
+ }
+ // also sort the objs on the page frame
+ if (SwPageFrame *pPage = pFrame->FindPageFrame())
+ pSortedObjs = pPage->GetSortedObjs();
+
+ if (pSortedObjs) // doesn't exist yet if called for inserting as-char fly
+ {
+ pSortedObjs->UpdateAll();
+ }
+ }
+
+ // Update the paragraph signatures.
+ if (SwEditShell* pEditShell = GetDoc().GetEditShell())
+ {
+ pEditShell->ValidateParagraphSignatures(this, true);
+ }
+
+ // Inform LOK clients about change in position of redlines (if any)
+ // Don't emit notifications during save: redline flags are temporarily changed during save, but
+ // it's not useful to let clients know about such changes.
+ if (!comphelper::LibreOfficeKit::isActive() || GetDoc().IsInWriting())
+ return;
+
+ const SwRedlineTable& rTable = GetDoc().getIDocumentRedlineAccess().GetRedlineTable();
+ for (SwRedlineTable::size_type nRedlnPos = 0; nRedlnPos < rTable.size(); ++nRedlnPos)
+ {
+ SwRangeRedline* pRedln = rTable[nRedlnPos];
+ if (pRedln->HasMark())
+ {
+ if (*this == pRedln->End()->GetNode() && *pRedln->GetPoint() != *pRedln->GetMark())
+ {
+ // Redline is changed only when some change occurs before it
+ if (nChangePos <= pRedln->Start()->GetContentIndex())
+ {
+ SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, pRedln);
+ }
+ }
+ }
+ else if (this == &pRedln->GetPoint()->GetNode())
+ SwRedlineTable::LOKRedlineNotification(RedlineNotification::Modify, pRedln);
+ }
+}
+
+void SwTextNode::ChgTextCollUpdateNum( const SwTextFormatColl *pOldColl,
+ const SwTextFormatColl *pNewColl)
+{
+ SwDoc& rDoc = GetDoc();
+ // query the OutlineLevel and if it changed, notify the Nodes-Array!
+ const int nOldLevel = pOldColl && pOldColl->IsAssignedToListLevelOfOutlineStyle() ?
+ pOldColl->GetAssignedOutlineStyleLevel() : MAXLEVEL;
+ const int nNewLevel = pNewColl && pNewColl->IsAssignedToListLevelOfOutlineStyle() ?
+ pNewColl->GetAssignedOutlineStyleLevel() : MAXLEVEL;
+
+ if ( MAXLEVEL != nNewLevel && -1 != nNewLevel )
+ {
+ SetAttrListLevel(nNewLevel);
+ }
+ rDoc.GetNodes().UpdateOutlineNode(*this);
+
+ SwNodes& rNds = GetNodes();
+ // If Level 0 (Chapter), update the footnotes!
+ if( ( !nNewLevel || !nOldLevel) && !rDoc.GetFootnoteIdxs().empty() &&
+ FTNNUM_CHAPTER == rDoc.GetFootnoteInfo().m_eNum &&
+ rNds.IsDocNodes() )
+ {
+ rDoc.GetFootnoteIdxs().UpdateFootnote( *rNds[GetIndex()] );
+ }
+
+ if( pNewColl && RES_CONDTXTFMTCOLL == pNewColl->Which() )
+ {
+ // check the condition of the text node again
+ ChkCondColl();
+ }
+}
+
+// If positioned exactly at the end of a CharStyle or Hyperlink,
+// set its DontExpand flag.
+bool SwTextNode::DontExpandFormat( sal_Int32 nIdx, bool bFlag,
+ bool bFormatToTextAttributes )
+{
+ if (bFormatToTextAttributes && nIdx == m_Text.getLength())
+ {
+ FormatToTextAttr( this );
+ }
+
+ bool bRet = false;
+ if ( HasHints() )
+ {
+ m_pSwpHints->SortIfNeedBe();
+ int nPos = m_pSwpHints->GetLastPosSortedByEnd(nIdx);
+ for ( ; nPos >= 0; --nPos)
+ {
+ SwTextAttr *pTmp = m_pSwpHints->GetSortedByEnd( nPos );
+ const sal_Int32 *pEnd = pTmp->GetEnd();
+ if( !pEnd )
+ continue;
+ assert( *pEnd <= nIdx );
+ if( nIdx != *pEnd )
+ break;
+ if( bFlag != pTmp->DontExpand() && !pTmp->IsLockExpandFlag()
+ && *pEnd > pTmp->GetStart())
+ {
+ bRet = true;
+ m_pSwpHints->NoteInHistory( pTmp );
+ pTmp->SetDontExpand( bFlag );
+ }
+ }
+ }
+ return bRet;
+}
+
+static bool lcl_GetTextAttrDefault(sal_Int32 nIndex, sal_Int32 nHintStart, sal_Int32 nHintEnd)
+{
+ return ((nHintStart <= nIndex) && (nIndex < nHintEnd));
+}
+static bool lcl_GetTextAttrExpand(sal_Int32 nIndex, sal_Int32 nHintStart, sal_Int32 nHintEnd)
+{
+ return ((nHintStart < nIndex) && (nIndex <= nHintEnd));
+}
+static bool lcl_GetTextAttrParent(sal_Int32 nIndex, sal_Int32 nHintStart, sal_Int32 nHintEnd)
+{
+ return ((nHintStart < nIndex) && (nIndex < nHintEnd));
+}
+
+static void
+lcl_GetTextAttrs(
+ std::vector<SwTextAttr *> *const pVector,
+ SwTextAttr **const ppTextAttr,
+ SwpHints const *const pSwpHints,
+ sal_Int32 const nIndex, sal_uInt16 const nWhich,
+ ::sw::GetTextAttrMode const eMode)
+{
+ assert(nWhich >= RES_TXTATR_BEGIN && nWhich < RES_TXTATR_END);
+ if (!pSwpHints)
+ return;
+ size_t const nSize = pSwpHints->Count();
+ sal_Int32 nPreviousIndex(0); // index of last hint with nWhich
+ bool (*pMatchFunc)(sal_Int32, sal_Int32, sal_Int32)=nullptr;
+ switch (eMode)
+ {
+ case ::sw::GetTextAttrMode::Default: pMatchFunc = &lcl_GetTextAttrDefault;
+ break;
+ case ::sw::GetTextAttrMode::Expand: pMatchFunc = &lcl_GetTextAttrExpand;
+ break;
+ case ::sw::GetTextAttrMode::Parent: pMatchFunc = &lcl_GetTextAttrParent;
+ break;
+ default: assert(false);
+ }
+
+ for( size_t i = pSwpHints->GetFirstPosSortedByWhichAndStart(nWhich); i < nSize; ++i )
+ {
+ SwTextAttr *const pHint = pSwpHints->GetSortedByWhichAndStart(i);
+ if (pHint->Which() != nWhich)
+ break; // hints are sorted by which&start, so we are done...
+
+ sal_Int32 const nHintStart = pHint->GetStart();
+ if (nIndex < nHintStart)
+ break; // hints are sorted by which&start, so we are done...
+
+ sal_Int32 const*const pEndIdx = pHint->GetEnd();
+ // cannot have hint with no end and no dummy char
+ assert(pEndIdx || pHint->HasDummyChar());
+ // If EXPAND is set, simulate the text input behavior, i.e.
+ // move the start, and expand the end.
+ bool const bContained( pEndIdx
+ ? (*pMatchFunc)(nIndex, nHintStart, *pEndIdx)
+ : (nHintStart == nIndex) );
+ if (bContained)
+ {
+ if (pVector)
+ {
+ if (nPreviousIndex < nHintStart)
+ {
+ pVector->clear(); // clear hints that are outside pHint
+ nPreviousIndex = nHintStart;
+ }
+ pVector->push_back(pHint);
+ }
+ else
+ {
+ *ppTextAttr = pHint; // and possibly overwrite outer hint
+ }
+ if (!pEndIdx)
+ {
+ break;
+ }
+ }
+ }
+}
+
+std::vector<SwTextAttr *>
+SwTextNode::GetTextAttrsAt(sal_Int32 const nIndex, sal_uInt16 const nWhich) const
+{
+ assert(nWhich >= RES_TXTATR_BEGIN && nWhich < RES_TXTATR_END);
+ std::vector<SwTextAttr *> ret;
+ lcl_GetTextAttrs(&ret, nullptr, m_pSwpHints.get(), nIndex, nWhich, ::sw::GetTextAttrMode::Default);
+ return ret;
+}
+
+SwTextAttr *
+SwTextNode::GetTextAttrAt(sal_Int32 const nIndex, sal_uInt16 const nWhich,
+ ::sw::GetTextAttrMode const eMode) const
+{
+ assert( (nWhich == RES_TXTATR_META)
+ || (nWhich == RES_TXTATR_METAFIELD)
+ || (nWhich == RES_TXTATR_AUTOFMT)
+ || (nWhich == RES_TXTATR_INETFMT)
+ || (nWhich == RES_TXTATR_CJK_RUBY)
+ || (nWhich == RES_TXTATR_UNKNOWN_CONTAINER)
+ || (nWhich == RES_TXTATR_CONTENTCONTROL)
+ || (nWhich == RES_TXTATR_INPUTFIELD ) );
+ // "GetTextAttrAt() will give wrong result for this hint!")
+
+ SwTextAttr * pRet(nullptr);
+ lcl_GetTextAttrs(nullptr, & pRet, m_pSwpHints.get(), nIndex, nWhich, eMode);
+ return pRet;
+}
+
+const SwTextInputField* SwTextNode::GetOverlappingInputField( const SwTextAttr& rTextAttr ) const
+{
+ const SwTextInputField* pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt(rTextAttr.GetStart(), RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent));
+
+ if ( pTextInputField == nullptr && rTextAttr.End() != nullptr )
+ {
+ pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt(*(rTextAttr.End()), RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent));
+ }
+
+ return pTextInputField;
+}
+
+void SwTextNode::DelFrames_TextNodePart()
+{
+ SetWrong( nullptr );
+ SetWrongDirty(sw::WrongState::TODO);
+
+ SetGrammarCheck( nullptr );
+ SetGrammarCheckDirty( true );
+
+ SetSmartTags( nullptr );
+ SetSmartTagDirty( true );
+
+ SetWordCountDirty( true );
+ SetAutoCompleteWordDirty( true );
+}
+
+SwTextField* SwTextNode::GetFieldTextAttrAt(
+ const sal_Int32 nIndex,
+ ::sw::GetTextAttrMode const eMode) const
+{
+ SwTextField* pTextField = dynamic_cast<SwTextField*>(GetTextAttrForCharAt( nIndex, RES_TXTATR_FIELD ));
+ if ( pTextField == nullptr )
+ {
+ pTextField = dynamic_cast<SwTextField*>(GetTextAttrForCharAt( nIndex, RES_TXTATR_ANNOTATION ));
+ }
+ if ( pTextField == nullptr )
+ {
+ pTextField =
+ dynamic_cast<SwTextField*>( GetTextAttrAt(
+ nIndex,
+ RES_TXTATR_INPUTFIELD,
+ eMode));
+ }
+
+ return pTextField;
+}
+
+static SwCharFormat* lcl_FindCharFormat( const SwCharFormats* pCharFormats, std::u16string_view rName )
+{
+ if( !rName.empty() )
+ {
+ const size_t nArrLen = pCharFormats->size();
+ for( size_t i = 1; i < nArrLen; i++ )
+ {
+ SwCharFormat* pFormat = (*pCharFormats)[ i ];
+ if( pFormat->GetName()==rName )
+ return pFormat;
+ }
+ }
+ return nullptr;
+}
+
+static void lcl_CopyHint(
+ const sal_uInt16 nWhich,
+ const SwTextAttr * const pHt,
+ SwTextAttr *const pNewHt,
+ SwDoc *const pOtherDoc,
+ SwTextNode *const pDest )
+{
+ assert(nWhich == pHt->Which()); // wrong hint-id
+ switch( nWhich )
+ {
+ // copy nodesarray section with footnote content
+ case RES_TXTATR_FTN :
+ assert(pDest); // "lcl_CopyHint: no destination text node?"
+ static_cast<const SwTextFootnote*>(pHt)->CopyFootnote( *static_cast<SwTextFootnote*>(pNewHt), *pDest);
+ break;
+
+ // Fields that are copied into different SwDocs must be registered
+ // at their new FieldTypes.
+
+ case RES_TXTATR_FIELD :
+ {
+ if( pOtherDoc != nullptr )
+ {
+ static_txtattr_cast<const SwTextField*>(pHt)->CopyTextField(
+ static_txtattr_cast<SwTextField*>(pNewHt));
+ }
+
+ // Table Formula must be copied relative.
+ const SwFormatField& rField = pHt->GetFormatField();
+ if( SwFieldIds::Table == rField.GetField()->GetTyp()->Which()
+ && static_cast<const SwTableField*>(rField.GetField())->IsIntrnlName())
+ {
+ // convert internal formula to external
+ const SwTableNode* const pDstTableNd =
+ static_txtattr_cast<const SwTextField*>(pHt)->GetTextNode().FindTableNode();
+ if( pDstTableNd )
+ {
+ SwTableField* const pTableField =
+ const_cast<SwTableField*>(static_cast<const SwTableField*>(
+ pNewHt->GetFormatField().GetField()));
+ pTableField->PtrToBoxNm( &pDstTableNd->GetTable() );
+ }
+ }
+ }
+ break;
+
+ case RES_TXTATR_INPUTFIELD :
+ case RES_TXTATR_ANNOTATION :
+ if( pOtherDoc != nullptr )
+ {
+ static_txtattr_cast<const SwTextField*>(pHt)->CopyTextField(
+ static_txtattr_cast<SwTextField*>(pNewHt));
+ }
+ break;
+
+ case RES_TXTATR_TOXMARK :
+ if( pOtherDoc && pDest && pDest->GetpSwpHints()
+ && pDest->GetpSwpHints()->Contains( pNewHt ) )
+ {
+ // ToXMarks that are copied to different SwDocs must register
+ // at their new ToX (sw::BroadcastingModify).
+ static_txtattr_cast<SwTextTOXMark*>(pNewHt)->CopyTOXMark(*pOtherDoc);
+ }
+ break;
+
+ case RES_TXTATR_CHARFMT :
+ // For CharacterStyles, the format must be copied too.
+ if( pDest && pDest->GetpSwpHints()
+ && pDest->GetpSwpHints()->Contains( pNewHt ) )
+ {
+ SwCharFormat* pFormat = pHt->GetCharFormat().GetCharFormat();
+
+ if (pOtherDoc)
+ {
+ pFormat = pOtherDoc->CopyCharFormat( *pFormat );
+ }
+ const_cast<SwFormatCharFormat&>(
+ pNewHt->GetCharFormat() ).SetCharFormat( pFormat );
+ }
+ break;
+ case RES_TXTATR_INETFMT :
+ {
+ // For Hyperlinks, the format must be copied too.
+ if( pOtherDoc && pDest && pDest->GetpSwpHints()
+ && pDest->GetpSwpHints()->Contains( pNewHt ) )
+ {
+ const SwDoc& rDoc = static_txtattr_cast<
+ const SwTextINetFormat*>(pHt)->GetTextNode().GetDoc();
+ const SwCharFormats* pCharFormats = rDoc.GetCharFormats();
+ const SwFormatINetFormat& rFormat = pHt->GetINetFormat();
+ SwCharFormat* pFormat;
+ pFormat = lcl_FindCharFormat( pCharFormats, rFormat.GetINetFormat() );
+ if( pFormat )
+ pOtherDoc->CopyCharFormat( *pFormat );
+ pFormat = lcl_FindCharFormat( pCharFormats, rFormat.GetVisitedFormat() );
+ if( pFormat )
+ pOtherDoc->CopyCharFormat( *pFormat );
+ }
+ //JP 24.04.98: The attribute must point to a text node, so that
+ // the styles can be created.
+ SwTextINetFormat *const pINetHt = static_txtattr_cast<SwTextINetFormat*>(pNewHt);
+ if ( !pINetHt->GetpTextNode() )
+ {
+ pINetHt->ChgTextNode( pDest );
+ }
+
+ //JP 22.10.97: set up link to char style
+ pINetHt->GetCharFormat();
+ break;
+ }
+ case RES_TXTATR_META:
+ case RES_TXTATR_METAFIELD:
+ OSL_ENSURE( pNewHt, "copying Meta should not fail!" );
+ OSL_ENSURE( pDest
+ && (CH_TXTATR_INWORD == pDest->GetText()[pNewHt->GetStart()]),
+ "missing CH_TXTATR?");
+ break;
+ }
+}
+
+/// copy attributes at position nTextStartIdx to node pDest
+// BP 7.6.93: Intentionally copy only attributes _with_ EndIdx!
+// CopyAttr is usually called when attributes are set on a
+// node with no text.
+void SwTextNode::CopyAttr( SwTextNode *pDest, const sal_Int32 nTextStartIdx,
+ const sal_Int32 nOldPos )
+{
+ if ( HasHints() )
+ {
+ SwDoc* const pOtherDoc = (&pDest->GetDoc() != &GetDoc()) ?
+ &pDest->GetDoc() : nullptr;
+
+ for ( size_t i = 0; i < m_pSwpHints->Count(); ++i )
+ {
+ SwTextAttr *const pHt = m_pSwpHints->Get(i);
+ sal_Int32 const nAttrStartIdx = pHt->GetStart();
+ if ( nTextStartIdx < nAttrStartIdx )
+ break; // beyond end of text, because nLen == 0
+
+ const sal_Int32 *const pEndIdx = pHt->GetEnd();
+ if ( pEndIdx && !pHt->HasDummyChar() )
+ {
+ sal_uInt16 const nWhich = pHt->Which();
+ if (RES_TXTATR_INPUTFIELD != nWhich // fdo#74981 skip fields
+ && ( *pEndIdx > nTextStartIdx
+ || (*pEndIdx == nTextStartIdx
+ && nAttrStartIdx == nTextStartIdx)))
+ {
+ if ( RES_TXTATR_REFMARK != nWhich )
+ {
+ // attribute in the area => copy
+ SwTextAttr *const pNewHt =
+ pDest->InsertItem( pHt->GetAttr(), nOldPos, nOldPos, SetAttrMode::IS_COPY);
+ if ( pNewHt )
+ {
+ lcl_CopyHint( nWhich, pHt, pNewHt,
+ pOtherDoc, pDest );
+ }
+ }
+ else if( !pOtherDoc
+ ? GetDoc().IsCopyIsMove()
+ : nullptr == pOtherDoc->GetRefMark( pHt->GetRefMark().GetRefName() ) )
+ {
+ pDest->InsertItem(
+ pHt->GetAttr(), nOldPos, nOldPos, SetAttrMode::IS_COPY);
+ }
+ }
+ }
+ }
+ }
+
+ if( this != pDest )
+ {
+ // notify layout frames, to prevent disappearance of footnote numbers
+ SwUpdateAttr aHint(
+ nOldPos,
+ nOldPos,
+ 0);
+
+ pDest->TriggerNodeUpdate(sw::LegacyModifyHint(&aHint, &aHint));
+ }
+}
+
+/// copy text and attributes to node pDest
+void SwTextNode::CopyText( SwTextNode *const pDest,
+ const SwContentIndex &rStart,
+ const sal_Int32 nLen,
+ const bool bForceCopyOfAllAttrs )
+{
+ SwContentIndex const aIdx( pDest, pDest->m_Text.getLength() );
+ CopyText( pDest, aIdx, rStart, nLen, bForceCopyOfAllAttrs );
+}
+
+void SwTextNode::CopyText( SwTextNode *const pDest,
+ const SwContentIndex &rDestStart,
+ const SwPosition &rStart,
+ sal_Int32 nLen,
+ const bool bForceCopyOfAllAttrs )
+{
+ CopyText( pDest, rDestStart, rStart.nContent, nLen, bForceCopyOfAllAttrs );
+}
+
+void SwTextNode::CopyText( SwTextNode *const pDest,
+ const SwContentIndex &rDestStart,
+ const SwContentIndex &rStart,
+ sal_Int32 nLen,
+ const bool bForceCopyOfAllAttrs )
+{
+ CHECK_SWPHINTS_IF_FRM(this);
+ CHECK_SWPHINTS(pDest);
+ assert(rDestStart.GetContentNode() == pDest);
+ assert(rStart.GetContentNode() == this);
+ sal_Int32 nTextStartIdx = rStart.GetIndex();
+ sal_Int32 nDestStart = rDestStart.GetIndex(); // remember old Pos
+
+ if (pDest->GetDoc().IsClipBoard() && GetNum())
+ {
+ // #i111677# cache expansion of source (for clipboard)
+ pDest->m_oNumStringCache = (nTextStartIdx != 0)
+ ? OUString() // fdo#49076: numbering only if copy from para start
+ : GetNumString();
+ }
+
+ if( !nLen )
+ {
+ // if no length is given, copy attributes at position rStart
+ CopyAttr( pDest, nTextStartIdx, nDestStart );
+
+ // copy hard attributes on whole paragraph
+ if( HasSwAttrSet() )
+ {
+ // i#96213 all or just the Char attributes?
+ if ( !bForceCopyOfAllAttrs &&
+ ( nDestStart ||
+ pDest->HasSwAttrSet() ||
+ nLen != pDest->GetText().getLength()))
+ {
+ SfxItemSetFixed<
+ RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
+ RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT,
+ RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>
+ aCharSet( pDest->GetDoc().GetAttrPool() );
+ aCharSet.Put( *GetpSwAttrSet() );
+ if( aCharSet.Count() )
+ {
+ pDest->SetAttr( aCharSet, nDestStart, nDestStart );
+ }
+ }
+ else
+ {
+ GetpSwAttrSet()->CopyToModify( *pDest );
+ }
+ }
+ return;
+ }
+
+ // 1. copy text
+ const sal_Int32 oldLen = pDest->m_Text.getLength();
+ // JP 15.02.96: missing attribute handling at the end!
+ // hence call InsertText and don't modify m_Text directly
+ pDest->InsertText( m_Text.copy(nTextStartIdx, nLen), rDestStart,
+ SwInsertFlags::EMPTYEXPAND );
+
+ // update with actual new size
+ nLen = pDest->m_Text.getLength() - oldLen;
+ if ( !nLen ) // string not longer?
+ return;
+
+ SwDoc* const pOtherDoc = (&pDest->GetDoc() != &GetDoc()) ? &pDest->GetDoc() : nullptr;
+
+ // copy hard attributes on whole paragraph
+ if( HasSwAttrSet() )
+ {
+ // i#96213 all or just the Char attributes?
+ if ( !bForceCopyOfAllAttrs &&
+ ( nDestStart ||
+ pDest->HasSwAttrSet() ||
+ nLen != pDest->GetText().getLength()))
+ {
+ SfxItemSetFixed<
+ RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
+ RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT,
+ RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>
+ aCharSet( pDest->GetDoc().GetAttrPool() );
+ aCharSet.Put( *GetpSwAttrSet() );
+ if( aCharSet.Count() )
+ {
+ pDest->SetAttr( aCharSet, nDestStart, nDestStart + nLen );
+ }
+ }
+ else
+ {
+ GetpSwAttrSet()->CopyToModify( *pDest );
+ }
+ }
+
+ bool const bUndoNodes = !pOtherDoc
+ && GetDoc().GetIDocumentUndoRedo().IsUndoNodes(GetNodes());
+
+ // Fetch end only now, because copying into self updates the start index
+ // and all attributes
+ nTextStartIdx = rStart.GetIndex();
+ const sal_Int32 nEnd = nTextStartIdx + nLen;
+
+ // 2. copy attributes
+ // Iterate over attribute array until the start of the attribute
+ // is behind the copied range
+ const size_t nSize = m_pSwpHints ? m_pSwpHints->Count() : 0;
+
+ // If copying into self, inserting can delete attributes!
+ // Hence first copy into temp-array, and then move that into hints array.
+ SwpHts aArr;
+
+ // Del-Array for all RefMarks without extent
+ SwpHts aRefMrkArr;
+
+ std::vector<std::pair<sal_Int32, sal_Int32>> metaFieldRanges;
+ sal_Int32 nDeletedDummyChars(0);
+ for (size_t n = 0; n < nSize; ++n)
+ {
+ SwTextAttr * const pHt = m_pSwpHints->Get(n);
+
+ const sal_Int32 nAttrStartIdx = pHt->GetStart();
+ if ( nAttrStartIdx >= nEnd )
+ break;
+
+ const sal_Int32 * const pEndIdx = pHt->GetEnd();
+ const sal_uInt16 nWhich = pHt->Which();
+
+ // JP 26.04.94: RefMarks are never copied. If the refmark doesn't have
+ // an extent, there is a dummy char in the text, which
+ // must be removed. So we first copy the attribute,
+ // but remember it, and when we're done delete it,
+ // which also deletes the dummy character!
+ // JP 14.08.95: May RefMarks be moved?
+ const bool bCopyRefMark = RES_TXTATR_REFMARK == nWhich
+ && ( bUndoNodes
+ || ( !pOtherDoc
+ ? GetDoc().IsCopyIsMove()
+ : nullptr == pOtherDoc->GetRefMark( pHt->GetRefMark().GetRefName() ) ) );
+
+ if ( pEndIdx
+ && RES_TXTATR_REFMARK == nWhich
+ && !bCopyRefMark )
+ {
+ continue;
+ }
+
+ // Input Fields are only copied, if completely covered by copied text
+ if ( nWhich == RES_TXTATR_INPUTFIELD )
+ {
+ assert(pEndIdx != nullptr &&
+ "<SwTextNode::CopyText(..)> - RES_TXTATR_INPUTFIELD without EndIndex!" );
+ if ( nAttrStartIdx < nTextStartIdx
+ || ( pEndIdx != nullptr
+ && *pEndIdx > nEnd ) )
+ {
+ continue;
+ }
+ }
+
+ if (nWhich == RES_TXTATR_METAFIELD)
+ {
+ // Skip metadata fields. Also remember the range to strip the text later.
+ metaFieldRanges.emplace_back(nAttrStartIdx, pEndIdx ? *pEndIdx : nEnd);
+ continue;
+ }
+
+ sal_Int32 nAttrStt = 0;
+ sal_Int32 nAttrEnd = 0;
+
+ if( nAttrStartIdx < nTextStartIdx )
+ {
+ // start is before selection
+ // copy hints with end and CH_TXTATR only if dummy char is copied
+ if ( pEndIdx && (*pEndIdx > nTextStartIdx) && !pHt->HasDummyChar() )
+ {
+ // attribute with extent and the end is in the selection
+ nAttrStt = nDestStart;
+ nAttrEnd = (*pEndIdx > nEnd)
+ ? rDestStart.GetIndex()
+ : nDestStart + (*pEndIdx) - nTextStartIdx;
+ }
+ else
+ {
+ continue;
+ }
+ }
+ else
+ {
+ // start is in the selection
+ nAttrStt = nDestStart + ( nAttrStartIdx - nTextStartIdx );
+ if( pEndIdx )
+ {
+ nAttrEnd = *pEndIdx > nEnd
+ ? rDestStart.GetIndex()
+ : nDestStart + ( *pEndIdx - nTextStartIdx );
+ }
+ else
+ {
+ nAttrEnd = nAttrStt;
+ }
+ }
+
+ SwTextAttr * pNewHt = nullptr;
+
+ if( pDest == this )
+ {
+ // copy the hint here, but insert it later
+ pNewHt = MakeTextAttr( GetDoc(), pHt->GetAttr(),
+ nAttrStt, nAttrEnd, CopyOrNewType::Copy, pDest );
+
+ lcl_CopyHint(nWhich, pHt, pNewHt, nullptr, pDest);
+ aArr.push_back( pNewHt );
+ }
+ else
+ {
+ pNewHt = pDest->InsertItem(
+ pHt->GetAttr(),
+ nAttrStt - nDeletedDummyChars,
+ nAttrEnd - nDeletedDummyChars,
+ SetAttrMode::NOTXTATRCHR | SetAttrMode::IS_COPY);
+ if (pNewHt)
+ {
+ lcl_CopyHint( nWhich, pHt, pNewHt, pOtherDoc, pDest );
+ }
+ else if (pHt->HasDummyChar())
+ {
+ // The attribute that has failed to be copied would insert
+ // dummy char, so positions of the following attributes have
+ // to be shifted by one to compensate for that missing char.
+ ++nDeletedDummyChars;
+ }
+ }
+
+ if( RES_TXTATR_REFMARK == nWhich && !pEndIdx && !bCopyRefMark )
+ {
+ aRefMrkArr.push_back( pNewHt );
+ }
+ }
+
+ // Strip the metadata fields, since we don't copy the RDF entries
+ // yet and so they are inconsistent upon copy/pasting.
+ if (!metaFieldRanges.empty())
+ {
+ // Reverse to remove without messing the offsets.
+ std::reverse(metaFieldRanges.begin(), metaFieldRanges.end());
+ for (const auto& pair : metaFieldRanges)
+ {
+ const SwContentIndex aIdx(pDest, pair.first);
+ pDest->EraseText(aIdx, pair.second - pair.first);
+ }
+ }
+
+ // this can only happen when copying into self
+ for (SwTextAttr* i : aArr)
+ {
+ InsertHint( i, SetAttrMode::NOTXTATRCHR );
+ }
+
+ if( pDest->GetpSwpHints() )
+ {
+ for (SwTextAttr* pNewHt : aRefMrkArr)
+ {
+ if( pNewHt->GetEnd() )
+ {
+ pDest->GetpSwpHints()->Delete( pNewHt );
+ pDest->DestroyAttr( pNewHt );
+ }
+ else
+ {
+ const SwContentIndex aIdx( pDest, pNewHt->GetStart() );
+ pDest->EraseText( aIdx, 1 );
+ }
+ }
+ }
+
+ CHECK_SWPHINTS_IF_FRM(this);
+ CHECK_SWPHINTS(pDest);
+}
+
+OUString SwTextNode::InsertText( const OUString & rStr, const SwPosition & rIdx,
+ const SwInsertFlags nMode )
+{
+ return InsertText(rStr, rIdx.nContent, nMode);
+}
+
+OUString SwTextNode::InsertText( const OUString & rStr, const SwContentIndex & rIdx,
+ const SwInsertFlags nMode )
+{
+ assert(rIdx.GetContentNode() == this);
+ assert(rIdx <= m_Text.getLength()); // invalid index
+
+ const sal_Int32 aPos = rIdx.GetIndex();
+ sal_Int32 nLen = m_Text.getLength() - aPos;
+ sal_Int32 const nOverflow(rStr.getLength() - GetSpaceLeft());
+ SAL_WARN_IF(nOverflow > 0, "sw.core",
+ "SwTextNode::InsertText: node text with insertion > capacity.");
+ OUString const sInserted(
+ (nOverflow > 0) ? rStr.copy(0, rStr.getLength() - nOverflow) : rStr);
+ if (sInserted.isEmpty())
+ {
+ return sInserted;
+ }
+ if (aPos == 0 && m_Text.isEmpty())
+ m_Text = sInserted;
+ else
+ m_Text = m_Text.replaceAt(aPos, 0, sInserted);
+ assert(GetSpaceLeft()>=0);
+ nLen = m_Text.getLength() - aPos - nLen;
+ assert(nLen != 0);
+
+ bool bOldExpFlg = IsIgnoreDontExpand();
+ if (nMode & SwInsertFlags::FORCEHINTEXPAND)
+ {
+ SetIgnoreDontExpand( true );
+ }
+
+ Update(rIdx, nLen, UpdateMode::Default); // text content changed!
+
+ if (nMode & SwInsertFlags::FORCEHINTEXPAND)
+ {
+ SetIgnoreDontExpand( bOldExpFlg );
+ }
+
+ if ( HasWriterListeners() )
+ { // send this before messing with hints, which will send RES_UPDATE_ATTR
+ auto aInsHint = sw::MakeInsertText(*this, aPos, nLen);
+ CallSwClientNotify(aInsHint);
+ }
+
+ if ( HasHints() )
+ {
+ m_pSwpHints->SortIfNeedBe();
+ bool const bHadHints(!m_pSwpHints->CanBeDeleted());
+ bool bMergePortionsNeeded(false);
+ for ( size_t i = 0; i < m_pSwpHints->Count() &&
+ rIdx >= m_pSwpHints->GetWithoutResorting(i)->GetStart(); ++i )
+ {
+ SwTextAttr * const pHt = m_pSwpHints->GetWithoutResorting( i );
+ const sal_Int32 * const pEndIdx = pHt->GetEnd();
+ if( !pEndIdx )
+ continue;
+
+ if( rIdx == *pEndIdx )
+ {
+ if ( (nMode & SwInsertFlags::NOHINTEXPAND) ||
+ (!(nMode & SwInsertFlags::FORCEHINTEXPAND)
+ && pHt->DontExpand()) )
+ {
+ m_pSwpHints->DeleteAtPos(i);
+ // on empty attributes also adjust Start
+ if( rIdx == pHt->GetStart() )
+ pHt->SetStart( pHt->GetStart() - nLen );
+ pHt->SetEnd(*pEndIdx - nLen);
+ // could be that pHt has IsFormatIgnoreEnd set, and it's
+ // not a RSID-only hint - now we have the inserted text
+ // between pHt and its continuation... which we don't know.
+ // punt the job to MergePortions below.
+ if (pHt->IsFormatIgnoreEnd())
+ {
+ bMergePortionsNeeded = true;
+ }
+ InsertHint( pHt, SetAttrMode::NOHINTADJUST );
+ }
+ // empty hints at insert position?
+ else if ( (nMode & SwInsertFlags::EMPTYEXPAND)
+ && (*pEndIdx == pHt->GetStart()) )
+ {
+ m_pSwpHints->DeleteAtPos(i);
+ pHt->SetStart( pHt->GetStart() - nLen );
+ const size_t nCurrentLen = m_pSwpHints->Count();
+ InsertHint( pHt/* AUTOSTYLES:, SetAttrMode::NOHINTADJUST*/ );
+ if ( nCurrentLen > m_pSwpHints->Count() && i )
+ {
+ --i;
+ }
+ continue;
+ }
+ else
+ {
+ continue;
+ }
+ }
+ if ( !(nMode & SwInsertFlags::NOHINTEXPAND) &&
+ rIdx == nLen && pHt->GetStart() == rIdx.GetIndex() &&
+ !pHt->IsDontExpandStartAttr() )
+ {
+ // no field, at paragraph start, HintExpand
+ m_pSwpHints->DeleteAtPos(i);
+ pHt->SetStart( pHt->GetStart() - nLen );
+ // no effect on format ignore flags here (para start)
+ InsertHint( pHt, SetAttrMode::NOHINTADJUST );
+ }
+ }
+ if (bMergePortionsNeeded)
+ {
+ m_pSwpHints->MergePortions(*this);
+ }
+ SAL_WARN_IF(bHadHints && m_pSwpHints->CanBeDeleted(), "sw.core",
+ "SwTextNode::InsertText: unexpected loss of hints");
+ }
+
+ // By inserting a character, the hidden flags
+ // at the TextNode can become invalid:
+ SetCalcHiddenCharFlags();
+
+ CHECK_SWPHINTS(this);
+ return sInserted;
+}
+
+void SwTextNode::CutText( SwTextNode * const pDest,
+ const SwContentIndex & rStart, const sal_Int32 nLen )
+{
+ assert(pDest); // Cut requires a destination
+ SwContentIndex aDestStt(pDest, pDest->GetText().getLength());
+ CutImpl( pDest, aDestStt, rStart, nLen, false );
+}
+
+void SwTextNode::CutImpl( SwTextNode * const pDest, const SwContentIndex & rDestStart,
+ const SwContentIndex & rStart, sal_Int32 nLen, const bool bUpdate )
+{
+ assert(pDest); // Cut requires a destination
+
+ assert(&GetDoc() == &pDest->GetDoc()); // must be same document
+
+ assert(pDest != this); // destination must be different node
+
+ assert(rDestStart.GetContentNode() == pDest);
+ assert(rStart.GetContentNode() == this);
+
+ if( !nLen )
+ {
+ // if no length is given, copy attributes at position rStart
+ CopyAttr( pDest, rStart.GetIndex(), rDestStart.GetIndex() );
+ return;
+ }
+
+ sal_Int32 nTextStartIdx = rStart.GetIndex();
+ sal_Int32 nDestStart = rDestStart.GetIndex(); // remember old Pos
+ const sal_Int32 nInitSize = pDest->m_Text.getLength();
+
+ if (pDest->GetSpaceLeft() < nLen)
+ { // FIXME: could only happen when called from SwRangeRedline::Show.
+ // unfortunately can't really do anything here to handle that...
+ abort();
+ }
+ pDest->m_Text = pDest->m_Text.replaceAt(nDestStart, 0,
+ m_Text.subView(nTextStartIdx, nLen));
+ OUString const newText = m_Text.replaceAt(nTextStartIdx, nLen, u"");
+ nLen = pDest->m_Text.getLength() - nInitSize; // update w/ current size!
+ if (!nLen) // String didn't grow?
+ return;
+
+ if (bUpdate)
+ {
+ // Update all SwContentIndex
+ pDest->Update(rDestStart, nLen, UpdateMode::Default);
+ }
+
+ CHECK_SWPHINTS(pDest);
+
+ const sal_Int32 nEnd = rStart.GetIndex() + nLen;
+ bool const bUndoNodes =
+ GetDoc().GetIDocumentUndoRedo().IsUndoNodes(GetNodes());
+
+ // copy hard attributes on whole paragraph
+ if (HasSwAttrSet())
+ {
+ bool hasSwAttrSet = pDest->HasSwAttrSet();
+ if (hasSwAttrSet)
+ {
+ // if we have our own property set it doesn't mean
+ // that this set defines any style different to Standard one.
+ hasSwAttrSet = false;
+
+ // so, let's check deeper if property set has defined any property
+ if (pDest->GetpSwAttrSet())
+ {
+ // check all items in the property set
+ SfxItemIter aIter( *pDest->GetpSwAttrSet() );
+ const SfxPoolItem* pItem = aIter.GetCurItem();
+ do
+ {
+ // check current item
+ const sal_uInt16 nWhich = IsInvalidItem( pItem )
+ ? pDest->GetpSwAttrSet()->GetWhichByOffset( aIter.GetCurPos() )
+ : pItem->Which();
+ if( RES_FRMATR_STYLE_NAME != nWhich &&
+ RES_FRMATR_CONDITIONAL_STYLE_NAME != nWhich &&
+ RES_PAGEDESC != nWhich &&
+ RES_BREAK != nWhich &&
+ SfxItemState::SET == pDest->GetpSwAttrSet()->GetItemState( nWhich, false ) )
+ {
+ // check if parent value (original value in style) has the same value as in [pItem]
+ const SfxPoolItem& rParentItem = pDest->GetpSwAttrSet()->GetParent()->Get( nWhich, true );
+
+ hasSwAttrSet = (rParentItem != *pItem);
+
+ // property set is not empty => no need to make anymore checks
+ if (hasSwAttrSet)
+ break;
+ }
+
+ // let's check next item
+ pItem = aIter.NextItem();
+ } while (pItem);
+ }
+ }
+
+ // all or just the Char attributes?
+ if( nInitSize || hasSwAttrSet ||
+ nLen != pDest->GetText().getLength())
+ {
+ SfxItemSetFixed<
+ RES_CHRATR_BEGIN, RES_CHRATR_END - 1,
+ RES_TXTATR_INETFMT, RES_TXTATR_CHARFMT,
+ RES_UNKNOWNATR_BEGIN, RES_UNKNOWNATR_END - 1>
+ aCharSet( pDest->GetDoc().GetAttrPool() );
+ aCharSet.Put( *GetpSwAttrSet() );
+ if( aCharSet.Count() )
+ pDest->SetAttr( aCharSet, nDestStart, nDestStart + nLen );
+ }
+ else
+ {
+ // Copy all attrs except RES_PARATR_LIST_LEVEL: it was initialized before
+ // and current SwTextNode can contain not suitable for pDest value
+ SfxItemSetFixed<RES_CHRATR_BEGIN, RES_PARATR_LIST_LEVEL - 1,
+ RES_PARATR_LIST_LEVEL + 1, HINT_END>
+ aCharSet(pDest->GetDoc().GetAttrPool());
+ aCharSet.Put(*GetpSwAttrSet());
+ if (aCharSet.Count())
+ pDest->SetAttr(aCharSet, nDestStart, nDestStart + nLen);
+ }
+ }
+
+ // notify frames - before moving hints, because footnotes
+ // want to find their anchor text frame in the follow chain
+ // (also ignore fieldmarks, the caller will recreate frames)
+ const sw::InsertText aInsHint(nDestStart, nLen, false, false);
+ pDest->HandleNonLegacyHint(aInsHint);
+ const sw::MoveText aMoveHint(pDest, nDestStart, nTextStartIdx, nLen);
+ CallSwClientNotify(aMoveHint);
+ const sw::DeleteText aDelText(nTextStartIdx, nLen);
+ HandleNonLegacyHint(aDelText);
+
+ // 2. move attributes
+ // Iterate over attribute array until the start of the attribute
+ // is behind the moved range
+ bool bMergePortionsNeeded(false);
+ size_t nAttrCnt = 0;
+ while (m_pSwpHints && (nAttrCnt < m_pSwpHints->Count()))
+ {
+ SwTextAttr * const pHt = m_pSwpHints->Get(nAttrCnt);
+ const sal_Int32 nAttrStartIdx = pHt->GetStart();
+ if ( nAttrStartIdx >= nEnd )
+ break;
+ const sal_Int32 * const pEndIdx = pHt->GetEnd();
+ const sal_uInt16 nWhich = pHt->Which();
+ SwTextAttr *pNewHt = nullptr;
+
+ // if the hint has a dummy character, then it must not be split!
+ if(nAttrStartIdx < nTextStartIdx)
+ {
+ // start is before the range
+ if (!pHt->HasDummyChar() && ( RES_TXTATR_REFMARK != nWhich
+ || bUndoNodes ) && pEndIdx && *pEndIdx > nTextStartIdx)
+ {
+ // attribute with extent and end of attribute is in the range
+ pNewHt = MakeTextAttr( pDest->GetDoc(), pHt->GetAttr(),
+ nDestStart,
+ nDestStart + (
+ *pEndIdx > nEnd
+ ? nLen
+ : *pEndIdx - nTextStartIdx ) );
+ }
+ }
+ else
+ {
+ // start is inside the range
+ if (!pEndIdx || *pEndIdx < nEnd ||
+ (!bUndoNodes && RES_TXTATR_REFMARK == nWhich)
+ || pHt->HasDummyChar() )
+ {
+ // do not delete note and later add it -> sidebar flickering
+ if (GetDoc().GetDocShell())
+ {
+ GetDoc().GetDocShell()->Broadcast( SfxHint(SfxHintId::SwSplitNodeOperation));
+ }
+ // move attribute
+ m_pSwpHints->Delete( pHt );
+ // reset start/end indexes
+ if (pHt->IsFormatIgnoreStart() || pHt->IsFormatIgnoreEnd())
+ {
+ bMergePortionsNeeded = true;
+ }
+ pHt->SetStart(nDestStart + (nAttrStartIdx - nTextStartIdx));
+ if (pEndIdx)
+ {
+ pHt->SetEnd( nDestStart + (
+ *pEndIdx > nEnd
+ ? nLen
+ : *pEndIdx - nTextStartIdx ) );
+ }
+ pDest->InsertHint( pHt,
+ SetAttrMode::NOTXTATRCHR
+ | SetAttrMode::DONTREPLACE );
+ if (GetDoc().GetDocShell())
+ {
+ GetDoc().GetDocShell()->Broadcast( SfxHint(SfxHintId::SwSplitNodeOperation));
+ }
+ continue; // iterate while loop, no ++ !
+ }
+ // the end is behind the range
+ else if (RES_TXTATR_REFMARK != nWhich || bUndoNodes)
+ {
+ pNewHt = MakeTextAttr( GetDoc(), pHt->GetAttr(),
+ nDestStart + (nAttrStartIdx - nTextStartIdx),
+ nDestStart + (*pEndIdx > nEnd
+ ? nLen
+ : *pEndIdx - nTextStartIdx));
+ }
+ }
+ if (pNewHt)
+ {
+ const bool bSuccess( pDest->InsertHint( pNewHt,
+ SetAttrMode::NOTXTATRCHR
+ | SetAttrMode::DONTREPLACE
+ | SetAttrMode::IS_COPY) );
+ if (bSuccess)
+ {
+ lcl_CopyHint( nWhich, pHt, pNewHt, nullptr, pDest );
+ }
+ }
+ ++nAttrCnt;
+ }
+ // If there are still empty attributes around, they have a higher priority
+ // than any attributes that become empty due to the move.
+ // So temporarily remove them and Update the array, then re-insert the
+ // removed ones so they overwrite the others.
+ if (m_pSwpHints && nAttrCnt < m_pSwpHints->Count())
+ {
+ SwpHts aArr;
+ while (nAttrCnt < m_pSwpHints->Count())
+ {
+ SwTextAttr * const pHt = m_pSwpHints->Get(nAttrCnt);
+ if (nEnd != pHt->GetStart())
+ break;
+ const sal_Int32 * const pEndIdx = pHt->GetEnd();
+ if (pEndIdx && *pEndIdx == nEnd)
+ {
+ aArr.push_back( pHt );
+ m_pSwpHints->Delete( pHt );
+ }
+ else
+ {
+ ++nAttrCnt;
+ }
+ }
+ Update(rStart, nLen, UpdateMode::Negative|UpdateMode::Delete);
+
+ for (SwTextAttr* pHt : aArr)
+ {
+ pHt->SetStart( rStart.GetIndex() );
+ pHt->SetEnd( rStart.GetIndex() );
+ InsertHint( pHt );
+ }
+ }
+ else
+ {
+ Update(rStart, nLen, UpdateMode::Negative|UpdateMode::Delete);
+ }
+
+ // set after moving hints
+ m_Text = newText;
+
+ if (bMergePortionsNeeded)
+ {
+ m_pSwpHints->MergePortions(*this);
+ }
+
+ CHECK_SWPHINTS(this);
+
+ TryDeleteSwpHints();
+}
+
+void SwTextNode::EraseText(const SwPosition &rIdx, const sal_Int32 nCount,
+ const SwInsertFlags nMode )
+{
+ EraseText(rIdx.nContent, nCount, nMode);
+}
+
+void SwTextNode::EraseText(const SwContentIndex &rIdx, const sal_Int32 nCount,
+ const SwInsertFlags nMode )
+{
+ assert(rIdx.GetContentNode() == this);
+ assert(rIdx <= m_Text.getLength()); // invalid index
+
+ const sal_Int32 nStartIdx = rIdx.GetIndex();
+ const sal_Int32 nCnt = (nCount==SAL_MAX_INT32)
+ ? m_Text.getLength() - nStartIdx : nCount;
+ const sal_Int32 nEndIdx = nStartIdx + nCnt;
+ if (nEndIdx <= m_Text.getLength())
+ m_Text = m_Text.replaceAt(nStartIdx, nCnt, u"");
+
+ // GCAttr(); don't remove all empty ones, just the ones that are in the
+ // range but not at the end of the range.
+
+ for ( size_t i = 0; m_pSwpHints && i < m_pSwpHints->Count(); ++i )
+ {
+ SwTextAttr *pHt = m_pSwpHints->Get(i);
+
+ const sal_Int32 nHintStart = pHt->GetStart();
+
+ if ( nHintStart < nStartIdx )
+ continue;
+
+ if ( nHintStart > nEndIdx )
+ break; // hints are sorted by end, so break here
+
+ const sal_Int32* pHtEndIdx = pHt->GetEnd();
+ const sal_uInt16 nWhich = pHt->Which();
+
+ if( !pHtEndIdx )
+ {
+ // attribute with neither end nor CH_TXTATR?
+ assert(pHt->HasDummyChar());
+ if (isTXTATR(nWhich) && (nHintStart < nEndIdx))
+ {
+ m_pSwpHints->DeleteAtPos(i);
+ DestroyAttr( pHt );
+ --i;
+ }
+ continue;
+ }
+
+ assert(!( (nHintStart < nEndIdx) && (*pHtEndIdx > nEndIdx)
+ && pHt->HasDummyChar() )
+ // next line: deleting exactly dummy char: DeleteAttributes
+ || ((nHintStart == nStartIdx) && (nHintStart + 1 == nEndIdx)));
+ // "ERROR: deleting left-overlapped attribute with CH_TXTATR");
+
+ // Delete the hint if:
+ // 1. The hint ends before the deletion end position or
+ // 2. The hint ends at the deletion end position and
+ // we are not in empty expand mode and
+ // the hint is a [toxmark|refmark|ruby|inputfield] text attribute
+ // 3. deleting exactly the dummy char of an hint with end and dummy
+ // char deletes the hint
+ if ( (*pHtEndIdx < nEndIdx)
+ || ( (*pHtEndIdx == nEndIdx) &&
+ !(SwInsertFlags::EMPTYEXPAND & nMode) &&
+ ( (RES_TXTATR_TOXMARK == nWhich) ||
+ (RES_TXTATR_REFMARK == nWhich) ||
+ (RES_TXTATR_CJK_RUBY == nWhich) ||
+ (RES_TXTATR_INPUTFIELD == nWhich) ) )
+ || ( (nHintStart < nEndIdx) &&
+ pHt->HasDummyChar() )
+ )
+ {
+ m_pSwpHints->DeleteAtPos(i);
+ DestroyAttr( pHt );
+ --i;
+ }
+ }
+
+ OSL_ENSURE(rIdx.GetIndex() == nStartIdx, "huh? start index has changed?");
+
+ TryDeleteSwpHints();
+
+ Update(rIdx, nCnt, UpdateMode::Negative);
+
+ if(1 == nCnt)
+ {
+ const auto aHint = sw::DeleteChar(nStartIdx);
+ CallSwClientNotify(aHint);
+ } else {
+ const auto aHint = sw::DeleteText(nStartIdx, nCnt);
+ CallSwClientNotify(aHint);
+ }
+
+ OSL_ENSURE(rIdx.GetIndex() == nStartIdx, "huh? start index has changed?");
+
+ // By deleting a character, the hidden flags
+ // at the TextNode can become invalid:
+ SetCalcHiddenCharFlags();
+
+ CHECK_SWPHINTS(this);
+}
+
+void SwTextNode::GCAttr()
+{
+ if ( !HasHints() )
+ return;
+
+ bool bChanged = false;
+ sal_Int32 nMin = m_Text.getLength();
+ sal_Int32 nMax = 0;
+ const bool bAll = nMin != 0; // on empty paragraphs only remove INetFormats
+
+ for ( size_t i = 0; m_pSwpHints && i < m_pSwpHints->Count(); ++i )
+ {
+ SwTextAttr * const pHt = m_pSwpHints->Get(i);
+
+ // if end and start are equal, delete it
+ const sal_Int32 * const pEndIdx = pHt->GetEnd();
+ if (pEndIdx && !pHt->HasDummyChar() && (*pEndIdx == pHt->GetStart())
+ && ( bAll || pHt->Which() == RES_TXTATR_INETFMT ) )
+ {
+ bChanged = true;
+ nMin = std::min( nMin, pHt->GetStart() );
+ nMax = std::max( nMax, *pHt->GetEnd() );
+ DestroyAttr( m_pSwpHints->Cut(i) );
+ --i;
+ }
+ else
+ {
+ pHt->SetDontExpand( false );
+ }
+ }
+ TryDeleteSwpHints();
+
+ if(bChanged)
+ {
+ // textframes react to aHint, others to aNew
+ SwUpdateAttr aHint(
+ nMin,
+ nMax,
+ 0);
+
+ CallSwClientNotify(sw::LegacyModifyHint(nullptr, &aHint));
+ SwFormatChg aNew( GetTextColl() );
+ CallSwClientNotify(sw::LegacyModifyHint(nullptr, &aNew));
+ }
+}
+
+SwNumRule* SwTextNode::GetNumRule(bool bInParent) const
+{
+ SwNumRule* pRet = nullptr;
+
+ const SfxPoolItem* pItem = GetNoCondAttr( RES_PARATR_NUMRULE, bInParent );
+ bool bNoNumRule = false;
+ if ( pItem )
+ {
+ OUString sNumRuleName =
+ static_cast<const SwNumRuleItem *>(pItem)->GetValue();
+ if (!sNumRuleName.isEmpty())
+ {
+ pRet = GetDoc().FindNumRulePtr( sNumRuleName );
+ }
+ else // numbering is turned off
+ bNoNumRule = true;
+ }
+
+ if ( !bNoNumRule )
+ {
+ if ( pRet && pRet == GetDoc().GetOutlineNumRule() &&
+ ( !HasSwAttrSet() ||
+ SfxItemState::SET !=
+ GetpSwAttrSet()->GetItemState( RES_PARATR_NUMRULE, false ) ) )
+ {
+ SwTextFormatColl* pColl = GetTextColl();
+ if ( pColl )
+ {
+ const SwNumRuleItem& rDirectItem = pColl->GetNumRule( false );
+ if ( rDirectItem.GetValue().isEmpty() )
+ {
+ pRet = nullptr;
+ }
+ }
+ }
+ }
+
+ return pRet;
+}
+
+void SwTextNode::NumRuleChgd()
+{
+ if ( IsInList() )
+ {
+ SwNumRule* pNumRule = GetNumRule();
+ if ( pNumRule && pNumRule != GetNum()->GetNumRule() )
+ {
+ mpNodeNum->ChangeNumRule( *pNumRule );
+ if (mpNodeNumRLHidden)
+ {
+ mpNodeNumRLHidden->ChangeNumRule(*pNumRule);
+ }
+ }
+ }
+
+ // Sending "noop" modify in order to cause invalidations of registered
+ // <SwTextFrame> instances to get the list style change respectively the change
+ // in the list tree reflected in the layout.
+ // Important note:
+ {
+ SvxTextLeftMarginItem & rLR = const_cast<SvxTextLeftMarginItem&>(GetSwAttrSet().GetTextLeftMargin());
+ CallSwClientNotify(sw::LegacyModifyHint(&rLR, &rLR));
+ }
+
+ SetWordCountDirty( true );
+}
+
+// -> #i27615#
+bool SwTextNode::IsNumbered(SwRootFrame const*const pLayout) const
+{
+ SwNumRule* pRule = GetNum(pLayout) ? GetNum(pLayout)->GetNumRule() : nullptr;
+ return pRule && IsCountedInList();
+}
+
+bool SwTextNode::HasMarkedLabel() const
+{
+ bool bResult = false;
+
+ if ( IsInList() )
+ {
+ bResult =
+ GetDoc().getIDocumentListsAccess().getListByName( GetListId() )->IsListLevelMarked( GetActualListLevel() );
+ }
+
+ return bResult;
+}
+// <- #i27615#
+
+SwTextNode* SwTextNode::MakeNewTextNode( SwNode& rPosNd, bool bNext,
+ bool bChgFollow )
+{
+ // ignore hard PageBreak/PageDesc/ColumnBreak from Auto-Set
+ std::optional<SwAttrSet> oNewAttrSet;
+ // #i75353#
+ bool bClearHardSetNumRuleWhenFormatCollChanges( false );
+ if( HasSwAttrSet() )
+ {
+ oNewAttrSet.emplace( *GetpSwAttrSet() );
+ const SfxItemSet* pTmpSet = GetpSwAttrSet();
+
+ if (bNext) // successor doesn't inherit breaks!
+ pTmpSet = &*oNewAttrSet;
+
+ // !bNext: remove PageBreaks/PageDesc/ColBreak from this
+ bool bRemoveFromCache = false;
+ std::vector<sal_uInt16> aClearWhichIds;
+ if ( bNext )
+ bRemoveFromCache = ( 0 != oNewAttrSet->ClearItem( RES_PAGEDESC ) );
+ else
+ aClearWhichIds.push_back( RES_PAGEDESC );
+
+ if( SfxItemState::SET == pTmpSet->GetItemState( RES_BREAK, false ) )
+ {
+ if ( bNext )
+ oNewAttrSet->ClearItem( RES_BREAK );
+ else
+ aClearWhichIds.push_back( RES_BREAK );
+ bRemoveFromCache = true;
+ }
+ if( SfxItemState::SET == pTmpSet->GetItemState( RES_KEEP, false ) )
+ {
+ if ( bNext )
+ oNewAttrSet->ClearItem( RES_KEEP );
+ else
+ aClearWhichIds.push_back( RES_KEEP );
+ bRemoveFromCache = true;
+ }
+ if( SfxItemState::SET == pTmpSet->GetItemState( RES_PARATR_SPLIT, false ) )
+ {
+ if ( bNext )
+ oNewAttrSet->ClearItem( RES_PARATR_SPLIT );
+ else
+ aClearWhichIds.push_back( RES_PARATR_SPLIT );
+ bRemoveFromCache = true;
+ }
+ if(SfxItemState::SET == pTmpSet->GetItemState(RES_PARATR_NUMRULE, false))
+ {
+ SwNumRule * pRule = GetNumRule();
+
+ if (pRule && IsOutline())
+ {
+ if ( bNext )
+ oNewAttrSet->ClearItem(RES_PARATR_NUMRULE);
+ else
+ {
+ // #i75353#
+ // No clear of hard set numbering rule at an outline paragraph at this point.
+ // Only if the paragraph style changes - see below.
+ bClearHardSetNumRuleWhenFormatCollChanges = true;
+ }
+ bRemoveFromCache = true;
+ }
+ }
+
+ if ( !aClearWhichIds.empty() )
+ bRemoveFromCache = 0 != ClearItemsFromAttrSet( aClearWhichIds );
+
+ if( !bNext && bRemoveFromCache )
+ {
+ InvalidateInSwCache(RES_OBJECTDYING);
+ }
+ }
+ SwNodes& rNds = GetNodes();
+
+ SwTextFormatColl* pColl = GetTextColl();
+
+ SwTextNode *pNode = new SwTextNode( rPosNd, pColl, oNewAttrSet ? &*oNewAttrSet : nullptr );
+
+ oNewAttrSet.reset();
+
+ const SwNumRule* pRule = GetNumRule();
+ if( pRule && pRule == pNode->GetNumRule() && rNds.IsDocNodes() )
+ {
+ // #i55459#
+ // - correction: parameter <bNext> has to be checked, as it was in the
+ // previous implementation.
+ if ( !bNext && !IsCountedInList() )
+ SetCountedInList(true);
+ }
+
+ // In case the numbering caused a style from the pool to be assigned to
+ // the new node, don't overwrite that here!
+ if( pColl != pNode->GetTextColl() ||
+ ( bChgFollow && pColl != GetTextColl() ))
+ return pNode; // that ought to be enough?
+
+ pNode->ChgTextCollUpdateNum( nullptr, pColl ); // for numbering/outline
+ if( bNext || !bChgFollow )
+ return pNode;
+
+ SwTextFormatColl *pNextColl = &pColl->GetNextTextFormatColl();
+ // i#101870 perform action on different paragraph styles before applying
+ // the new paragraph style
+ if (pNextColl != pColl)
+ {
+ // #i75353#
+ if ( bClearHardSetNumRuleWhenFormatCollChanges )
+ {
+ if ( ClearItemsFromAttrSet( { RES_PARATR_NUMRULE } ) != 0 )
+ {
+ InvalidateInSwCache(RES_ATTRSET_CHG);
+ }
+ }
+ }
+ ChgFormatColl( pNextColl );
+
+ return pNode;
+}
+
+SwContentNode* SwTextNode::AppendNode( const SwPosition & rPos )
+{
+ // position behind which it will be inserted
+ SwTextNode* pNew = MakeNewTextNode( *rPos.GetNodes()[rPos.GetNodeIndex() + 1] );
+
+ // reset list attributes at appended text node
+ pNew->ResetAttr( RES_PARATR_LIST_ISRESTART );
+ pNew->ResetAttr( RES_PARATR_LIST_RESTARTVALUE );
+ pNew->ResetAttr( RES_PARATR_LIST_ISCOUNTED );
+ if ( pNew->GetNumRule() == nullptr )
+ {
+ pNew->ResetAttr( RES_PARATR_LIST_ID );
+ pNew->ResetAttr( RES_PARATR_LIST_LEVEL );
+ }
+
+ if (!IsInList() && GetNumRule() && !GetListId().isEmpty())
+ {
+ AddToList();
+ }
+
+ if( HasWriterListeners() )
+ MakeFramesForAdjacentContentNode(*pNew);
+ return pNew;
+}
+
+SwTextAttr * SwTextNode::GetTextAttrForCharAt(
+ const sal_Int32 nIndex,
+ const sal_uInt16 nWhich ) const
+{
+ assert(nWhich >= RES_TXTATR_BEGIN && nWhich <= RES_TXTATR_END);
+ if ( HasHints() )
+ {
+ for ( size_t i = 0; i < m_pSwpHints->Count(); ++i )
+ {
+ SwTextAttr * const pHint = m_pSwpHints->Get(i);
+ const sal_Int32 nStartPos = pHint->GetStart();
+ if ( nIndex < nStartPos )
+ {
+ return nullptr;
+ }
+ if ( (nIndex == nStartPos) && pHint->HasDummyChar() )
+ {
+ return ( RES_TXTATR_END == nWhich || nWhich == pHint->Which() )
+ ? pHint : nullptr;
+ }
+ }
+ }
+ return nullptr;
+}
+
+SwTextAttr* SwTextNode::GetTextAttrForEndCharAt(sal_Int32 nIndex, sal_uInt16 nWhich) const
+{
+ SwTextAttr* pAttr = GetTextAttrAt(nIndex, nWhich, ::sw::GetTextAttrMode::Expand);
+ if (!pAttr)
+ {
+ return nullptr;
+ }
+
+ if (!pAttr->End())
+ {
+ return nullptr;
+ }
+
+ // The start-end range covers the end dummy character.
+ if (*pAttr->End() - 1 != nIndex)
+ {
+ return nullptr;
+ }
+
+ return pAttr;
+}
+
+namespace
+{
+
+sal_uInt16 lcl_BoundListLevel(const int nActualLevel)
+{
+ return o3tl::narrowing<sal_uInt16>( std::clamp( nActualLevel, 0, MAXLEVEL-1 ) );
+}
+
+}
+
+// -> #i29560#
+bool SwTextNode::HasNumber(SwRootFrame const*const pLayout) const
+{
+ bool bResult = false;
+
+ const SwNumRule *const pRule = GetNum(pLayout) ? GetNum(pLayout)->GetNumRule() : nullptr;
+ if ( pRule )
+ {
+ const SwNumFormat& aFormat(pRule->Get(lcl_BoundListLevel(GetActualListLevel())));
+
+ // #i40041#
+ bResult = aFormat.IsEnumeration();
+ }
+
+ return bResult;
+}
+
+bool SwTextNode::HasBullet() const
+{
+ bool bResult = false;
+
+ const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if ( pRule )
+ {
+ const SwNumFormat& aFormat(pRule->Get(lcl_BoundListLevel(GetActualListLevel())));
+
+ bResult = aFormat.IsItemize();
+ }
+
+ return bResult;
+}
+// <- #i29560#
+
+// #128041# - introduce parameter <_bInclPrefixAndSuffixStrings>
+//i53420 added max outline parameter
+OUString SwTextNode::GetNumString( const bool _bInclPrefixAndSuffixStrings,
+ const unsigned int _nRestrictToThisLevel,
+ SwRootFrame const*const pLayout, SwListRedlineType eRedline) const
+{
+ if (GetDoc().IsClipBoard() && m_oNumStringCache)
+ {
+ // #i111677# do not expand number strings in clipboard documents
+ return *m_oNumStringCache;
+ }
+ const SwNumRule* pRule = GetNum(pLayout, eRedline) ? GetNum(pLayout, eRedline)->GetNumRule() : nullptr;
+ if ( pRule &&
+ IsCountedInList() )
+ {
+ SvxNumberType const& rNumberType(
+ pRule->Get( lcl_BoundListLevel(GetActualListLevel(eRedline)) ) );
+ if (rNumberType.IsTextFormat() ||
+
+ (style::NumberingType::NUMBER_NONE == rNumberType.GetNumberingType()))
+ {
+ return pRule->MakeNumString( GetNum(pLayout, eRedline)->GetNumberVector(),
+ _bInclPrefixAndSuffixStrings,
+ _nRestrictToThisLevel,
+ false,
+ nullptr,
+ GetLang(0));
+ }
+ }
+
+ return OUString();
+}
+
+tools::Long SwTextNode::GetLeftMarginWithNum( bool bTextLeft ) const
+{
+ tools::Long nRet = 0;
+ const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if( pRule )
+ {
+ const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
+
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
+ {
+ nRet = rFormat.GetAbsLSpace();
+
+ if( !bTextLeft )
+ {
+ if( 0 > rFormat.GetFirstLineOffset() &&
+ nRet > -rFormat.GetFirstLineOffset() )
+ nRet = nRet + rFormat.GetFirstLineOffset();
+ else
+ nRet = 0;
+ }
+
+ if( pRule->IsAbsSpaces() )
+ {
+ SvxFirstLineIndentItem const& rFirst(GetSwAttrSet().GetFirstLineIndent());
+ nRet = nRet - GetSwAttrSet().GetTextLeftMargin().GetLeft(rFirst);
+ }
+ }
+ else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ ::sw::ListLevelIndents const indents(AreListLevelIndentsApplicable());
+ // note: the result is *always* added to either the left-margin
+ // or the text-left-margin of the node itself by the caller.
+ // so first, subtract what the caller has computed anyway,
+ // and then add the value according to combination of
+ // list/paragraph items. (this is rather inelegant)
+ SvxFirstLineIndentItem firstLine(GetSwAttrSet().GetFirstLineIndent());
+ SvxTextLeftMarginItem leftMargin(GetSwAttrSet().GetTextLeftMargin());
+ nRet = bTextLeft
+ ? - leftMargin.GetTextLeft()
+ : - leftMargin.GetLeft(firstLine);
+ if (indents & ::sw::ListLevelIndents::LeftMargin)
+ {
+ leftMargin.SetTextLeft(rFormat.GetIndentAt());
+ }
+ if (indents & ::sw::ListLevelIndents::FirstLine)
+ {
+ firstLine.SetTextFirstLineOffset(rFormat.GetFirstLineIndent());
+ }
+ nRet += bTextLeft
+ ? leftMargin.GetTextLeft()
+ : leftMargin.GetLeft(firstLine);
+ }
+ }
+
+ return nRet;
+}
+
+bool SwTextNode::GetFirstLineOfsWithNum( short& rFLOffset ) const
+{
+ // #i95907#
+ rFLOffset = 0;
+
+ // #i51089#
+ const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if ( pRule )
+ {
+ if ( IsCountedInList() )
+ {
+ const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
+ {
+ rFLOffset = rFormat.GetFirstLineOffset(); //TODO: overflow
+
+ if (!getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
+ {
+ SvxFirstLineIndentItem const aItem(GetSwAttrSet().GetFirstLineIndent());
+ rFLOffset = rFLOffset + aItem.GetTextFirstLineOffset();
+ }
+ }
+ else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ if (AreListLevelIndentsApplicable() & ::sw::ListLevelIndents::FirstLine)
+ {
+ rFLOffset = rFormat.GetFirstLineIndent();
+ }
+ else if (!getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
+ {
+ SvxFirstLineIndentItem const aItem(GetSwAttrSet().GetFirstLineIndent());
+ rFLOffset = aItem.GetTextFirstLineOffset();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ rFLOffset = GetSwAttrSet().GetFirstLineIndent().GetTextFirstLineOffset();
+ return false;
+}
+
+SwTwips SwTextNode::GetAdditionalIndentForStartingNewList() const
+{
+ SwTwips nAdditionalIndent = 0;
+
+ const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if ( pRule )
+ {
+ const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
+ {
+ SvxFirstLineIndentItem const& rFirst(GetSwAttrSet().GetFirstLineIndent());
+ nAdditionalIndent = GetSwAttrSet().GetTextLeftMargin().GetLeft(rFirst);
+
+ if (getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
+ {
+ nAdditionalIndent = nAdditionalIndent -
+ GetSwAttrSet().GetFirstLineIndent().GetTextFirstLineOffset();
+ }
+ }
+ else if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ // note: there was an apparent bug here, list GetIndentAt()
+ // was interpreted as left-margin not text-left-margin unlike every
+ // other use of it.
+ ::sw::ListLevelIndents const indents(AreListLevelIndentsApplicable());
+ SvxFirstLineIndentItem const& rFirst(
+ indents & ::sw::ListLevelIndents::FirstLine
+ ? SvxFirstLineIndentItem(rFormat.GetFirstLineIndent(), RES_MARGIN_FIRSTLINE)
+ : GetSwAttrSet().GetFirstLineIndent());
+ SvxTextLeftMarginItem const& rLeft(
+ indents & ::sw::ListLevelIndents::LeftMargin
+ ? SvxTextLeftMarginItem(rFormat.GetIndentAt(), RES_MARGIN_TEXTLEFT)
+ : GetSwAttrSet().GetTextLeftMargin());
+ nAdditionalIndent = rLeft.GetLeft(rFirst);
+ if (!(indents & ::sw::ListLevelIndents::FirstLine))
+ {
+ if (getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
+ {
+ nAdditionalIndent = nAdditionalIndent - rFirst.GetTextFirstLineOffset();
+ }
+ }
+ }
+ }
+ else
+ {
+ SvxFirstLineIndentItem const& rFirst(GetSwAttrSet().GetFirstLineIndent());
+ nAdditionalIndent = GetSwAttrSet().GetTextLeftMargin().GetLeft(rFirst);
+ }
+
+ return nAdditionalIndent;
+}
+
+// #i91133#
+tools::Long SwTextNode::GetLeftMarginForTabCalculation() const
+{
+ tools::Long nLeftMarginForTabCalc = 0;
+
+ bool bLeftMarginForTabCalcSetToListLevelIndent( false );
+ const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if( pRule )
+ {
+ const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ if (AreListLevelIndentsApplicable() & ::sw::ListLevelIndents::LeftMargin)
+ {
+ nLeftMarginForTabCalc = rFormat.GetIndentAt();
+ bLeftMarginForTabCalcSetToListLevelIndent = true;
+ }
+ }
+ }
+ if ( !bLeftMarginForTabCalcSetToListLevelIndent )
+ {
+ nLeftMarginForTabCalc = GetSwAttrSet().GetTextLeftMargin().GetTextLeft();
+ }
+
+ return nLeftMarginForTabCalc;
+}
+
+static void Replace0xFF(
+ SwTextNode const& rNode,
+ OUStringBuffer & rText,
+ sal_Int32 & rTextStt,
+ sal_Int32 nEndPos )
+{
+ if (!rNode.GetpSwpHints())
+ return;
+
+ sal_Unicode cSrchChr = CH_TXTATR_BREAKWORD;
+ for( int nSrchIter = 0; 2 > nSrchIter; ++nSrchIter, cSrchChr = CH_TXTATR_INWORD )
+ {
+ sal_Int32 nPos = rText.indexOf(cSrchChr);
+ while (-1 != nPos && nPos < nEndPos)
+ {
+ const SwTextAttr* const pAttr =
+ rNode.GetTextAttrForCharAt(rTextStt + nPos);
+ if( pAttr )
+ {
+ switch( pAttr->Which() )
+ {
+ case RES_TXTATR_FIELD:
+ case RES_TXTATR_ANNOTATION:
+ rText.remove(nPos, 1);
+ ++rTextStt;
+ break;
+
+ case RES_TXTATR_FTN:
+ rText.remove(nPos, 1);
+ ++rTextStt;
+ break;
+
+ default:
+ rText.remove(nPos, 1);
+ ++rTextStt;
+ }
+ }
+ else
+ {
+ ++nPos;
+ ++nEndPos;
+ }
+ nPos = rText.indexOf(cSrchChr, nPos);
+ }
+ }
+}
+
+// Expand fields
+// #i83479# - handling of new parameters
+OUString SwTextNode::GetExpandText(SwRootFrame const*const pLayout,
+ const sal_Int32 nIdx,
+ const sal_Int32 nLen,
+ const bool bWithNum,
+ const bool bAddSpaceAfterListLabelStr,
+ const bool bWithSpacesForLevel,
+ const ExpandMode eAdditionalMode) const
+
+{
+ ExpandMode eMode = ExpandMode::ExpandFields | eAdditionalMode;
+ if (pLayout && pLayout->IsHideRedlines())
+ {
+ eMode |= ExpandMode::HideDeletions;
+ }
+
+ ModelToViewHelper aConversionMap(*this, pLayout, eMode);
+ const OUString aExpandText = aConversionMap.getViewText();
+ const sal_Int32 nExpandBegin = aConversionMap.ConvertToViewPosition( nIdx );
+ sal_Int32 nEnd = nLen == -1 ? GetText().getLength() : nIdx + nLen;
+ const sal_Int32 nExpandEnd = aConversionMap.ConvertToViewPosition( nEnd );
+ OUStringBuffer aText(aExpandText.subView(nExpandBegin, nExpandEnd-nExpandBegin));
+
+ // remove dummy characters of Input Fields
+ comphelper::string::remove(aText, CH_TXT_ATR_INPUTFIELDSTART);
+ comphelper::string::remove(aText, CH_TXT_ATR_INPUTFIELDEND);
+ comphelper::string::remove(aText, CH_TXTATR_BREAKWORD);
+
+ if( bWithNum )
+ {
+ if (!GetNumString(true, MAXLEVEL, pLayout).isEmpty())
+ {
+ if ( bAddSpaceAfterListLabelStr )
+ {
+ const sal_Unicode aSpace = ' ';
+ aText.insert(0, aSpace);
+ }
+ aText.insert(0, GetNumString(true, MAXLEVEL, pLayout));
+ }
+ }
+
+ if (bWithSpacesForLevel)
+ {
+ const sal_Unicode aSpace = ' ';
+ for (int nLevel = GetActualListLevel(); nLevel > 0; --nLevel)
+ {
+ aText.insert(0, aSpace);
+ aText.insert(0, aSpace);
+ }
+ }
+
+ return aText.makeStringAndClear();
+}
+
+bool SwTextNode::CopyExpandText(SwTextNode& rDestNd, const SwContentIndex* pDestIdx,
+ sal_Int32 nIdx, sal_Int32 nLen,
+ SwRootFrame const*const pLayout, bool bWithNum,
+ bool bWithFootnote, bool bReplaceTabsWithSpaces ) const
+{
+ if( &rDestNd == this )
+ return false;
+ assert(!pDestIdx || pDestIdx->GetContentNode() == &rDestNd);
+
+ SwContentIndex aDestIdx(&rDestNd, rDestNd.GetText().getLength());
+ if( pDestIdx )
+ aDestIdx = *pDestIdx;
+ const sal_Int32 nDestStt = aDestIdx.GetIndex();
+
+ // first, start with the text
+ OUStringBuffer buf(GetText());
+ if( bReplaceTabsWithSpaces )
+ buf.replace('\t', ' ');
+
+ // mask hidden characters
+ const sal_Unicode cChar = CH_TXTATR_BREAKWORD;
+ SwScriptInfo::MaskHiddenRanges(*this, buf, 0, buf.getLength(), cChar);
+
+ buf.remove(0, nIdx);
+ if (nLen != -1)
+ {
+ buf.truncate(nLen);
+ }
+ // remove dummy characters of Input Fields
+ {
+ comphelper::string::remove(buf, CH_TXT_ATR_INPUTFIELDSTART);
+ comphelper::string::remove(buf, CH_TXT_ATR_INPUTFIELDEND);
+ }
+ rDestNd.InsertText(buf.makeStringAndClear(), aDestIdx);
+ nLen = aDestIdx.GetIndex() - nDestStt;
+
+ // set all char attributes with Symbol font
+ if ( HasHints() )
+ {
+ sal_Int32 nInsPos = nDestStt - nIdx;
+ for ( size_t i = 0; i < m_pSwpHints->Count(); ++i )
+ {
+ const SwTextAttr* pHt = m_pSwpHints->Get(i);
+ const sal_Int32 nAttrStartIdx = pHt->GetStart();
+ const sal_uInt16 nWhich = pHt->Which();
+ if (nIdx + nLen <= nAttrStartIdx)
+ break; // behind end of text
+
+ const sal_Int32 *pEndIdx = pHt->End();
+ if( pEndIdx && *pEndIdx > nIdx &&
+ ( RES_CHRATR_FONT == nWhich ||
+ RES_TXTATR_CHARFMT == nWhich ||
+ RES_TXTATR_AUTOFMT == nWhich ))
+ {
+ const SvxFontItem* const pFont =
+ CharFormat::GetItem( *pHt, RES_CHRATR_FONT );
+ if ( pFont && RTL_TEXTENCODING_SYMBOL == pFont->GetCharSet() )
+ {
+ // attribute in area => copy
+ rDestNd.InsertItem( *const_cast<SvxFontItem*>(pFont),
+ nInsPos + nAttrStartIdx, nInsPos + *pEndIdx );
+ }
+ }
+ else if ( pHt->HasDummyChar() && (nAttrStartIdx >= nIdx) )
+ {
+ aDestIdx = nInsPos + nAttrStartIdx;
+ switch( nWhich )
+ {
+ case RES_TXTATR_FIELD:
+ case RES_TXTATR_ANNOTATION:
+ {
+ OUString const aExpand(
+ static_txtattr_cast<SwTextField const*>(pHt)->GetFormatField().GetField()->ExpandField(true, pLayout));
+ if (!aExpand.isEmpty())
+ {
+ ++aDestIdx; // insert behind
+ OUString const ins(
+ rDestNd.InsertText( aExpand, aDestIdx));
+ SAL_INFO_IF(ins.getLength() != aExpand.getLength(),
+ "sw.core", "GetExpandText lossage");
+ aDestIdx = nInsPos + nAttrStartIdx;
+ nInsPos += ins.getLength();
+ }
+ rDestNd.EraseText( aDestIdx, 1 );
+ --nInsPos;
+ }
+ break;
+
+ case RES_TXTATR_FTN:
+ {
+ if ( bWithFootnote )
+ {
+ const SwFormatFootnote& rFootnote = pHt->GetFootnote();
+ OUString sExpand;
+ auto const number(pLayout && pLayout->IsHideRedlines()
+ ? rFootnote.GetNumberRLHidden()
+ : rFootnote.GetNumber());
+ if( !rFootnote.GetNumStr().isEmpty() )
+ sExpand = rFootnote.GetNumStr();
+ else if( rFootnote.IsEndNote() )
+ sExpand = GetDoc().GetEndNoteInfo().m_aFormat.
+ GetNumStr(number);
+ else
+ sExpand = GetDoc().GetFootnoteInfo().m_aFormat.
+ GetNumStr(number);
+ if( !sExpand.isEmpty() )
+ {
+ ++aDestIdx; // insert behind
+ SvxEscapementItem aItem( SvxEscapement::Superscript, RES_CHRATR_ESCAPEMENT );
+ rDestNd.InsertItem(
+ aItem,
+ aDestIdx.GetIndex(),
+ aDestIdx.GetIndex() );
+ OUString const ins( rDestNd.InsertText(sExpand, aDestIdx, SwInsertFlags::EMPTYEXPAND));
+ SAL_INFO_IF(ins.getLength() != sExpand.getLength(),
+ "sw.core", "GetExpandText lossage");
+ aDestIdx = nInsPos + nAttrStartIdx;
+ nInsPos += ins.getLength();
+ }
+ }
+ rDestNd.EraseText( aDestIdx, 1 );
+ --nInsPos;
+ }
+ break;
+
+ default:
+ rDestNd.EraseText( aDestIdx, 1 );
+ --nInsPos;
+ }
+ }
+ }
+ }
+
+ if( bWithNum )
+ {
+ aDestIdx = nDestStt;
+ rDestNd.InsertText( GetNumString(true, MAXLEVEL, pLayout), aDestIdx );
+ }
+
+ aDestIdx = 0;
+ sal_Int32 nStartDelete(-1);
+ while (aDestIdx < rDestNd.GetText().getLength())
+ {
+ sal_Unicode const cur(rDestNd.GetText()[aDestIdx.GetIndex()]);
+ if ( (cChar == cur) // filter substituted hidden text
+ || (CH_TXT_ATR_FIELDSTART == cur) // filter all fieldmarks
+ || (CH_TXT_ATR_FIELDSEP == cur)
+ || (CH_TXT_ATR_FIELDEND == cur)
+ || (CH_TXT_ATR_FORMELEMENT == cur))
+ {
+ if (-1 == nStartDelete)
+ {
+ nStartDelete = aDestIdx.GetIndex(); // start deletion range
+ }
+ ++aDestIdx;
+ if (aDestIdx < rDestNd.GetText().getLength())
+ {
+ continue;
+ } // else: end of paragraph => delete, see below
+ }
+ else
+ {
+ if (-1 == nStartDelete)
+ {
+ ++aDestIdx;
+ continue;
+ } // else: delete, see below
+ }
+ assert(-1 != nStartDelete); // without delete range, would have continued
+ rDestNd.EraseText(
+ SwContentIndex(&rDestNd, nStartDelete),
+ aDestIdx.GetIndex() - nStartDelete);
+ assert(aDestIdx.GetIndex() == nStartDelete);
+ nStartDelete = -1; // reset
+ }
+
+ return true;
+}
+
+OUString SwTextNode::GetRedlineText() const
+{
+ std::vector<sal_Int32> aRedlArr;
+ const SwDoc& rDoc = GetDoc();
+ SwRedlineTable::size_type nRedlPos = rDoc.getIDocumentRedlineAccess().GetRedlinePos( *this, RedlineType::Delete );
+ if( SwRedlineTable::npos != nRedlPos )
+ {
+ // some redline-delete object exists for the node
+ const SwNodeOffset nNdIdx = GetIndex();
+ for( ; nRedlPos < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() ; ++nRedlPos )
+ {
+ const SwRangeRedline* pTmp = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedlPos ];
+ if( RedlineType::Delete == pTmp->GetType() )
+ {
+ const SwPosition *pRStt = pTmp->Start(), *pREnd = pTmp->End();
+ if( pRStt->GetNodeIndex() < nNdIdx )
+ {
+ if( pREnd->GetNodeIndex() > nNdIdx )
+ // paragraph is fully deleted
+ return OUString();
+ else if( pREnd->GetNodeIndex() == nNdIdx )
+ {
+ // deleted from 0 to nContent
+ aRedlArr.push_back( 0 );
+ aRedlArr.push_back( pREnd->GetContentIndex() );
+ }
+ }
+ else if( pRStt->GetNodeIndex() == nNdIdx )
+ {
+ //aRedlArr.Insert( pRStt->GetContentIndex(), aRedlArr.Count() );
+ aRedlArr.push_back( pRStt->GetContentIndex() );
+ if( pREnd->GetNodeIndex() == nNdIdx )
+ aRedlArr.push_back( pREnd->GetContentIndex() );
+ else
+ {
+ aRedlArr.push_back(GetText().getLength());
+ break; // that was all
+ }
+ }
+ else
+ break; // that was all
+ }
+ }
+ }
+
+ OUStringBuffer aText(GetText());
+
+ sal_Int32 nTextStt = 0;
+ sal_Int32 nIdxEnd = aText.getLength();
+ for( size_t n = 0; n < aRedlArr.size(); n += 2 )
+ {
+ sal_Int32 nStt = aRedlArr[ n ];
+ sal_Int32 nEnd = aRedlArr[ n+1 ];
+ if( ( 0 <= nStt && nStt <= nIdxEnd ) ||
+ ( 0 <= nEnd && nEnd <= nIdxEnd ))
+ {
+ if( nStt < 0 ) nStt = 0;
+ if( nIdxEnd < nEnd ) nEnd = nIdxEnd;
+ const sal_Int32 nDelCnt = nEnd - nStt;
+ aText.remove(nStt - nTextStt, nDelCnt);
+ Replace0xFF(*this, aText, nTextStt, nStt - nTextStt);
+ nTextStt += nDelCnt;
+ }
+ else if( nStt >= nIdxEnd )
+ break;
+ }
+ Replace0xFF(*this, aText, nTextStt, aText.getLength());
+
+ return aText.makeStringAndClear();
+}
+
+void SwTextNode::ReplaceText( const SwContentIndex& rStart, const sal_Int32 nDelLen,
+ const OUString & rStr)
+{
+ assert(rStart.GetContentNode() == this);
+ assert( rStart.GetIndex() < m_Text.getLength() // index out of bounds
+ && rStart.GetIndex() + nDelLen <= m_Text.getLength());
+
+ sal_Int32 const nOverflow(rStr.getLength() - nDelLen - GetSpaceLeft());
+ SAL_WARN_IF(nOverflow > 0, "sw.core",
+ "SwTextNode::ReplaceText: node text with insertion > node capacity.");
+ OUString const sInserted(
+ (nOverflow > 0) ? rStr.copy(0, rStr.getLength() - nOverflow) : rStr);
+ if (sInserted.isEmpty() && 0 == nDelLen)
+ {
+ return; // nothing to do
+ }
+
+ const sal_Int32 nStartPos = rStart.GetIndex();
+ sal_Int32 nEndPos = nStartPos + nDelLen;
+ sal_Int32 nLen = nDelLen;
+ for( sal_Int32 nPos = nStartPos; nPos < nEndPos; ++nPos )
+ {
+ if ((CH_TXTATR_BREAKWORD == m_Text[nPos]) ||
+ (CH_TXTATR_INWORD == m_Text[nPos]))
+ {
+ SwTextAttr *const pHint = GetTextAttrForCharAt( nPos );
+ if (pHint)
+ {
+ assert(!( pHint->GetEnd() && pHint->HasDummyChar()
+ && (pHint->GetStart() < nEndPos)
+ && (*pHint->GetEnd() > nEndPos) ));
+ // "deleting left-overlapped attribute with CH_TXTATR"
+ DeleteAttribute( pHint );
+ --nEndPos;
+ --nLen;
+ }
+ }
+ }
+
+ bool bOldExpFlg = IsIgnoreDontExpand();
+ SetIgnoreDontExpand( true );
+
+ const sal_Int32 nInsLen = sInserted.getLength();
+ if (nLen && nInsLen)
+ {
+ m_Text = m_Text.replaceAt(nStartPos, nLen, sInserted);
+
+ if (nLen > nInsLen) // short insert
+ {
+ // delete up to the point that the user specified
+ const SwContentIndex aNegIdx(rStart, nInsLen);
+ Update(aNegIdx, nLen - nInsLen, UpdateMode::Negative);
+ }
+ else if (nLen < nInsLen) // long insert
+ {
+ const SwContentIndex aIdx(rStart, nLen);
+ Update(aIdx, nInsLen - nLen, UpdateMode::Replace);
+ }
+
+ for (sal_Int32 i = 0; i < nInsLen; i++)
+ {
+ ++const_cast<SwContentIndex&>(rStart);
+ }
+ }
+ else
+ {
+ m_Text = m_Text.replaceAt(nStartPos, nLen, u"");
+ Update(rStart, nLen, UpdateMode::Negative);
+
+ m_Text = m_Text.replaceAt(nStartPos, 0, sInserted);
+ Update(rStart, sInserted.getLength(), UpdateMode::Replace);
+ }
+
+ SetIgnoreDontExpand( bOldExpFlg );
+ auto aDelHint = sw::DeleteText(nStartPos, nDelLen);
+ CallSwClientNotify(aDelHint);
+
+ if (sInserted.getLength())
+ {
+ auto aInsHint = sw::MakeInsertText(*this, nStartPos, sInserted.getLength());
+ CallSwClientNotify(aInsHint);
+ }
+}
+
+void SwTextNode::ReplaceText( SwPosition& rStart, const sal_Int32 nDelLen,
+ const OUString & rStr)
+{
+ ReplaceText(rStart.nContent, nDelLen, rStr);
+}
+
+namespace {
+ void lcl_ResetParAttrs( SwTextNode &rTextNode )
+ {
+ const o3tl::sorted_vector<sal_uInt16> aAttrs{ RES_PARATR_LIST_ID, RES_PARATR_LIST_LEVEL,
+ RES_PARATR_LIST_ISRESTART,
+ RES_PARATR_LIST_RESTARTVALUE,
+ RES_PARATR_LIST_ISCOUNTED };
+ SwPaM aPam( rTextNode );
+ // #i96644#
+ // suppress side effect "send data changed events"
+ rTextNode.GetDoc().ResetAttrs( aPam, false, aAttrs, false );
+ }
+
+ // Helper method for special handling of modified attributes at text node.
+ // The following is handled:
+ // (1) on changing the paragraph style - RES_FMT_CHG:
+ // Check, if list style of the text node is changed. If yes, add respectively
+ // remove the text node to the corresponding list.
+ // (2) on changing the attributes - RES_ATTRSET_CHG:
+ // Same as (1).
+ // (3) on changing the list style - RES_PARATR_NUMRULE:
+ // Same as (1).
+ void HandleModifyAtTextNode( SwTextNode& rTextNode,
+ const SfxPoolItem* pOldValue,
+ const SfxPoolItem* pNewValue )
+ {
+ const sal_uInt16 nWhich = pOldValue ? pOldValue->Which() :
+ pNewValue ? pNewValue->Which() : 0 ;
+ bool bNumRuleSet = false;
+ bool bParagraphStyleChanged = false;
+ OUString sNumRule;
+ OUString sOldNumRule;
+ switch ( nWhich )
+ {
+ case RES_FMT_CHG:
+ {
+ bParagraphStyleChanged = true;
+ if( rTextNode.GetNodes().IsDocNodes() )
+ {
+ const SwNumRule* pFormerNumRuleAtTextNode =
+ rTextNode.GetNum() ? rTextNode.GetNum()->GetNumRule() : nullptr;
+ if ( pFormerNumRuleAtTextNode )
+ {
+ sOldNumRule = pFormerNumRuleAtTextNode->GetName();
+ }
+ if ( rTextNode.IsEmptyListStyleDueToSetOutlineLevelAttr() )
+ {
+ const SwNumRuleItem& rNumRuleItem = rTextNode.GetTextColl()->GetNumRule();
+ if ( !rNumRuleItem.GetValue().isEmpty() )
+ {
+ rTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ }
+ }
+ const SwNumRule* pNumRuleAtTextNode = rTextNode.GetNumRule();
+ if ( pNumRuleAtTextNode )
+ {
+ bNumRuleSet = true;
+ sNumRule = pNumRuleAtTextNode->GetName();
+ }
+ }
+ break;
+ }
+ case RES_ATTRSET_CHG:
+ {
+ const SwNumRule* pFormerNumRuleAtTextNode =
+ rTextNode.GetNum() ? rTextNode.GetNum()->GetNumRule() : nullptr;
+ if ( pFormerNumRuleAtTextNode )
+ {
+ sOldNumRule = pFormerNumRuleAtTextNode->GetName();
+ }
+
+ const SwAttrSetChg* pSet = dynamic_cast<const SwAttrSetChg*>(pNewValue);
+ if ( pSet && pSet->GetChgSet()->GetItemState( RES_PARATR_NUMRULE, false ) ==
+ SfxItemState::SET )
+ {
+ // #i70748#
+ rTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ bNumRuleSet = true;
+ }
+ // #i70748#
+ // The new list style set at the paragraph.
+ const SwNumRule* pNumRuleAtTextNode = rTextNode.GetNumRule();
+ if ( pNumRuleAtTextNode )
+ {
+ sNumRule = pNumRuleAtTextNode->GetName();
+ }
+ break;
+ }
+ case RES_PARATR_NUMRULE:
+ {
+ if ( rTextNode.GetNodes().IsDocNodes() )
+ {
+ const SwNumRule* pFormerNumRuleAtTextNode =
+ rTextNode.GetNum() ? rTextNode.GetNum()->GetNumRule() : nullptr;
+ if ( pFormerNumRuleAtTextNode )
+ {
+ sOldNumRule = pFormerNumRuleAtTextNode->GetName();
+ }
+
+ if ( pNewValue )
+ {
+ // #i70748#
+ rTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ bNumRuleSet = true;
+ }
+ // #i70748#
+ // The new list style set at the paragraph.
+ const SwNumRule* pNumRuleAtTextNode = rTextNode.GetNumRule();
+ if ( pNumRuleAtTextNode )
+ {
+ sNumRule = pNumRuleAtTextNode->GetName();
+ }
+ }
+ break;
+ }
+ }
+ if ( sNumRule != sOldNumRule )
+ {
+ if ( bNumRuleSet )
+ {
+ if (sNumRule.isEmpty())
+ {
+ rTextNode.RemoveFromList();
+ if ( bParagraphStyleChanged )
+ {
+ lcl_ResetParAttrs(rTextNode);
+ }
+ }
+ else
+ {
+ rTextNode.RemoveFromList();
+ // If new list style is the outline style, apply outline
+ // level as the list level.
+ if (sNumRule==SwNumRule::GetOutlineRuleName())
+ {
+ // #i70748#
+ OSL_ENSURE( rTextNode.GetTextColl()->IsAssignedToListLevelOfOutlineStyle(),
+ "<HandleModifyAtTextNode()> - text node with outline style, but its paragraph style is not assigned to outline style." );
+ const int nNewListLevel =
+ rTextNode.GetTextColl()->GetAssignedOutlineStyleLevel();
+ if ( 0 <= nNewListLevel && nNewListLevel < MAXLEVEL )
+ {
+ rTextNode.SetAttrListLevel( nNewListLevel );
+ }
+ }
+ rTextNode.AddToList();
+ }
+ }
+ else // <sNumRule.Len() == 0 && sOldNumRule.Len() != 0>
+ {
+ rTextNode.RemoveFromList();
+ if ( bParagraphStyleChanged )
+ {
+ lcl_ResetParAttrs(rTextNode);
+ // #i70748#
+ if ( rTextNode.GetAttr( RES_PARATR_OUTLINELEVEL, false ).GetValue() > 0 )
+ {
+ rTextNode.SetEmptyListStyleDueToSetOutlineLevelAttr();
+ }
+ }
+ }
+ }
+ else if (!sNumRule.isEmpty() && !rTextNode.IsInList())
+ {
+ rTextNode.AddToList();
+ }
+ }
+ // End of method <HandleModifyAtTextNode>
+}
+
+SwFormatColl* SwTextNode::ChgFormatColl( SwFormatColl *pNewColl )
+{
+ OSL_ENSURE( pNewColl,"ChgFormatColl: Collectionpointer has value 0." );
+ assert( dynamic_cast<const SwTextFormatColl *>(pNewColl) && "ChgFormatColl: is not a Text Collection pointer." );
+
+ SwTextFormatColl *pOldColl = GetTextColl();
+ if( pNewColl != pOldColl )
+ {
+ SetCalcHiddenCharFlags();
+ SwContentNode::ChgFormatColl( pNewColl );
+ OSL_ENSURE( !mbInSetOrResetAttr,
+ "DEBUG OSL_ENSURE(ON - <SwTextNode::ChgFormatColl(..)> called during <Set/ResetAttr(..)>" );
+ if ( !mbInSetOrResetAttr )
+ {
+ SwFormatChg aTmp1( pOldColl );
+ SwFormatChg aTmp2( pNewColl );
+ HandleModifyAtTextNode( *this, &aTmp1, &aTmp2 );
+ }
+
+ // reset fill information on parent style change
+ if(maFillAttributes)
+ {
+ maFillAttributes.reset();
+ }
+ }
+
+ // only for real nodes-array
+ if( GetNodes().IsDocNodes() )
+ {
+ ChgTextCollUpdateNum( pOldColl, static_cast<SwTextFormatColl *>(pNewColl) );
+ }
+
+ return pOldColl;
+}
+
+const SwNodeNum* SwTextNode::GetNum(SwRootFrame const*const pLayout, SwListRedlineType eRedline) const
+{
+ // invariant: it's only in list in Hide mode if it's in list in normal mode
+ assert(mpNodeNum || !mpNodeNumRLHidden);
+ return (pLayout && pLayout->IsHideRedlines()) || SwListRedlineType::HIDDEN == eRedline
+ ? mpNodeNumRLHidden.get()
+ : ( SwListRedlineType::ORIGTEXT == eRedline ? mpNodeNumOrig.get() : mpNodeNum.get() );
+}
+
+void SwTextNode::DoNum(std::function<void (SwNodeNum &)> const& rFunc)
+{
+ // temp. clear because GetActualListLevel() may be called and the assert
+ // there triggered during update, which is unhelpful
+ std::unique_ptr<SwNodeNum> pBackup = std::move(mpNodeNumRLHidden);
+ std::unique_ptr<SwNodeNum> pBackup2 = std::move(mpNodeNumOrig);
+ assert(mpNodeNum);
+ rFunc(*mpNodeNum);
+ if (pBackup)
+ {
+ mpNodeNumRLHidden = std::move(pBackup);
+ rFunc(*mpNodeNumRLHidden);
+ }
+ if (pBackup2)
+ {
+ mpNodeNumOrig = std::move(pBackup2);
+ rFunc(*mpNodeNumOrig);
+ }
+}
+
+SwNumberTree::tNumberVector
+SwTextNode::GetNumberVector(SwRootFrame const*const pLayout, SwListRedlineType eRedline) const
+{
+ if (SwNodeNum const*const pNum = GetNum(pLayout, eRedline))
+ {
+ return pNum->GetNumberVector();
+ }
+ else
+ {
+ SwNumberTree::tNumberVector aResult;
+ return aResult;
+ }
+}
+
+bool SwTextNode::IsOutline() const
+{
+ bool bResult = false;
+
+ if ( GetAttrOutlineLevel() > 0 )
+ {
+ bResult = !IsInRedlines();
+ }
+ else
+ {
+ const SwNumRule* pRule( GetNum() ? GetNum()->GetNumRule() : nullptr );
+ if ( pRule && pRule->IsOutlineRule() )
+ {
+ bResult = !IsInRedlines();
+ }
+ }
+
+ return bResult;
+}
+
+bool SwTextNode::IsOutlineStateChanged() const
+{
+ return IsOutline() != m_bLastOutlineState;
+}
+
+void SwTextNode::UpdateOutlineState()
+{
+ m_bLastOutlineState = IsOutline();
+}
+
+int SwTextNode::GetAttrOutlineLevel() const
+{
+ return GetAttr(RES_PARATR_OUTLINELEVEL).GetValue();
+}
+
+void SwTextNode::SetAttrOutlineLevel(int nLevel)
+{
+ assert(0 <= nLevel && nLevel <= MAXLEVEL); // Level Out Of Range
+ if ( 0 <= nLevel && nLevel <= MAXLEVEL )
+ {
+ SetAttr( SfxUInt16Item( RES_PARATR_OUTLINELEVEL,
+ o3tl::narrowing<sal_uInt16>(nLevel) ) );
+ }
+}
+
+void SwTextNode::GetAttrOutlineContentVisible(bool& bOutlineContentVisibleAttr)
+{
+ const SfxGrabBagItem & rGrabBagItem = GetAttr(RES_PARATR_GRABBAG);
+ auto it = rGrabBagItem.GetGrabBag().find("OutlineContentVisibleAttr");
+ if (it != rGrabBagItem.GetGrabBag().end())
+ it->second >>= bOutlineContentVisibleAttr;
+}
+
+void SwTextNode::SetAttrOutlineContentVisible(bool bVisible)
+{
+ SfxGrabBagItem aGrabBagItem(RES_PARATR_GRABBAG);
+ aGrabBagItem.GetGrabBag()["OutlineContentVisibleAttr"] <<= bVisible;
+ SetAttr(aGrabBagItem);
+}
+
+// #i70748#
+
+void SwTextNode::SetEmptyListStyleDueToSetOutlineLevelAttr()
+{
+ if ( !mbEmptyListStyleSetDueToSetOutlineLevelAttr )
+ {
+ SetAttr( SwNumRuleItem() );
+ mbEmptyListStyleSetDueToSetOutlineLevelAttr = true;
+ }
+}
+
+void SwTextNode::ResetEmptyListStyleDueToResetOutlineLevelAttr()
+{
+ if ( mbEmptyListStyleSetDueToSetOutlineLevelAttr )
+ {
+ ResetAttr( RES_PARATR_NUMRULE );
+ mbEmptyListStyleSetDueToSetOutlineLevelAttr = false;
+ }
+}
+
+void SwTextNode::SetAttrListLevel( int nLevel )
+{
+ if ( nLevel < 0 || nLevel >= MAXLEVEL )
+ {
+ assert(false); // invalid level
+ return;
+ }
+
+ SfxInt16Item aNewListLevelItem( RES_PARATR_LIST_LEVEL,
+ static_cast<sal_Int16>(nLevel) );
+ SetAttr( aNewListLevelItem );
+}
+
+bool SwTextNode::HasAttrListLevel() const
+{
+ return GetpSwAttrSet() &&
+ GetpSwAttrSet()->GetItemState( RES_PARATR_LIST_LEVEL, false ) == SfxItemState::SET;
+}
+
+int SwTextNode::GetAttrListLevel() const
+{
+ int nAttrListLevel = 0;
+
+ const SfxInt16Item& aListLevelItem =
+ GetAttr( RES_PARATR_LIST_LEVEL );
+ nAttrListLevel = static_cast<int>(aListLevelItem.GetValue());
+
+ return nAttrListLevel;
+}
+
+int SwTextNode::GetActualListLevel(SwListRedlineType eRedline) const
+{
+ assert(SwListRedlineType::SHOW != eRedline ||
+ !GetNum(nullptr, SwListRedlineType::SHOW) || !mpNodeNumRLHidden || // must be in sync
+ GetNum(nullptr, SwListRedlineType::SHOW)->GetLevelInListTree() ==
+ mpNodeNumRLHidden->GetLevelInListTree());
+ return GetNum(nullptr, eRedline) ? GetNum(nullptr, eRedline)->GetLevelInListTree() : -1;
+}
+
+void SwTextNode::SetListRestart( bool bRestart )
+{
+ if ( !bRestart )
+ {
+ // attribute not contained in paragraph style's attribute set. Thus,
+ // it can be reset to the attribute pool default by resetting the attribute.
+ ResetAttr( RES_PARATR_LIST_ISRESTART );
+ }
+ else
+ {
+ SfxBoolItem aNewIsRestartItem( RES_PARATR_LIST_ISRESTART,
+ true );
+ SetAttr( aNewIsRestartItem );
+ }
+}
+
+bool SwTextNode::IsListRestart() const
+{
+ const SfxBoolItem& aIsRestartItem = GetAttr( RES_PARATR_LIST_ISRESTART );
+
+ return aIsRestartItem.GetValue();
+}
+
+/** Returns if the paragraph has a visible numbering or bullet.
+ This includes all kinds of numbering/bullet/outlines.
+ The concrete list label string has to be checked, too.
+ */
+bool SwTextNode::HasVisibleNumberingOrBullet() const
+{
+ const SwNumRule* pRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if ( pRule && IsCountedInList())
+ {
+ const SwNumFormat& rFormat = pRule->Get(lcl_BoundListLevel(GetActualListLevel()));
+ if (getIDocumentSettingAccess()->get(DocumentSettingId::NO_NUMBERING_SHOW_FOLLOWBY))
+ // True if we have something in label text or there is a non-empty
+ // FollowedBy separator (space, tab or whatsoever)
+ return rFormat.GetLabelFollowedBy() != SvxNumberFormat::LabelFollowedBy::NOTHING ||
+ !pRule->MakeNumString(*GetNum()).isEmpty();
+ else
+ // #i87154#
+ // Correction of #newlistlevelattrs#:
+ // The numbering type has to be checked for bullet lists.
+ return SVX_NUM_NUMBER_NONE != rFormat.GetNumberingType() ||
+ !pRule->MakeNumString(*(GetNum())).isEmpty();
+ }
+
+ return false;
+}
+
+void SwTextNode::SetAttrListRestartValue( SwNumberTree::tSwNumTreeNumber nNumber )
+{
+ const bool bChanged( HasAttrListRestartValue()
+ ? GetAttrListRestartValue() != nNumber
+ : nNumber != USHRT_MAX );
+
+ if ( !bChanged && HasAttrListRestartValue() )
+ return;
+
+ if ( nNumber == USHRT_MAX )
+ {
+ ResetAttr( RES_PARATR_LIST_RESTARTVALUE );
+ }
+ else
+ {
+ SfxInt16Item aNewListRestartValueItem( RES_PARATR_LIST_RESTARTVALUE,
+ static_cast<sal_Int16>(nNumber) );
+ SetAttr( aNewListRestartValueItem );
+ }
+}
+
+bool SwTextNode::HasAttrListRestartValue() const
+{
+ return GetpSwAttrSet() &&
+ GetpSwAttrSet()->GetItemState( RES_PARATR_LIST_RESTARTVALUE, false ) == SfxItemState::SET;
+}
+SwNumberTree::tSwNumTreeNumber SwTextNode::GetAttrListRestartValue() const
+{
+ OSL_ENSURE( HasAttrListRestartValue(),
+ "<SwTextNode::GetAttrListRestartValue()> - only ask for list restart value, if attribute is set at text node." );
+
+ const SfxInt16Item& aListRestartValueItem =
+ GetAttr( RES_PARATR_LIST_RESTARTVALUE );
+ return static_cast<SwNumberTree::tSwNumTreeNumber>(aListRestartValueItem.GetValue());
+}
+
+SwNumberTree::tSwNumTreeNumber SwTextNode::GetActualListStartValue() const
+{
+ SwNumberTree::tSwNumTreeNumber nListRestartValue = 1;
+
+ if ( IsListRestart() && HasAttrListRestartValue() )
+ {
+ nListRestartValue = GetAttrListRestartValue();
+ }
+ else
+ {
+ SwNumRule* pRule = GetNumRule();
+ if ( pRule )
+ {
+ const SwNumFormat* pFormat =
+ pRule->GetNumFormat( o3tl::narrowing<sal_uInt16>(GetAttrListLevel()) );
+ if ( pFormat )
+ {
+ nListRestartValue = pFormat->GetStart();
+ }
+ }
+ }
+
+ return nListRestartValue;
+}
+
+bool SwTextNode::IsNotifiable() const
+{
+ return m_bNotifiable && IsNotificationEnabled();
+}
+
+bool SwTextNode::IsNotificationEnabled() const
+{
+ const SwDoc& rDoc = GetDoc();
+ return !rDoc.IsInReading() && !rDoc.IsInDtor();
+}
+
+void SwTextNode::SetCountedInList( bool bCounted )
+{
+ if ( bCounted )
+ {
+ // attribute not contained in paragraph style's attribute set. Thus,
+ // it can be reset to the attribute pool default by resetting the attribute.
+ ResetAttr( RES_PARATR_LIST_ISCOUNTED );
+ }
+ else
+ {
+ SfxBoolItem aIsCountedInListItem( RES_PARATR_LIST_ISCOUNTED, false );
+ SetAttr( aIsCountedInListItem );
+ }
+}
+
+bool SwTextNode::IsCountedInList() const
+{
+ const SfxBoolItem& aIsCountedInListItem = GetAttr( RES_PARATR_LIST_ISCOUNTED );
+
+ return aIsCountedInListItem.GetValue();
+}
+
+static SwList * FindList(SwTextNode *const pNode)
+{
+ const OUString sListId = pNode->GetListId();
+ if (!sListId.isEmpty())
+ {
+ auto & rIDLA(pNode->GetDoc().getIDocumentListsAccess());
+ SwList* pList = rIDLA.getListByName( sListId );
+ if ( pList == nullptr )
+ {
+ // Create corresponding list.
+ SwNumRule* pNumRule = pNode->GetNumRule();
+ if ( pNumRule )
+ {
+ pList = rIDLA.createList(sListId, pNode->GetNumRule()->GetName());
+ }
+ }
+ OSL_ENSURE( pList != nullptr,
+ "<SwTextNode::AddToList()> - no list for given list id. Serious defect" );
+ return pList;
+ }
+ return nullptr;
+}
+
+void SwTextNode::AddToList()
+{
+ if ( IsInList() )
+ {
+ OSL_FAIL( "<SwTextNode::AddToList()> - the text node is already added to a list. Serious defect" );
+ return;
+ }
+
+ SwList *const pList(FindList(this));
+ if (!(pList && GetNodes().IsDocNodes())) // not for undo nodes
+ return;
+
+ assert(!mpNodeNum);
+ mpNodeNum.reset(new SwNodeNum(this, false));
+ pList->InsertListItem(*mpNodeNum, SwListRedlineType::SHOW, GetAttrListLevel(), GetDoc());
+
+ // set redline lists
+ // "default" list: visible items in Show Changes mode (tracked insertions and deletions)
+ // "hidden" list: visible items in Hide Changes mode (tracked insertions, but not deletions)
+ // "orig" list: visible items rejecting all changes (no tracked insertions and deletions)
+ bool bRecordChanges = GetDoc().GetDocShell() && GetDoc().GetDocShell()->IsChangeRecording();
+ if (!bRecordChanges || GetDoc().IsInXMLImport() || GetDoc().IsInWriterfilterImport() )
+ {
+ const SwRedlineTable& rRedTable = GetDoc().getIDocumentRedlineAccess().GetRedlineTable();
+ SwRedlineTable::size_type nRedlPos = GetDoc().getIDocumentRedlineAccess().GetRedlinePos(*this, RedlineType::Insert);
+ // paragraph start is not in a tracked insertion
+ if ( SwRedlineTable::npos == nRedlPos || GetIndex() <= rRedTable[nRedlPos]->Start()->GetNode().GetIndex() )
+ {
+ AddToListOrig();
+
+ // if the paragraph is not deleted, add to the "hidden" list, too
+ SwRedlineTable::size_type nRedlPosDel = GetDoc().getIDocumentRedlineAccess().GetRedlinePos(*this, RedlineType::Delete);
+ if ( SwRedlineTable::npos == nRedlPosDel )
+ AddToListRLHidden();
+ }
+ // inserted paragraph, e.g. during file load, add to the "hidden" list
+ else if ( SwRedlineTable::npos != nRedlPos )
+ AddToListRLHidden();
+ }
+ else if ( bRecordChanges )
+ AddToListRLHidden();
+
+ // iterate all frames & if there's one with hidden layout...
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> iter(*this);
+ for (SwTextFrame* pFrame = iter.First(); pFrame && !mpNodeNumRLHidden; pFrame = iter.Next())
+ {
+ if (pFrame->getRootFrame()->IsHideRedlines())
+ {
+ if (pFrame->GetTextNodeForParaProps() == this)
+ {
+ AddToListRLHidden();
+ }
+ break; // assume it's consistent, need to check only once
+ }
+ }
+}
+
+void SwTextNode::AddToListRLHidden()
+{
+ if (mpNodeNumRLHidden)
+ return;
+
+ SwList *const pList(FindList(this));
+ if (pList)
+ {
+ assert(!mpNodeNumRLHidden);
+ mpNodeNumRLHidden.reset(new SwNodeNum(this, true));
+ pList->InsertListItem(*mpNodeNumRLHidden, SwListRedlineType::HIDDEN, GetAttrListLevel(), GetDoc());
+ }
+}
+
+void SwTextNode::AddToListOrig()
+{
+ if (mpNodeNumOrig)
+ return;
+
+ SwList *const pList(FindList(this));
+ if (pList)
+ {
+ assert(!mpNodeNumOrig);
+ mpNodeNumOrig.reset(new SwNodeNum(this, true));
+ pList->InsertListItem(*mpNodeNumOrig, SwListRedlineType::ORIGTEXT, GetAttrListLevel(), GetDoc());
+ }
+}
+
+void SwTextNode::RemoveFromList()
+{
+ // sw_redlinehide: ensure it's removed from the other half too!
+ RemoveFromListRLHidden();
+ RemoveFromListOrig();
+ if ( IsInList() )
+ {
+ SwList::RemoveListItem(*mpNodeNum, GetDoc());
+ mpNodeNum.reset();
+
+ SetWordCountDirty( true );
+ }
+}
+
+void SwTextNode::RemoveFromListRLHidden()
+{
+ if (mpNodeNumRLHidden) // direct access because RemoveFromList doesn't have layout
+ {
+ assert(mpNodeNumRLHidden->GetParent() || !GetNodes().IsDocNodes());
+ SwList::RemoveListItem(*mpNodeNumRLHidden, GetDoc());
+ mpNodeNumRLHidden.reset();
+
+ SetWordCountDirty( true );
+ }
+}
+
+void SwTextNode::RemoveFromListOrig()
+{
+ if (mpNodeNumOrig) // direct access because RemoveFromList doesn't have layout
+ {
+ assert(mpNodeNumOrig->GetParent() || !GetNodes().IsDocNodes());
+ SwList::RemoveListItem(*mpNodeNumOrig, GetDoc());
+ mpNodeNumOrig.reset();
+
+ SetWordCountDirty( true );
+ }
+}
+
+bool SwTextNode::IsInList() const
+{
+ return GetNum() != nullptr && GetNum()->GetParent() != nullptr;
+}
+
+bool SwTextNode::IsFirstOfNumRule(SwRootFrame const& rLayout) const
+{
+ bool bResult = false;
+
+ SwNodeNum const*const pNum(GetNum(&rLayout));
+ if (pNum && pNum->GetNumRule())
+ bResult = pNum->IsFirst();
+
+ return bResult;
+}
+
+void SwTextNode::SetListId(OUString const& rListId)
+{
+ const SfxStringItem& rListIdItem =
+ GetAttr( RES_PARATR_LIST_ID );
+ if (rListIdItem.GetValue() != rListId)
+ {
+ if (rListId.isEmpty())
+ {
+ ResetAttr( RES_PARATR_LIST_ID );
+ }
+ else
+ {
+ SfxStringItem aNewListIdItem(RES_PARATR_LIST_ID, rListId);
+ SetAttr( aNewListIdItem );
+ }
+ }
+}
+
+OUString SwTextNode::GetListId() const
+{
+ const SfxStringItem& rListIdItem =
+ GetAttr( RES_PARATR_LIST_ID );
+ const OUString& sListId {rListIdItem.GetValue()};
+
+ // As long as no explicit list id attribute is set, use the list id of
+ // the list, which has been created for the applied list style.
+ if (sListId.isEmpty())
+ {
+ SwNumRule* pRule = GetNumRule();
+ if ( pRule )
+ {
+ return pRule->GetDefaultListId();
+ }
+ }
+
+ return sListId;
+}
+
+/** Determines, if the list level indent attributes can be applied to the
+ paragraph.
+
+ The list level indents can be applied to the paragraph under the one
+ of following conditions:
+ - the list style is directly applied to the paragraph and the paragraph
+ has no own indent attributes.
+ - the list style is applied to the paragraph through one of its paragraph
+ styles, the paragraph has no own indent attributes and on the paragraph
+ style hierarchy from the paragraph to the paragraph style with the
+ list style no indent attributes are found.
+
+ @return bitmask
+*/
+::sw::ListLevelIndents SwTextNode::AreListLevelIndentsApplicable() const
+{
+ ::sw::ListLevelIndents ret(::sw::ListLevelIndents::No);
+ if (AreListLevelIndentsApplicableImpl(RES_MARGIN_FIRSTLINE))
+ {
+ ret |= ::sw::ListLevelIndents::FirstLine;
+ }
+ if (AreListLevelIndentsApplicableImpl(RES_MARGIN_TEXTLEFT))
+ {
+ ret |= ::sw::ListLevelIndents::LeftMargin;
+ }
+ return ret;
+}
+
+bool SwTextNode::AreListLevelIndentsApplicableImpl(sal_uInt16 const nWhich) const
+{
+ bool bAreListLevelIndentsApplicable( true );
+
+ if ( !GetNum() || !GetNum()->GetNumRule() )
+ {
+ // no list style applied to paragraph
+ bAreListLevelIndentsApplicable = false;
+ }
+ else if ( HasSwAttrSet() &&
+ GetpSwAttrSet()->GetItemState(nWhich, false) == SfxItemState::SET)
+ {
+ // paragraph has hard-set indent attributes
+ bAreListLevelIndentsApplicable = false;
+ }
+ else if ( HasSwAttrSet() &&
+ GetpSwAttrSet()->GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET )
+ {
+ // list style is directly applied to paragraph and paragraph has no
+ // hard-set indent attributes
+ bAreListLevelIndentsApplicable = true;
+ }
+ else
+ {
+ // list style is applied through one of the paragraph styles and
+ // paragraph has no hard-set indent attributes
+
+ // check, paragraph's
+ const SwTextFormatColl* pColl = GetTextColl();
+ while ( pColl )
+ {
+ if (pColl->GetAttrSet().GetItemState(nWhich, false) == SfxItemState::SET)
+ {
+ // indent attributes found in the paragraph style hierarchy.
+ bAreListLevelIndentsApplicable = false;
+ break;
+ }
+
+ if ( pColl->GetAttrSet().GetItemState( RES_PARATR_NUMRULE, false ) == SfxItemState::SET )
+ {
+ // paragraph style with the list style found and until now no
+ // indent attributes are found in the paragraph style hierarchy.
+ bAreListLevelIndentsApplicable = true;
+ break;
+ }
+
+ pColl = dynamic_cast<const SwTextFormatColl*>(pColl->DerivedFrom());
+ OSL_ENSURE( pColl,
+ "<SwTextNode::AreListLevelIndentsApplicable()> - something wrong in paragraph's style hierarchy. The applied list style is not found." );
+ }
+ }
+
+ return bAreListLevelIndentsApplicable;
+}
+
+/** Retrieves the list tab stop position, if the paragraph's list level defines
+ one and this list tab stop has to merged into the tap stops of the paragraph
+
+ @param nListTabStopPosition
+ output parameter - containing the list tab stop position
+
+ @return boolean - indicating, if a list tab stop position is provided
+*/
+bool SwTextNode::GetListTabStopPosition( tools::Long& nListTabStopPosition ) const
+{
+ bool bListTabStopPositionProvided(false);
+
+ const SwNumRule* pNumRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if ( pNumRule && HasVisibleNumberingOrBullet() && GetActualListLevel() >= 0 )
+ {
+ const SwNumFormat& rFormat = pNumRule->Get( o3tl::narrowing<sal_uInt16>(GetActualListLevel()) );
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT &&
+ rFormat.GetLabelFollowedBy() == SvxNumberFormat::LISTTAB )
+ {
+ bListTabStopPositionProvided = true;
+ nListTabStopPosition = rFormat.GetListtabPos();
+
+ if ( getIDocumentSettingAccess()->get(DocumentSettingId::TABS_RELATIVE_TO_INDENT) )
+ {
+ // tab stop position are treated to be relative to the "before text"
+ // indent value of the paragraph. Thus, adjust <nListTabStopPos>.
+ if (AreListLevelIndentsApplicable() & ::sw::ListLevelIndents::LeftMargin)
+ {
+ nListTabStopPosition -= rFormat.GetIndentAt();
+ }
+ else if (!getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING))
+ {
+ SvxTextLeftMarginItem const aItem(GetSwAttrSet().GetTextLeftMargin());
+ nListTabStopPosition -= aItem.GetTextLeft();
+ }
+ }
+ }
+ }
+
+ return bListTabStopPositionProvided;
+}
+
+OUString SwTextNode::GetLabelFollowedBy() const
+{
+ const SwNumRule* pNumRule = GetNum() ? GetNum()->GetNumRule() : nullptr;
+ if ( pNumRule && HasVisibleNumberingOrBullet() && GetActualListLevel() >= 0 )
+ {
+ const SwNumFormat& rFormat = pNumRule->Get( o3tl::narrowing<sal_uInt16>(GetActualListLevel()) );
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ return rFormat.GetLabelFollowedByAsString();
+ }
+ }
+
+ return OUString();
+}
+
+void SwTextNode::CalcHiddenCharFlags() const
+{
+ sal_Int32 nStartPos;
+ sal_Int32 nEndPos;
+ // Update of the flags is done inside GetBoundsOfHiddenRange()
+ SwScriptInfo::GetBoundsOfHiddenRange( *this, 0, nStartPos, nEndPos );
+}
+
+// #i12836# enhanced pdf export
+bool SwTextNode::IsHidden() const
+{
+ if ( IsHiddenByParaField() || HasHiddenCharAttribute( true ) )
+ return true;
+
+ const SwSectionNode* pSectNd = FindSectionNode();
+ return pSectNd && pSectNd->GetSection().IsHiddenFlag();
+}
+
+namespace {
+ // Helper class for special handling of setting attributes at text node:
+ // In constructor an instance of the helper class recognize whose attributes
+ // are set and perform corresponding actions before the intrinsic set of
+ // attributes has been taken place.
+ // In the destructor - after the attributes have been set at the text
+ // node - corresponding actions are performed.
+ // The following is handled:
+ // (1) When the list style attribute - RES_PARATR_NUMRULE - is set,
+ // (A) list style attribute is empty -> the text node is removed from
+ // its list.
+ // (B) list style attribute is not empty
+ // (a) text node has no list style -> add text node to its list after
+ // the attributes have been set.
+ // (b) text node has list style -> change of list style is notified
+ // after the attributes have been set.
+ // (2) When the list id attribute - RES_PARATR_LIST_ID - is set and changed,
+ // the text node is removed from its current list before the attributes
+ // are set and added to its new list after the attributes have been set.
+ // (3) Notify list tree, if list level - RES_PARATR_LIST_LEVEL - is set
+ // and changed after the attributes have been set
+ // (4) Notify list tree, if list restart - RES_PARATR_LIST_ISRESTART - is set
+ // and changed after the attributes have been set
+ // (5) Notify list tree, if list restart value - RES_PARATR_LIST_RESTARTVALUE -
+ // is set and changed after the attributes have been set
+ // (6) Notify list tree, if count in list - RES_PARATR_LIST_ISCOUNTED - is set
+ // and changed after the attributes have been set
+ // (7) Set or Reset empty list style due to changed outline level - RES_PARATR_OUTLINELEVEL.
+ class HandleSetAttrAtTextNode
+ {
+ public:
+ HandleSetAttrAtTextNode( SwTextNode& rTextNode,
+ const SfxPoolItem& pItem );
+ HandleSetAttrAtTextNode( SwTextNode& rTextNode,
+ const SfxItemSet& rItemSet );
+ ~HandleSetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE;
+
+ private:
+ SwTextNode& mrTextNode;
+ bool mbAddTextNodeToList;
+ bool mbUpdateListLevel;
+ bool mbUpdateListRestart;
+ bool mbUpdateListCount;
+ // #i70748#
+ bool mbOutlineLevelSet;
+ };
+
+ HandleSetAttrAtTextNode::HandleSetAttrAtTextNode( SwTextNode& rTextNode,
+ const SfxPoolItem& pItem )
+ : mrTextNode( rTextNode ),
+ mbAddTextNodeToList( false ),
+ mbUpdateListLevel( false ),
+ mbUpdateListRestart( false ),
+ mbUpdateListCount( false ),
+ // #i70748#
+ mbOutlineLevelSet( false )
+ {
+ switch ( pItem.Which() )
+ {
+ // handle RES_PARATR_NUMRULE
+ case RES_PARATR_NUMRULE:
+ {
+ mrTextNode.RemoveFromList();
+
+ const SwNumRuleItem& rNumRuleItem =
+ dynamic_cast<const SwNumRuleItem&>(pItem);
+ if ( !rNumRuleItem.GetValue().isEmpty() )
+ {
+ mbAddTextNodeToList = true;
+ // #i105562#
+
+ mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ }
+ }
+ break;
+
+ // handle RES_PARATR_LIST_ID
+ case RES_PARATR_LIST_ID:
+ {
+ const SfxStringItem& rListIdItem =
+ dynamic_cast<const SfxStringItem&>(pItem);
+ OSL_ENSURE( rListIdItem.GetValue().getLength() > 0,
+ "<HandleSetAttrAtTextNode(..)> - empty list id attribute not expected. Serious defect." );
+ const OUString sListIdOfTextNode = rTextNode.GetListId();
+ if ( rListIdItem.GetValue() != sListIdOfTextNode )
+ {
+ mbAddTextNodeToList = true;
+ if ( mrTextNode.IsInList() )
+ {
+ mrTextNode.RemoveFromList();
+ }
+ }
+ }
+ break;
+
+ // handle RES_PARATR_LIST_LEVEL
+ case RES_PARATR_LIST_LEVEL:
+ {
+ const SfxInt16Item& aListLevelItem =
+ dynamic_cast<const SfxInt16Item&>(pItem);
+ if ( aListLevelItem.GetValue() != mrTextNode.GetAttrListLevel() )
+ {
+ mbUpdateListLevel = true;
+ }
+ }
+ break;
+
+ // handle RES_PARATR_LIST_ISRESTART
+ case RES_PARATR_LIST_ISRESTART:
+ {
+ const SfxBoolItem& aListIsRestartItem =
+ dynamic_cast<const SfxBoolItem&>(pItem);
+ if ( aListIsRestartItem.GetValue() !=
+ mrTextNode.IsListRestart() )
+ {
+ mbUpdateListRestart = true;
+ }
+ }
+ break;
+
+ // handle RES_PARATR_LIST_RESTARTVALUE
+ case RES_PARATR_LIST_RESTARTVALUE:
+ {
+ const SfxInt16Item& aListRestartValueItem =
+ dynamic_cast<const SfxInt16Item&>(pItem);
+ if ( !mrTextNode.HasAttrListRestartValue() ||
+ aListRestartValueItem.GetValue() != mrTextNode.GetAttrListRestartValue() )
+ {
+ mbUpdateListRestart = true;
+ }
+ }
+ break;
+
+ // handle RES_PARATR_LIST_ISCOUNTED
+ case RES_PARATR_LIST_ISCOUNTED:
+ {
+ const SfxBoolItem& aIsCountedInListItem =
+ dynamic_cast<const SfxBoolItem&>(pItem);
+ if ( aIsCountedInListItem.GetValue() !=
+ mrTextNode.IsCountedInList() )
+ {
+ mbUpdateListCount = true;
+ }
+ }
+ break;
+
+ // #i70748#
+ // handle RES_PARATR_OUTLINELEVEL
+ case RES_PARATR_OUTLINELEVEL:
+ {
+ const SfxUInt16Item& aOutlineLevelItem =
+ dynamic_cast<const SfxUInt16Item&>(pItem);
+ if ( aOutlineLevelItem.GetValue() != mrTextNode.GetAttrOutlineLevel() )
+ {
+ mbOutlineLevelSet = true;
+ }
+ }
+ break;
+ }
+
+ }
+
+ HandleSetAttrAtTextNode::HandleSetAttrAtTextNode( SwTextNode& rTextNode,
+ const SfxItemSet& rItemSet )
+ : mrTextNode( rTextNode ),
+ mbAddTextNodeToList( false ),
+ mbUpdateListLevel( false ),
+ mbUpdateListRestart( false ),
+ mbUpdateListCount( false ),
+ // #i70748#
+ mbOutlineLevelSet( false )
+ {
+ // handle RES_PARATR_NUMRULE
+ if ( const SwNumRuleItem* pNumRuleItem = rItemSet.GetItemIfSet( RES_PARATR_NUMRULE, false ) )
+ {
+ mrTextNode.RemoveFromList();
+
+ if ( !pNumRuleItem->GetValue().isEmpty() )
+ {
+ mbAddTextNodeToList = true;
+ // #i70748#
+ mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ }
+ }
+
+ // handle RES_PARATR_LIST_ID
+ if ( const SfxStringItem* pListIdItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_ID, false ) )
+ {
+ const OUString sListIdOfTextNode = mrTextNode.GetListId();
+ if ( pListIdItem->GetValue() != sListIdOfTextNode )
+ {
+ mbAddTextNodeToList = true;
+ if ( mrTextNode.IsInList() )
+ {
+ mrTextNode.RemoveFromList();
+ }
+ }
+ }
+
+ // handle RES_PARATR_LIST_LEVEL
+ if ( const SfxInt16Item* pListLevelItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_LEVEL, false ) )
+ {
+ if (pListLevelItem->GetValue() != mrTextNode.GetAttrListLevel())
+ {
+ mbUpdateListLevel = true;
+ }
+ }
+
+ // handle RES_PARATR_LIST_ISRESTART
+ if ( const SfxBoolItem* pListIsRestartItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_ISRESTART, false ) )
+ {
+ if (pListIsRestartItem->GetValue() != mrTextNode.IsListRestart())
+ {
+ mbUpdateListRestart = true;
+ }
+ }
+
+ // handle RES_PARATR_LIST_RESTARTVALUE
+ if ( const SfxInt16Item* pListRestartValueItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_RESTARTVALUE, false ) )
+ {
+ if ( !mrTextNode.HasAttrListRestartValue() ||
+ pListRestartValueItem->GetValue() != mrTextNode.GetAttrListRestartValue() )
+ {
+ mbUpdateListRestart = true;
+ }
+ }
+
+ // handle RES_PARATR_LIST_ISCOUNTED
+ if ( const SfxBoolItem* pIsCountedInListItem = rItemSet.GetItemIfSet( RES_PARATR_LIST_ISCOUNTED, false ) )
+ {
+ if (pIsCountedInListItem->GetValue() != mrTextNode.IsCountedInList())
+ {
+ mbUpdateListCount = true;
+ }
+ }
+
+ // #i70748#
+ // handle RES_PARATR_OUTLINELEVEL
+ if ( const SfxUInt16Item* pOutlineLevelItem = rItemSet.GetItemIfSet( RES_PARATR_OUTLINELEVEL, false ) )
+ {
+ if (pOutlineLevelItem->GetValue() != mrTextNode.GetAttrOutlineLevel())
+ {
+ mbOutlineLevelSet = true;
+ }
+ }
+ }
+
+ HandleSetAttrAtTextNode::~HandleSetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE
+ {
+ if ( mbAddTextNodeToList )
+ {
+ SwNumRule* pNumRuleAtTextNode = mrTextNode.GetNumRule();
+ if ( pNumRuleAtTextNode )
+ {
+ mrTextNode.AddToList();
+ }
+ }
+ else
+ {
+ if ( mbUpdateListLevel && mrTextNode.IsInList() )
+ {
+ auto const nLevel(mrTextNode.GetAttrListLevel());
+ const SwDoc& rDoc(mrTextNode.GetDoc());
+ mrTextNode.DoNum(
+ [nLevel, &rDoc](SwNodeNum & rNum) { rNum.SetLevelInListTree(nLevel, rDoc); });
+ }
+
+ if ( mbUpdateListRestart && mrTextNode.IsInList() )
+ {
+ const SwDoc& rDoc(mrTextNode.GetDoc());
+ mrTextNode.DoNum(
+ [&rDoc](SwNodeNum & rNum) {
+ rNum.InvalidateMe();
+ rNum.NotifyInvalidSiblings(rDoc);
+ });
+ }
+
+ if (mbUpdateListCount && mrTextNode.IsInList() && HasNumberingWhichNeedsLayoutUpdate(mrTextNode))
+ {
+ // Repaint all text frames that belong to this numbering to avoid outdated generated
+ // numbers.
+ const SwDoc& rDoc(mrTextNode.GetDoc());
+ mrTextNode.DoNum(
+ [&rDoc](SwNodeNum & rNum) { rNum.InvalidateAndNotifyTree(rDoc); });
+ }
+ }
+
+ // #i70748#
+ if (!mbOutlineLevelSet)
+ return;
+
+ mrTextNode.GetNodes().UpdateOutlineNode(mrTextNode);
+ if (mrTextNode.GetAttrOutlineLevel() == 0)
+ {
+ mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ }
+ else
+ {
+ if ( mrTextNode.GetSwAttrSet().GetItemState( RES_PARATR_NUMRULE )
+ != SfxItemState::SET )
+ {
+ mrTextNode.SetEmptyListStyleDueToSetOutlineLevelAttr();
+ }
+ }
+ }
+ // End of class <HandleSetAttrAtTextNode>
+}
+
+bool SwTextNode::SetAttr( const SfxPoolItem& pItem )
+{
+ const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
+ mbInSetOrResetAttr = true;
+
+ HandleSetAttrAtTextNode aHandleSetAttr( *this, pItem );
+
+ bool bRet = SwContentNode::SetAttr( pItem );
+
+ mbInSetOrResetAttr = bOldIsSetOrResetAttr;
+
+ return bRet;
+}
+
+bool SwTextNode::SetAttr( const SfxItemSet& rSet )
+{
+ const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
+ mbInSetOrResetAttr = true;
+
+ HandleSetAttrAtTextNode aHandleSetAttr( *this, rSet );
+
+ bool bRet = SwContentNode::SetAttr( rSet );
+
+ mbInSetOrResetAttr = bOldIsSetOrResetAttr;
+
+ return bRet;
+}
+
+void SwTextNode::SetInSwUndo(bool bInUndo)
+{
+ m_bInUndo = bInUndo;
+}
+
+namespace {
+ // Helper class for special handling of resetting attributes at text node:
+ // In constructor an instance of the helper class recognize whose attributes
+ // are reset and perform corresponding actions before the intrinsic reset of
+ // attributes has been taken place.
+ // In the destructor - after the attributes have been reset at the text
+ // node - corresponding actions are performed.
+ // The following is handled:
+ // (1) When the list style attribute - RES_PARATR_NUMRULE - is reset,
+ // the text is removed from its list before the attributes have been reset.
+ // (2) When the list id attribute - RES_PARATR_LIST_ID - is reset,
+ // the text is removed from its list before the attributes have been reset.
+ // (3) Notify list tree, if list level - RES_PARATR_LIST_LEVEL - is reset.
+ // (4) Notify list tree, if list restart - RES_PARATR_LIST_ISRESTART - is reset.
+ // (5) Notify list tree, if list restart value - RES_PARATR_LIST_RESTARTVALUE - is reset.
+ // (6) Notify list tree, if count in list - RES_PARATR_LIST_ISCOUNTED - is reset.
+ // (7) Reset empty list style, if outline level attribute - RES_PARATR_OUTLINELEVEL - is reset.
+ class HandleResetAttrAtTextNode
+ {
+ public:
+ HandleResetAttrAtTextNode( SwTextNode& rTextNode,
+ const sal_uInt16 nWhich1,
+ sal_uInt16 nWhich2 );
+ HandleResetAttrAtTextNode( SwTextNode& rTextNode,
+ const std::vector<sal_uInt16>& rWhichArr );
+ explicit HandleResetAttrAtTextNode( SwTextNode& rTextNode );
+
+ ~HandleResetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE;
+
+ private:
+ SwTextNode& mrTextNode;
+ bool mbListStyleOrIdReset;
+ bool mbUpdateListLevel;
+ bool mbUpdateListRestart;
+ bool mbUpdateListCount;
+
+ void init( sal_uInt16 nWhich, bool& rbRemoveFromList );
+ };
+
+ HandleResetAttrAtTextNode::HandleResetAttrAtTextNode( SwTextNode& rTextNode,
+ const sal_uInt16 nWhich1,
+ sal_uInt16 nWhich2 )
+ : mrTextNode( rTextNode ),
+ mbListStyleOrIdReset( false ),
+ mbUpdateListLevel( false ),
+ mbUpdateListRestart( false ),
+ mbUpdateListCount( false )
+ {
+ if ( nWhich2 < nWhich1 )
+ nWhich2 = nWhich1;
+ bool bRemoveFromList( false );
+ for ( sal_uInt16 nWhich = nWhich1; nWhich <= nWhich2; ++nWhich )
+ init( nWhich, bRemoveFromList );
+ if ( bRemoveFromList && mrTextNode.IsInList() )
+ mrTextNode.RemoveFromList();
+ }
+
+ HandleResetAttrAtTextNode::HandleResetAttrAtTextNode( SwTextNode& rTextNode,
+ const std::vector<sal_uInt16>& rWhichArr )
+ : mrTextNode( rTextNode ),
+ mbListStyleOrIdReset( false ),
+ mbUpdateListLevel( false ),
+ mbUpdateListRestart( false ),
+ mbUpdateListCount( false )
+ {
+ bool bRemoveFromList( false );
+ for ( sal_uInt16 nWhich : rWhichArr )
+ init( nWhich, bRemoveFromList );
+ if ( bRemoveFromList && mrTextNode.IsInList() )
+ mrTextNode.RemoveFromList();
+ }
+
+ HandleResetAttrAtTextNode::HandleResetAttrAtTextNode( SwTextNode& rTextNode )
+ : mrTextNode( rTextNode ),
+ mbListStyleOrIdReset( true ),
+ mbUpdateListLevel( false ),
+ mbUpdateListRestart( false ),
+ mbUpdateListCount( false )
+ {
+ if ( rTextNode.IsInList() )
+ {
+ rTextNode.RemoveFromList();
+ }
+ // #i70748#
+ mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ }
+
+ void HandleResetAttrAtTextNode::init( sal_uInt16 rWhich, bool& rbRemoveFromList )
+ {
+ if ( rWhich == RES_PARATR_NUMRULE )
+ {
+ rbRemoveFromList = rbRemoveFromList ||
+ mrTextNode.GetNumRule() != nullptr;
+ mbListStyleOrIdReset = true;
+ }
+ else if ( rWhich == RES_PARATR_LIST_ID )
+ {
+ rbRemoveFromList = rbRemoveFromList ||
+ ( mrTextNode.GetpSwAttrSet() &&
+ mrTextNode.GetpSwAttrSet()->GetItemState( RES_PARATR_LIST_ID, false ) == SfxItemState::SET );
+ mbListStyleOrIdReset = true;
+ }
+ else if ( rWhich == RES_PARATR_OUTLINELEVEL )
+ mrTextNode.ResetEmptyListStyleDueToResetOutlineLevelAttr();
+ else if ( rWhich == RES_BACKGROUND )
+ mrTextNode.ResetAttr( XATTR_FILL_FIRST, XATTR_FILL_LAST );
+
+ if ( !rbRemoveFromList )
+ {
+ // RES_PARATR_LIST_LEVEL
+ mbUpdateListLevel = mbUpdateListLevel ||
+ ( rWhich == RES_PARATR_LIST_LEVEL &&
+ mrTextNode.HasAttrListLevel() );
+
+ // RES_PARATR_LIST_ISRESTART and RES_PARATR_LIST_RESTARTVALUE
+ mbUpdateListRestart = mbUpdateListRestart ||
+ ( rWhich == RES_PARATR_LIST_ISRESTART &&
+ mrTextNode.IsListRestart() ) ||
+ ( rWhich == RES_PARATR_LIST_RESTARTVALUE &&
+ mrTextNode.HasAttrListRestartValue() );
+
+ // RES_PARATR_LIST_ISCOUNTED
+ mbUpdateListCount = mbUpdateListCount ||
+ ( rWhich == RES_PARATR_LIST_ISCOUNTED &&
+ !mrTextNode.IsCountedInList() );
+ }
+ }
+
+ HandleResetAttrAtTextNode::~HandleResetAttrAtTextNode() COVERITY_NOEXCEPT_FALSE
+ {
+ if ( mbListStyleOrIdReset && !mrTextNode.IsInList() )
+ {
+ // check, if in spite of the reset of the list style or the list id
+ // the paragraph still has to be added to a list.
+ if (mrTextNode.GetNumRule() && !mrTextNode.GetListId().isEmpty())
+ {
+ // #i96062#
+ // If paragraph has no list level attribute set and list style
+ // is the outline style, apply outline level as the list level.
+ if ( !mrTextNode.HasAttrListLevel() &&
+ mrTextNode.GetNumRule()->GetName()==SwNumRule::GetOutlineRuleName() &&
+ mrTextNode.GetTextColl()->IsAssignedToListLevelOfOutlineStyle() )
+ {
+ int nNewListLevel = mrTextNode.GetTextColl()->GetAssignedOutlineStyleLevel();
+ if ( 0 <= nNewListLevel && nNewListLevel < MAXLEVEL )
+ {
+ mrTextNode.SetAttrListLevel( nNewListLevel );
+ }
+ }
+ mrTextNode.AddToList();
+ }
+ // #i70748#
+ // #i105562#
+ else
+ {
+ if (mrTextNode.GetpSwAttrSet()
+ && mrTextNode.GetAttr(RES_PARATR_OUTLINELEVEL, false).GetValue() > 0)
+ {
+ mrTextNode.SetEmptyListStyleDueToSetOutlineLevelAttr();
+ }
+ }
+ }
+
+ if ( !mrTextNode.IsInList() )
+ return;
+
+ if ( mbUpdateListLevel )
+ {
+ auto const nLevel(mrTextNode.GetAttrListLevel());
+ const SwDoc& rDoc(mrTextNode.GetDoc());
+ mrTextNode.DoNum(
+ [nLevel, &rDoc](SwNodeNum & rNum) { rNum.SetLevelInListTree(nLevel, rDoc); });
+ }
+
+ if ( mbUpdateListRestart )
+ {
+ const SwDoc& rDoc(mrTextNode.GetDoc());
+ mrTextNode.DoNum(
+ [&rDoc](SwNodeNum & rNum) {
+ rNum.InvalidateMe();
+ rNum.NotifyInvalidSiblings(rDoc);
+ });
+ }
+
+ if ( mbUpdateListCount )
+ {
+ const SwDoc& rDoc(mrTextNode.GetDoc());
+ mrTextNode.DoNum(
+ [&rDoc](SwNodeNum & rNum) { rNum.InvalidateAndNotifyTree(rDoc); });
+ }
+ }
+ // End of class <HandleResetAttrAtTextNode>
+}
+
+bool SwTextNode::ResetAttr( sal_uInt16 nWhich1, sal_uInt16 nWhich2 )
+{
+ const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
+ mbInSetOrResetAttr = true;
+
+ HandleResetAttrAtTextNode aHandleResetAttr( *this, nWhich1, nWhich2 );
+
+ bool bRet = SwContentNode::ResetAttr( nWhich1, nWhich2 );
+
+ mbInSetOrResetAttr = bOldIsSetOrResetAttr;
+
+ return bRet;
+}
+
+bool SwTextNode::ResetAttr( const std::vector<sal_uInt16>& rWhichArr )
+{
+ const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
+ mbInSetOrResetAttr = true;
+
+ HandleResetAttrAtTextNode aHandleResetAttr( *this, rWhichArr );
+
+ bool bRet = SwContentNode::ResetAttr( rWhichArr );
+
+ mbInSetOrResetAttr = bOldIsSetOrResetAttr;
+
+ return bRet;
+}
+
+sal_uInt16 SwTextNode::ResetAllAttr()
+{
+ const bool bOldIsSetOrResetAttr( mbInSetOrResetAttr );
+ mbInSetOrResetAttr = true;
+
+ HandleResetAttrAtTextNode aHandleResetAttr( *this );
+
+ const sal_uInt16 nRet = SwContentNode::ResetAllAttr();
+
+ mbInSetOrResetAttr = bOldIsSetOrResetAttr;
+
+ return nRet;
+}
+
+void SwTextNode::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextNode"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), BAD_CAST(OString::number(sal_Int32(GetIndex())).getStr()));
+
+ OUString sText = GetText();
+ for (int i = 0; i < 32; ++i)
+ sText = sText.replace(i, '*');
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_Text"));
+ (void)xmlTextWriterWriteString(pWriter, BAD_CAST(sText.toUtf8().getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+
+ if (GetFormatColl())
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextFormatColl"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("name"), BAD_CAST(GetFormatColl()->GetName().toUtf8().getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+
+ if (HasSwAttrSet())
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwAttrSet"));
+ GetSwAttrSet().dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+
+ if (HasHints())
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwpHints"));
+ const SwpHints& rHints = GetSwpHints();
+ for (size_t i = 0; i < rHints.Count(); ++i)
+ rHints.Get(i)->dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+sal_uInt32 SwTextNode::GetRsid( sal_Int32 nStt, sal_Int32 nEnd ) const
+{
+ SfxItemSetFixed<RES_CHRATR_RSID, RES_CHRATR_RSID> aSet( const_cast<SwAttrPool&>((GetDoc().GetAttrPool())) );
+ if (GetParaAttr(aSet, nStt, nEnd))
+ {
+ const SvxRsidItem* pRsid = aSet.GetItem<SvxRsidItem>(RES_CHRATR_RSID);
+ if( pRsid )
+ return pRsid->GetValue();
+ }
+
+ return 0;
+}
+
+sal_uInt32 SwTextNode::GetParRsid() const
+{
+ return reinterpret_cast<const SvxRsidItem&>(GetAttr( RES_PARATR_RSID )).GetValue();
+}
+
+bool SwTextNode::CompareParRsid( const SwTextNode &rTextNode ) const
+{
+ sal_uInt32 nThisRsid = GetParRsid();
+ sal_uInt32 nRsid = rTextNode.GetParRsid();
+
+ return nThisRsid == nRsid;
+}
+
+bool SwTextNode::CompareRsid( const SwTextNode &rTextNode, sal_Int32 nStt1, sal_Int32 nStt2 ) const
+{
+ sal_uInt32 nThisRsid = GetRsid( nStt1, nStt1 );
+ sal_uInt32 nRsid = rTextNode.GetRsid( nStt2, nStt2 );
+
+ return nThisRsid == nRsid;
+}
+
+// sw::Metadatable
+::sfx2::IXmlIdRegistry& SwTextNode::GetRegistry()
+{
+ return GetDoc().GetXmlIdRegistry();
+}
+
+bool SwTextNode::IsInClipboard() const
+{
+ return GetDoc().IsClipBoard();
+}
+
+bool SwTextNode::IsInUndo() const
+{
+ return GetDoc().GetIDocumentUndoRedo().IsUndoNodes(GetNodes());
+}
+
+bool SwTextNode::IsInContent() const
+{
+ return !GetDoc().IsInHeaderFooter( *this );
+}
+
+void SwTextNode::HandleNonLegacyHint(const SfxHint& rHint)
+{
+ assert(!dynamic_cast<const sw::LegacyModifyHint*>(&rHint));
+ sw::TextNodeNotificationSuppressor(*this);
+ CallSwClientNotify(rHint);
+
+ SwDoc& rDoc = GetDoc();
+ // #125329# - assure that text node is in document nodes array
+ if ( !rDoc.IsInDtor() && &rDoc.GetNodes() == &GetNodes() )
+ {
+ rDoc.GetNodes().UpdateOutlineNode(*this);
+ }
+}
+
+void SwTextNode::UpdateDocPos(const SwTwips nDocPos, const sal_uInt32 nIndex)
+{
+ const sw::DocPosUpdateAtIndex aHint(nDocPos, *this, nIndex);
+ CallSwClientNotify(aHint);
+}
+
+void SwTextNode::TriggerNodeUpdate(const sw::LegacyModifyHint& rHint)
+{
+ const auto pOldValue = rHint.m_pOld;
+ const auto pNewValue = rHint.m_pNew;
+ {
+ sw::TextNodeNotificationSuppressor(*this);
+
+ // Override Modify so that deleting styles works properly (outline
+ // numbering!).
+ // Never call ChgTextCollUpdateNum for Nodes in Undo.
+ if( pOldValue
+ && pNewValue
+ && RES_FMT_CHG == pOldValue->Which()
+ && GetRegisteredIn() == static_cast<const SwFormatChg*>(pNewValue)->pChangedFormat
+ && GetNodes().IsDocNodes() )
+ {
+ assert(dynamic_cast<SwTextFormatColl const*>(static_cast<const SwFormatChg*>(pOldValue)->pChangedFormat));
+ assert(dynamic_cast<SwTextFormatColl const*>(static_cast<const SwFormatChg*>(pNewValue)->pChangedFormat));
+ ChgTextCollUpdateNum(
+ static_cast<const SwTextFormatColl*>(static_cast<const SwFormatChg*>(pOldValue)->pChangedFormat),
+ static_cast<const SwTextFormatColl*>(static_cast<const SwFormatChg*>(pNewValue)->pChangedFormat) );
+ }
+
+ // reset fill information
+ if (maFillAttributes && pNewValue)
+ {
+ const sal_uInt16 nWhich = pNewValue->Which();
+ bool bReset(RES_FMT_CHG == nWhich); // ..on format change (e.g. style changed)
+
+ if(!bReset && RES_ATTRSET_CHG == nWhich) // ..on ItemChange from DrawingLayer FillAttributes
+ {
+ SfxItemIter aIter(*static_cast<const SwAttrSetChg*>(pNewValue)->GetChgSet());
+
+ for(const SfxPoolItem* pItem = aIter.GetCurItem(); pItem && !bReset; pItem = aIter.NextItem())
+ {
+ bReset = !IsInvalidItem(pItem) && pItem->Which() >= XATTR_FILL_FIRST && pItem->Which() <= XATTR_FILL_LAST;
+ }
+ }
+
+ if(bReset)
+ {
+ maFillAttributes.reset();
+ }
+ }
+
+ if ( !mbInSetOrResetAttr )
+ {
+ HandleModifyAtTextNode( *this, pOldValue, pNewValue );
+ }
+
+ SwContentNode::SwClientNotify(*this, rHint);
+
+ SwDoc& rDoc = GetDoc();
+ // #125329# - assure that text node is in document nodes array
+ if ( !rDoc.IsInDtor() && &rDoc.GetNodes() == &GetNodes() )
+ {
+ rDoc.GetNodes().UpdateOutlineNode(*this);
+ }
+ }
+
+ if (pOldValue && (RES_REMOVE_UNO_OBJECT == pOldValue->Which()))
+ { // invalidate cached uno object
+ SetXParagraph(nullptr);
+ }
+}
+
+void SwTextNode::SwClientNotify( const SwModify& rModify, const SfxHint& rHint )
+{
+ if(rHint.GetId() == SfxHintId::SwAutoFormatUsedHint)
+ {
+ static_cast<const sw::AutoFormatUsedHint&>(rHint).CheckNode(this);
+ return;
+ }
+ else if (rHint.GetId() == SfxHintId::SwLegacyModify)
+ {
+ auto pLegacyHint = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ TriggerNodeUpdate(*pLegacyHint);
+ }
+ else if (dynamic_cast<const SwAttrHint*>(&rHint))
+ {
+ if (&rModify == GetRegisteredIn())
+ ChkCondColl();
+ }
+}
+
+uno::Reference< rdf::XMetadatable >
+SwTextNode::MakeUnoObject()
+{
+ const uno::Reference<rdf::XMetadatable> xMeta(
+ SwXParagraph::CreateXParagraph(GetDoc(), this, nullptr));
+ return xMeta;
+}
+
+drawinglayer::attribute::SdrAllFillAttributesHelperPtr SwTextNode::getSdrAllFillAttributesHelper() const
+{
+ // create SdrAllFillAttributesHelper on demand
+ if(!maFillAttributes)
+ {
+ const_cast< SwTextNode* >(this)->maFillAttributes = std::make_shared<drawinglayer::attribute::SdrAllFillAttributesHelper>(GetSwAttrSet());
+ }
+
+ return maFillAttributes;
+}
+
+void SwTextNode::SetXParagraph(rtl::Reference<SwXParagraph> const & xParagraph)
+{
+ m_wXParagraph = xParagraph.get();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/swfntcch.cxx b/sw/source/core/txtnode/swfntcch.cxx
new file mode 100644
index 0000000000..100a0e7a79
--- /dev/null
+++ b/sw/source/core/txtnode/swfntcch.cxx
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <shellio.hxx>
+#include <viewsh.hxx>
+#include <swfntcch.hxx>
+#include <fmtcol.hxx>
+#include <fntcache.hxx>
+#include <swfont.hxx>
+
+// from atrstck.cxx
+extern const sal_uInt8 StackPos[];
+
+// FontCache is created in txtinit.cxx TextInit_ and deleted in TextFinit
+SwFontCache *pSwFontCache = nullptr;
+
+SwFontObj::SwFontObj( const void *pOwn, SwViewShell *pSh ) :
+ SwCacheObj( pOwn ),
+ m_aSwFont( &static_cast<SwTextFormatColl const *>(pOwn)->GetAttrSet(), pSh ? &pSh->getIDocumentSettingAccess() : nullptr )
+{
+ m_aSwFont.AllocFontCacheId( pSh, m_aSwFont.GetActual() );
+ const SwAttrSet& rAttrSet = static_cast<SwTextFormatColl const *>(pOwn)->GetAttrSet();
+ for (sal_uInt16 i = RES_CHRATR_BEGIN; i < RES_CHRATR_END; i++)
+ m_pDefaultArray[ StackPos[ i ] ] = &rAttrSet.Get( i );
+}
+
+SwFontObj::~SwFontObj()
+{
+}
+
+SwFontAccess::SwFontAccess( const void *pOwn, SwViewShell *pSh ) :
+ SwCacheAccess( *pSwFontCache, pOwn,
+ static_cast<const SwTextFormatColl*>(pOwn)->IsInSwFntCache() ),
+ m_pShell( pSh )
+{
+}
+
+SwFontObj *SwFontAccess::Get( )
+{
+ return static_cast<SwFontObj *>( SwCacheAccess::Get( ) );
+}
+
+SwCacheObj *SwFontAccess::NewObj( )
+{
+ const_cast<SwTextFormatColl*>(static_cast<const SwTextFormatColl*>(m_pOwner))->SetInSwFntCache();
+ return new SwFontObj( m_pOwner, m_pShell );
+}
+
+SAL_DLLPUBLIC_EXPORT void FlushFontCache()
+{
+ if (pSwFontCache)
+ pSwFontCache->Flush();
+ if (pFntCache)
+ pFntCache->Flush();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/swfont.cxx b/sw/source/core/txtnode/swfont.cxx
new file mode 100644
index 0000000000..98fec0f153
--- /dev/null
+++ b/sw/source/core/txtnode/swfont.cxx
@@ -0,0 +1,1497 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <hintids.hxx>
+
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <vcl/outdev.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/wrlmitem.hxx>
+#include <editeng/kernitem.hxx>
+#include <editeng/cmapitem.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <editeng/autokernitem.hxx>
+#include <editeng/shdditem.hxx>
+#include <editeng/charreliefitem.hxx>
+#include <editeng/contouritem.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/postitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/emphasismarkitem.hxx>
+#include <editeng/charscaleitem.hxx>
+#include <editeng/charrotateitem.hxx>
+#include <editeng/twolinesitem.hxx>
+#include <editeng/charhiddenitem.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/shaditem.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <charatr.hxx>
+#include <viewsh.hxx>
+#include <swfont.hxx>
+#include <fntcache.hxx>
+#include <txtfrm.hxx>
+#include <scriptinfo.hxx>
+
+#ifdef DBG_UTIL
+// global Variable
+SvStatistics g_SvStat;
+#endif
+
+using namespace ::com::sun::star;
+
+// set background brush, depending on character formatting
+void SwFont::SetBackColor( std::optional<Color> xNewColor )
+{
+ mxBackColor = xNewColor;
+ m_bFontChg = true;
+ m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr;
+}
+
+void SwFont::SetTopBorder( const editeng::SvxBorderLine* pTopBorder )
+{
+ if( pTopBorder )
+ m_aTopBorder = *pTopBorder;
+ else
+ {
+ m_aTopBorder.reset();
+ m_nTopBorderDist = 0;
+ }
+ m_bFontChg = true;
+ m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr;
+}
+
+void SwFont::SetBottomBorder( const editeng::SvxBorderLine* pBottomBorder )
+{
+ if( pBottomBorder )
+ m_aBottomBorder = *pBottomBorder;
+ else
+ {
+ m_aBottomBorder.reset();
+ m_nBottomBorderDist = 0;
+ }
+ m_bFontChg = true;
+ m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr;
+}
+
+void SwFont::SetRightBorder( const editeng::SvxBorderLine* pRightBorder )
+{
+ if( pRightBorder )
+ m_aRightBorder = *pRightBorder;
+ else
+ {
+ m_aRightBorder.reset();
+ m_nRightBorderDist = 0;
+ }
+ m_bFontChg = true;
+ m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr;
+}
+
+void SwFont::SetLeftBorder( const editeng::SvxBorderLine* pLeftBorder )
+{
+ if( pLeftBorder )
+ m_aLeftBorder = *pLeftBorder;
+ else
+ {
+ m_aLeftBorder.reset();
+ m_nLeftBorderDist = 0;
+ }
+ m_bFontChg = true;
+ m_aSub[SwFontScript::Latin].m_nFontCacheId = m_aSub[SwFontScript::CJK].m_nFontCacheId = m_aSub[SwFontScript::CTL].m_nFontCacheId = nullptr;
+}
+
+const std::optional<editeng::SvxBorderLine>&
+SwFont::GetAbsTopBorder(const bool bVertLayout, const bool bVertLayoutLRBT) const
+{
+ switch (GetOrientation(bVertLayout, bVertLayoutLRBT).get())
+ {
+ case 0 :
+ return m_aTopBorder;
+ case 900 :
+ return m_aRightBorder;
+ case 1800 :
+ return m_aBottomBorder;
+ case 2700 :
+ return m_aLeftBorder;
+ default :
+ assert(false);
+ return m_aTopBorder;
+ }
+}
+
+const std::optional<editeng::SvxBorderLine>&
+SwFont::GetAbsBottomBorder(const bool bVertLayout, const bool bVertLayoutLRBT) const
+{
+ switch (GetOrientation(bVertLayout, bVertLayoutLRBT).get())
+ {
+ case 0 :
+ return m_aBottomBorder;
+ case 900 :
+ return m_aLeftBorder;
+ case 1800 :
+ return m_aTopBorder;
+ case 2700 :
+ return m_aRightBorder;
+ default :
+ assert(false);
+ return m_aBottomBorder;
+ }
+}
+
+const std::optional<editeng::SvxBorderLine>&
+SwFont::GetAbsLeftBorder(const bool bVertLayout, const bool bVertLayoutLRBT) const
+{
+ switch (GetOrientation(bVertLayout, bVertLayoutLRBT).get())
+ {
+ case 0 :
+ return m_aLeftBorder;
+ case 900 :
+ return m_aTopBorder;
+ case 1800 :
+ return m_aRightBorder;
+ case 2700 :
+ return m_aBottomBorder;
+ default :
+ assert(false);
+ return m_aLeftBorder;
+ }
+}
+
+const std::optional<editeng::SvxBorderLine>&
+SwFont::GetAbsRightBorder(const bool bVertLayout, const bool bVertLayoutLRBT) const
+{
+ switch (GetOrientation(bVertLayout, bVertLayoutLRBT).get())
+ {
+ case 0 :
+ return m_aRightBorder;
+ case 900 :
+ return m_aBottomBorder;
+ case 1800 :
+ return m_aLeftBorder;
+ case 2700 :
+ return m_aTopBorder;
+ default :
+ assert(false);
+ return m_aRightBorder;
+ }
+}
+
+SvxShadowLocation SwFont::GetAbsShadowLocation(const bool bVertLayout,
+ const bool bVertLayoutLRBT) const
+{
+ SvxShadowLocation aLocation = SvxShadowLocation::NONE;
+ switch (GetOrientation(bVertLayout, bVertLayoutLRBT).get())
+ {
+ case 0:
+ aLocation = m_aShadowLocation;
+ break;
+
+ case 900:
+ switch ( m_aShadowLocation )
+ {
+ case SvxShadowLocation::TopLeft:
+ aLocation = SvxShadowLocation::BottomLeft;
+ break;
+ case SvxShadowLocation::TopRight:
+ aLocation = SvxShadowLocation::TopLeft;
+ break;
+ case SvxShadowLocation::BottomLeft:
+ aLocation = SvxShadowLocation::BottomRight;
+ break;
+ case SvxShadowLocation::BottomRight:
+ aLocation = SvxShadowLocation::TopRight;
+ break;
+ case SvxShadowLocation::NONE:
+ case SvxShadowLocation::End:
+ aLocation = m_aShadowLocation;
+ break;
+ }
+ break;
+
+ case 1800:
+ switch ( m_aShadowLocation )
+ {
+ case SvxShadowLocation::TopLeft:
+ aLocation = SvxShadowLocation::BottomRight;
+ break;
+ case SvxShadowLocation::TopRight:
+ aLocation = SvxShadowLocation::BottomLeft;
+ break;
+ case SvxShadowLocation::BottomLeft:
+ aLocation = SvxShadowLocation::TopRight;
+ break;
+ case SvxShadowLocation::BottomRight:
+ aLocation = SvxShadowLocation::TopLeft;
+ break;
+ case SvxShadowLocation::NONE:
+ case SvxShadowLocation::End:
+ aLocation = m_aShadowLocation;
+ break;
+ }
+ break;
+
+ case 2700:
+ switch ( m_aShadowLocation )
+ {
+ case SvxShadowLocation::TopLeft:
+ aLocation = SvxShadowLocation::TopRight;
+ break;
+ case SvxShadowLocation::TopRight:
+ aLocation = SvxShadowLocation::BottomRight;
+ break;
+ case SvxShadowLocation::BottomLeft:
+ aLocation = SvxShadowLocation::TopLeft;
+ break;
+ case SvxShadowLocation::BottomRight:
+ aLocation = SvxShadowLocation::BottomLeft;
+ break;
+ case SvxShadowLocation::NONE:
+ case SvxShadowLocation::End:
+ aLocation = m_aShadowLocation;
+ break;
+ }
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+ return aLocation;
+}
+
+sal_uInt16 SwFont::CalcShadowSpace(const SvxShadowItemSide nShadow, const bool bVertLayout,
+ const bool bVertLayoutLRBT, const bool bSkipLeft,
+ const bool bSkipRight) const
+{
+ sal_uInt16 nSpace = 0;
+ const Degree10 nOrient = GetOrientation(bVertLayout, bVertLayoutLRBT);
+ const SvxShadowLocation aLoc = GetAbsShadowLocation(bVertLayout, bVertLayoutLRBT);
+ switch( nShadow )
+ {
+ case SvxShadowItemSide::TOP:
+ if(( aLoc == SvxShadowLocation::TopLeft ||
+ aLoc == SvxShadowLocation::TopRight ) &&
+ ( nOrient == 0_deg10 || nOrient == 1800_deg10 ||
+ ( nOrient == 900_deg10 && !bSkipRight ) ||
+ ( nOrient == 2700_deg10 && !bSkipLeft )))
+ {
+ nSpace = m_nShadowWidth;
+ }
+ break;
+
+ case SvxShadowItemSide::BOTTOM:
+ if(( aLoc == SvxShadowLocation::BottomLeft ||
+ aLoc == SvxShadowLocation::BottomRight ) &&
+ ( nOrient == 0_deg10 || nOrient == 1800_deg10 ||
+ ( nOrient == 900_deg10 && !bSkipLeft ) ||
+ ( nOrient == 2700_deg10 && !bSkipRight )))
+ {
+ nSpace = m_nShadowWidth;
+ }
+ break;
+
+ case SvxShadowItemSide::LEFT:
+ if(( aLoc == SvxShadowLocation::TopLeft ||
+ aLoc == SvxShadowLocation::BottomLeft ) &&
+ ( nOrient == 900_deg10 || nOrient == 2700_deg10 ||
+ ( nOrient == 0_deg10 && !bSkipLeft ) ||
+ ( nOrient == 1800_deg10 && !bSkipRight )))
+ {
+ nSpace = m_nShadowWidth;
+ }
+ break;
+
+ case SvxShadowItemSide::RIGHT:
+ if(( aLoc == SvxShadowLocation::TopRight ||
+ aLoc == SvxShadowLocation::BottomRight ) &&
+ ( nOrient == 900_deg10 || nOrient == 2700_deg10 ||
+ ( nOrient == 0_deg10 && !bSkipRight ) ||
+ ( nOrient == 1800_deg10 && !bSkipLeft )))
+ {
+ nSpace = m_nShadowWidth;
+ }
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ return nSpace;
+}
+
+void SwFont::dumpAsXml(xmlTextWriterPtr writer) const
+{
+ (void)xmlTextWriterStartElement(writer, BAD_CAST("SwFont"));
+ (void)xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("ptr"), "%p", this);
+ // do not use Color::AsRGBHexString() as that omits the transparency
+ (void)xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("color"), "%08" SAL_PRIxUINT32, sal_uInt32(GetColor()));
+ (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("height"), BAD_CAST(OString::number(GetSize(GetActual()).Height()).getStr()));
+ (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("width"), BAD_CAST(OString::number(GetSize(GetActual()).Width()).getStr()));
+ {
+ std::stringstream ss;
+ ss << GetWeight();
+ (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("weight"), BAD_CAST(ss.str().c_str()));
+ }
+ (void)xmlTextWriterEndElement(writer);
+}
+
+// maps directions for vertical layout
+static Degree10 MapDirection(Degree10 nDir, const bool bVertFormat, const bool bVertFormatLRBT)
+{
+ if ( bVertFormat )
+ {
+ switch ( nDir.get() )
+ {
+ case 0 :
+ if (bVertFormatLRBT)
+ nDir = 900_deg10;
+ else
+ nDir = 2700_deg10;
+ break;
+ case 900 :
+ nDir = 0_deg10;
+ break;
+ case 2700 :
+ nDir = 1800_deg10;
+ break;
+#if OSL_DEBUG_LEVEL > 0
+ default :
+ OSL_FAIL( "Unsupported direction" );
+ break;
+#endif
+ }
+ }
+ return nDir;
+}
+
+// maps the absolute direction set at the font to its logical counterpart
+// in the rotated environment
+Degree10 UnMapDirection(Degree10 nDir, const bool bVertFormat, const bool bVertFormatLRBT)
+{
+ if (bVertFormatLRBT)
+ {
+ switch (nDir.get())
+ {
+ case 900:
+ nDir = 0_deg10;
+ break;
+ default:
+ SAL_WARN("sw.core", "unsupported direction for VertLRBT");
+ break;
+ }
+ return nDir;
+ }
+
+ if ( bVertFormat )
+ {
+ switch ( nDir.get() )
+ {
+ case 0 :
+ nDir = 900_deg10;
+ break;
+ case 1800 :
+ nDir = 2700_deg10;
+ break;
+ case 2700 :
+ nDir = 0_deg10;
+ break;
+#if OSL_DEBUG_LEVEL > 0
+ default :
+ OSL_FAIL( "Unsupported direction" );
+ break;
+#endif
+ }
+ }
+ return nDir;
+}
+
+Degree10 SwFont::GetOrientation(const bool bVertFormat, const bool bVertFormatLRBT) const
+{
+ return UnMapDirection(m_aSub[m_nActual].GetOrientation(), bVertFormat, bVertFormatLRBT);
+}
+
+void SwFont::SetVertical(Degree10 nDir, const bool bVertFormat, const bool bVertLayoutLRBT)
+{
+ // map direction if frame has vertical layout
+ nDir = MapDirection(nDir, bVertFormat, bVertLayoutLRBT);
+
+ if( nDir != m_aSub[SwFontScript::Latin].GetOrientation() )
+ {
+ m_bFontChg = true;
+ bool bVertical = bVertFormat && !bVertLayoutLRBT;
+ m_aSub[SwFontScript::Latin].SetVertical(nDir, bVertical);
+ m_aSub[SwFontScript::CJK].SetVertical(nDir, bVertical);
+ m_aSub[SwFontScript::CTL].SetVertical(nDir, bVertical);
+ }
+}
+
+/*
+ Escapement:
+ frEsc: Fraction, ratio of Escapements
+ Esc = resulting Escapement
+ A1 = original Ascent (nOrgAscent)
+ A2 = shrunk Ascent (nEscAscent)
+ Ax = resulting Ascent (GetAscent())
+ H1 = original Height (nOrgHeight)
+ H2 = shrunk Height (nEscHeight)
+ Hx = resulting Height (GetHeight())
+ Bx = resulting Baseline for Text (CalcPos())
+ (Attention: Y - A1!)
+
+ Escapement:
+ Esc = H1 * frEsc;
+
+ Superscript:
+ Ax = A2 + Esc;
+ Hx = H2 + Esc;
+ Bx = A1 - Esc;
+
+ Subscript:
+ Ax = A1;
+ Hx = A1 + Esc + (H2 - A2);
+ Bx = A1 + Esc;
+*/
+
+// nEsc is the percentage
+sal_uInt16 SwSubFont::CalcEscAscent( const sal_uInt16 nOldAscent ) const
+{
+ if( DFLT_ESC_AUTO_SUPER != GetEscapement() &&
+ DFLT_ESC_AUTO_SUB != GetEscapement() )
+ {
+ const tools::Long nAscent = nOldAscent +
+ ( static_cast<tools::Long>(m_nOrgHeight) * GetEscapement() ) / 100;
+ if ( nAscent>0 )
+ return std::max<sal_uInt16>( nAscent, m_nOrgAscent );
+ }
+ return m_nOrgAscent;
+}
+
+void SwFont::SetDiffFnt( const SfxItemSet *pAttrSet,
+ const IDocumentSettingAccess *pIDocumentSettingAccess )
+{
+ mxBackColor.reset();
+
+ if( pAttrSet )
+ {
+
+ if( const SvxFontItem* pFont = pAttrSet->GetItemIfSet( RES_CHRATR_FONT ) )
+ {
+ m_aSub[SwFontScript::Latin].SetFamily( pFont->GetFamily() );
+ m_aSub[SwFontScript::Latin].Font::SetFamilyName( pFont->GetFamilyName() );
+ m_aSub[SwFontScript::Latin].Font::SetStyleName( pFont->GetStyleName() );
+ m_aSub[SwFontScript::Latin].Font::SetPitch( pFont->GetPitch() );
+ m_aSub[SwFontScript::Latin].Font::SetCharSet( pFont->GetCharSet() );
+ }
+ if( const SvxFontHeightItem *pHeight = pAttrSet->GetItemIfSet( RES_CHRATR_FONTSIZE ) )
+ {
+ m_aSub[SwFontScript::Latin].SvxFont::SetPropr( 100 );
+ m_aSub[SwFontScript::Latin].m_aSize = m_aSub[SwFontScript::Latin].Font::GetFontSize();
+ Size aTmpSize = m_aSub[SwFontScript::Latin].m_aSize;
+ aTmpSize.setHeight( pHeight->GetHeight() );
+ m_aSub[SwFontScript::Latin].SetSize( aTmpSize );
+ }
+ if( const SvxPostureItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_POSTURE ) )
+ m_aSub[SwFontScript::Latin].Font::SetItalic( pItem->GetPosture() );
+ if( const SvxWeightItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_WEIGHT ) )
+ m_aSub[SwFontScript::Latin].Font::SetWeight( pItem->GetWeight() );
+ if( const SvxLanguageItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_LANGUAGE ) )
+ m_aSub[SwFontScript::Latin].SetLanguage( pItem->GetLanguage() );
+
+ if( const SvxFontItem* pFont = pAttrSet->GetItemIfSet( RES_CHRATR_CJK_FONT ) )
+ {
+ m_aSub[SwFontScript::CJK].SetFamily( pFont->GetFamily() );
+ m_aSub[SwFontScript::CJK].Font::SetFamilyName( pFont->GetFamilyName() );
+ m_aSub[SwFontScript::CJK].Font::SetStyleName( pFont->GetStyleName() );
+ m_aSub[SwFontScript::CJK].Font::SetPitch( pFont->GetPitch() );
+ m_aSub[SwFontScript::CJK].Font::SetCharSet( pFont->GetCharSet() );
+ }
+ if( const SvxFontHeightItem* pHeight = pAttrSet->GetItemIfSet( RES_CHRATR_CJK_FONTSIZE) )
+ {
+ m_aSub[SwFontScript::CJK].SvxFont::SetPropr( 100 );
+ m_aSub[SwFontScript::CJK].m_aSize = m_aSub[SwFontScript::CJK].Font::GetFontSize();
+ Size aTmpSize = m_aSub[SwFontScript::CJK].m_aSize;
+ aTmpSize.setHeight( pHeight->GetHeight() );
+ m_aSub[SwFontScript::CJK].SetSize( aTmpSize );
+ }
+ if( const SvxPostureItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_CJK_POSTURE ) )
+ m_aSub[SwFontScript::CJK].Font::SetItalic( pItem->GetPosture() );
+ if( const SvxWeightItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_CJK_WEIGHT ) )
+ m_aSub[SwFontScript::CJK].Font::SetWeight( pItem->GetWeight() );
+ if( const SvxLanguageItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_CJK_LANGUAGE ) )
+ {
+ LanguageType eNewLang = pItem->GetLanguage();
+ m_aSub[SwFontScript::CJK].SetLanguage( eNewLang );
+ m_aSub[SwFontScript::Latin].SetCJKContextLanguage( eNewLang );
+ m_aSub[SwFontScript::CJK].SetCJKContextLanguage( eNewLang );
+ m_aSub[SwFontScript::CTL].SetCJKContextLanguage( eNewLang );
+ }
+
+ if( const SvxFontItem* pFont = pAttrSet->GetItemIfSet( RES_CHRATR_CTL_FONT ) )
+ {
+ m_aSub[SwFontScript::CTL].SetFamily( pFont->GetFamily() );
+ m_aSub[SwFontScript::CTL].Font::SetFamilyName( pFont->GetFamilyName() );
+ m_aSub[SwFontScript::CTL].Font::SetStyleName( pFont->GetStyleName() );
+ m_aSub[SwFontScript::CTL].Font::SetPitch( pFont->GetPitch() );
+ m_aSub[SwFontScript::CTL].Font::SetCharSet( pFont->GetCharSet() );
+ }
+ if( const SvxFontHeightItem* pHeight = pAttrSet->GetItemIfSet( RES_CHRATR_CTL_FONTSIZE ) )
+ {
+ m_aSub[SwFontScript::CTL].SvxFont::SetPropr( 100 );
+ m_aSub[SwFontScript::CTL].m_aSize = m_aSub[SwFontScript::CTL].Font::GetFontSize();
+ Size aTmpSize = m_aSub[SwFontScript::CTL].m_aSize;
+ aTmpSize.setHeight( pHeight->GetHeight() );
+ m_aSub[SwFontScript::CTL].SetSize( aTmpSize );
+ }
+ if( const SvxPostureItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_CTL_POSTURE ) )
+ m_aSub[SwFontScript::CTL].Font::SetItalic(pItem->GetPosture() );
+ if( const SvxWeightItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_CTL_WEIGHT ) )
+ m_aSub[SwFontScript::CTL].Font::SetWeight( pItem->GetWeight() );
+ if( const SvxLanguageItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_CTL_LANGUAGE ) )
+ m_aSub[SwFontScript::CTL].SetLanguage( pItem->GetLanguage() );
+
+ if( const SvxUnderlineItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_UNDERLINE ) )
+ {
+ SetUnderline( pItem->GetLineStyle() );
+ SetUnderColor( pItem->GetColor() );
+ }
+ if( const SvxOverlineItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_OVERLINE ) )
+ {
+ SetOverline( pItem->GetLineStyle() );
+ SetOverColor( pItem->GetColor() );
+ }
+ if( const SvxCrossedOutItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_CROSSEDOUT ) )
+ SetStrikeout( pItem->GetStrikeout() );
+ if( const SvxColorItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_COLOR ) )
+ SetColor( pItem->GetValue() );
+ if( const SvxEmphasisMarkItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_EMPHASIS_MARK ))
+ SetEmphasisMark( pItem->GetEmphasisMark() );
+
+ SetTransparent( true );
+ SetAlign( ALIGN_BASELINE );
+ if( const SvxContourItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_CONTOUR ) )
+ SetOutline( pItem->GetValue() );
+ if( const SvxShadowedItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_SHADOWED ) )
+ SetShadow( pItem->GetValue() );
+ if( const SvxCharReliefItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_RELIEF ) )
+ SetRelief( pItem->GetValue() );
+ if( const SvxShadowedItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_SHADOWED ))
+ SetPropWidth( pItem->GetValue() ? 50 : 100 );
+ if( const SvxAutoKernItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_AUTOKERN ) )
+ {
+ if( pItem->GetValue() )
+ {
+ SetAutoKern( ( !pIDocumentSettingAccess ||
+ !pIDocumentSettingAccess->get(DocumentSettingId::KERN_ASIAN_PUNCTUATION) ) ?
+ FontKerning::FontSpecific :
+ FontKerning::Asian );
+ }
+ else
+ SetAutoKern( FontKerning::NONE );
+ }
+ if( const SvxWordLineModeItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_WORDLINEMODE ) )
+ SetWordLineMode( pItem->GetValue() );
+
+ if( const SvxEscapementItem* pEsc = pAttrSet->GetItemIfSet( RES_CHRATR_ESCAPEMENT ) )
+ {
+ SetEscapement( pEsc->GetEsc() );
+ if( m_aSub[SwFontScript::Latin].IsEsc() )
+ SetProportion( pEsc->GetProportionalHeight() );
+ }
+ if( const SvxCaseMapItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_CASEMAP ) )
+ SetCaseMap( pItem->GetCaseMap() );
+ if( const SvxKerningItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_KERNING ) )
+ SetFixKerning( pItem->GetValue() );
+ if( const SvxCharRotateItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_ROTATE ) )
+ SetVertical( pItem->GetValue() );
+ if( const SvxBrushItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_BACKGROUND ) )
+ mxBackColor = pItem->GetColor();
+ if( const SvxBrushItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_HIGHLIGHT ) )
+ SetHighlightColor(pItem->GetColor());
+ if( const SvxBoxItem* pBoxItem = pAttrSet->GetItemIfSet( RES_CHRATR_BOX ) )
+ {
+ SetTopBorder(pBoxItem->GetTop());
+ SetBottomBorder(pBoxItem->GetBottom());
+ SetRightBorder(pBoxItem->GetRight());
+ SetLeftBorder(pBoxItem->GetLeft());
+ SetTopBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::TOP));
+ SetBottomBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::BOTTOM));
+ SetRightBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::RIGHT));
+ SetLeftBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::LEFT));
+ }
+ if( const SvxShadowItem* pShadowItem = pAttrSet->GetItemIfSet( RES_CHRATR_SHADOW ) )
+ {
+ SetShadowColor(pShadowItem->GetColor());
+ SetShadowWidth(pShadowItem->GetWidth());
+ SetShadowLocation(pShadowItem->GetLocation());
+ }
+ const SvxTwoLinesItem* pTwoLinesItem = pAttrSet->GetItemIfSet( RES_CHRATR_TWO_LINES );
+ if( pTwoLinesItem && pTwoLinesItem->GetValue() )
+ SetVertical( 0_deg10 );
+ }
+ else
+ {
+ Invalidate();
+ }
+ m_bPaintBlank = false;
+ OSL_ENSURE( m_aSub[SwFontScript::Latin].IsTransparent(), "SwFont: Transparent revolution" );
+}
+
+SwFont::SwFont( const SwFont &rFont )
+ : m_aSub(rFont.m_aSub)
+{
+ m_nActual = rFont.m_nActual;
+ mxBackColor = rFont.mxBackColor;
+ m_aHighlightColor = rFont.m_aHighlightColor;
+ m_aTopBorder = rFont.m_aTopBorder;
+ m_aBottomBorder = rFont.m_aBottomBorder;
+ m_aRightBorder = rFont.m_aRightBorder;
+ m_aLeftBorder = rFont.m_aLeftBorder;
+ m_nTopBorderDist = rFont.m_nTopBorderDist;
+ m_nBottomBorderDist = rFont.m_nBottomBorderDist;
+ m_nRightBorderDist = rFont.m_nRightBorderDist;
+ m_nLeftBorderDist = rFont.m_nLeftBorderDist;
+ m_aShadowColor = rFont.m_aShadowColor;
+ m_nShadowWidth = rFont.m_nShadowWidth;
+ m_aShadowLocation = rFont.m_aShadowLocation;
+ m_aUnderColor = rFont.GetUnderColor();
+ m_aOverColor = rFont.GetOverColor();
+ m_nToxCount = 0;
+ m_nRefCount = 0;
+ m_nMetaCount = 0;
+ m_nContentControlCount = 0;
+ m_nInputFieldCount = 0;
+ m_bFontChg = rFont.m_bFontChg;
+ m_bOrgChg = rFont.m_bOrgChg;
+ m_bPaintBlank = rFont.m_bPaintBlank;
+ m_bGreyWave = rFont.m_bGreyWave;
+}
+
+SwFont::SwFont( const SwAttrSet* pAttrSet,
+ const IDocumentSettingAccess* pIDocumentSettingAccess )
+ : m_aSub()
+{
+ m_nActual = SwFontScript::Latin;
+ m_nToxCount = 0;
+ m_nRefCount = 0;
+ m_nMetaCount = 0;
+ m_nContentControlCount = 0;
+ m_nInputFieldCount = 0;
+ m_bPaintBlank = false;
+ m_bGreyWave = false;
+ m_bOrgChg = true;
+ {
+ const SvxFontItem& rFont = pAttrSet->GetFont();
+ m_aSub[SwFontScript::Latin].SetFamily( rFont.GetFamily() );
+ m_aSub[SwFontScript::Latin].SetFamilyName( rFont.GetFamilyName() );
+ m_aSub[SwFontScript::Latin].SetStyleName( rFont.GetStyleName() );
+ m_aSub[SwFontScript::Latin].SetPitch( rFont.GetPitch() );
+ m_aSub[SwFontScript::Latin].SetCharSet( rFont.GetCharSet() );
+ m_aSub[SwFontScript::Latin].SvxFont::SetPropr( 100 ); // 100% of FontSize
+ Size aTmpSize = m_aSub[SwFontScript::Latin].m_aSize;
+ aTmpSize.setHeight( pAttrSet->GetSize().GetHeight() );
+ m_aSub[SwFontScript::Latin].SetSize( aTmpSize );
+ m_aSub[SwFontScript::Latin].SetItalic( pAttrSet->GetPosture().GetPosture() );
+ m_aSub[SwFontScript::Latin].SetWeight( pAttrSet->GetWeight().GetWeight() );
+ m_aSub[SwFontScript::Latin].SetLanguage( pAttrSet->GetLanguage().GetLanguage() );
+ }
+
+ {
+ const SvxFontItem& rFont = pAttrSet->GetCJKFont();
+ m_aSub[SwFontScript::CJK].SetFamily( rFont.GetFamily() );
+ m_aSub[SwFontScript::CJK].SetFamilyName( rFont.GetFamilyName() );
+ m_aSub[SwFontScript::CJK].SetStyleName( rFont.GetStyleName() );
+ m_aSub[SwFontScript::CJK].SetPitch( rFont.GetPitch() );
+ m_aSub[SwFontScript::CJK].SetCharSet( rFont.GetCharSet() );
+ m_aSub[SwFontScript::CJK].SvxFont::SetPropr( 100 ); // 100% of FontSize
+ Size aTmpSize = m_aSub[SwFontScript::CJK].m_aSize;
+ aTmpSize.setHeight( pAttrSet->GetCJKSize().GetHeight() );
+ m_aSub[SwFontScript::CJK].SetSize( aTmpSize );
+ m_aSub[SwFontScript::CJK].SetItalic( pAttrSet->GetCJKPosture().GetPosture() );
+ m_aSub[SwFontScript::CJK].SetWeight( pAttrSet->GetCJKWeight().GetWeight() );
+ LanguageType eNewLang = pAttrSet->GetCJKLanguage().GetLanguage();
+ m_aSub[SwFontScript::CJK].SetLanguage( eNewLang );
+ m_aSub[SwFontScript::Latin].SetCJKContextLanguage( eNewLang );
+ m_aSub[SwFontScript::CJK].SetCJKContextLanguage( eNewLang );
+ m_aSub[SwFontScript::CTL].SetCJKContextLanguage( eNewLang );
+ }
+
+ {
+ const SvxFontItem& rFont = pAttrSet->GetCTLFont();
+ m_aSub[SwFontScript::CTL].SetFamily( rFont.GetFamily() );
+ m_aSub[SwFontScript::CTL].SetFamilyName( rFont.GetFamilyName() );
+ m_aSub[SwFontScript::CTL].SetStyleName( rFont.GetStyleName() );
+ m_aSub[SwFontScript::CTL].SetPitch( rFont.GetPitch() );
+ m_aSub[SwFontScript::CTL].SetCharSet( rFont.GetCharSet() );
+ m_aSub[SwFontScript::CTL].SvxFont::SetPropr( 100 ); // 100% of FontSize
+ Size aTmpSize = m_aSub[SwFontScript::CTL].m_aSize;
+ aTmpSize.setHeight( pAttrSet->GetCTLSize().GetHeight() );
+ m_aSub[SwFontScript::CTL].SetSize( aTmpSize );
+ m_aSub[SwFontScript::CTL].SetItalic( pAttrSet->GetCTLPosture().GetPosture() );
+ m_aSub[SwFontScript::CTL].SetWeight( pAttrSet->GetCTLWeight().GetWeight() );
+ m_aSub[SwFontScript::CTL].SetLanguage( pAttrSet->GetCTLLanguage().GetLanguage() );
+ }
+ if ( pAttrSet->GetCharHidden().GetValue() )
+ SetUnderline( LINESTYLE_DOTTED );
+ else
+ SetUnderline( pAttrSet->GetUnderline().GetLineStyle() );
+ SetUnderColor( pAttrSet->GetUnderline().GetColor() );
+ SetOverline( pAttrSet->GetOverline().GetLineStyle() );
+ SetOverColor( pAttrSet->GetOverline().GetColor() );
+ SetEmphasisMark( pAttrSet->GetEmphasisMark().GetEmphasisMark() );
+ SetStrikeout( pAttrSet->GetCrossedOut().GetStrikeout() );
+ SetColor( pAttrSet->GetColor().GetValue() );
+ SetTransparent( true );
+ SetAlign( ALIGN_BASELINE );
+ SetOutline( pAttrSet->GetContour().GetValue() );
+ SetShadow( pAttrSet->GetShadowed().GetValue() );
+ SetPropWidth( pAttrSet->GetCharScaleW().GetValue() );
+ SetRelief( pAttrSet->GetCharRelief().GetValue() );
+ if( pAttrSet->GetAutoKern().GetValue() )
+ {
+ SetAutoKern( ( !pIDocumentSettingAccess ||
+ !pIDocumentSettingAccess->get(DocumentSettingId::KERN_ASIAN_PUNCTUATION) ) ?
+ FontKerning::FontSpecific :
+ FontKerning::Asian );
+ }
+ else
+ SetAutoKern( FontKerning::NONE );
+ SetWordLineMode( pAttrSet->GetWordLineMode().GetValue() );
+ const SvxEscapementItem &rEsc = pAttrSet->GetEscapement();
+ SetEscapement( rEsc.GetEsc() );
+ if( m_aSub[SwFontScript::Latin].IsEsc() )
+ SetProportion( rEsc.GetProportionalHeight() );
+ SetCaseMap( pAttrSet->GetCaseMap().GetCaseMap() );
+ SetFixKerning( pAttrSet->GetKerning().GetValue() );
+ if( const SvxBrushItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_BACKGROUND ) )
+ mxBackColor = pItem->GetColor();
+ if( const SvxBrushItem* pItem = pAttrSet->GetItemIfSet( RES_CHRATR_HIGHLIGHT ) )
+ SetHighlightColor(pItem->GetColor());
+ else
+ SetHighlightColor(COL_TRANSPARENT);
+ if( const SvxBoxItem* pBoxItem = pAttrSet->GetItemIfSet( RES_CHRATR_BOX ) )
+ {
+ SetTopBorder(pBoxItem->GetTop());
+ SetBottomBorder(pBoxItem->GetBottom());
+ SetRightBorder(pBoxItem->GetRight());
+ SetLeftBorder(pBoxItem->GetLeft());
+ SetTopBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::TOP));
+ SetBottomBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::BOTTOM));
+ SetRightBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::RIGHT));
+ SetLeftBorderDist(pBoxItem->GetDistance(SvxBoxItemLine::LEFT));
+ }
+ else
+ {
+ SetTopBorder(nullptr);
+ SetBottomBorder(nullptr);
+ SetRightBorder(nullptr);
+ SetLeftBorder(nullptr);
+ SetTopBorderDist(0);
+ SetBottomBorderDist(0);
+ SetRightBorderDist(0);
+ SetLeftBorderDist(0);
+ }
+
+ if( const SvxShadowItem* pShadowItem = pAttrSet->GetItemIfSet( RES_CHRATR_SHADOW ) )
+ {
+ SetShadowColor(pShadowItem->GetColor());
+ SetShadowWidth(pShadowItem->GetWidth());
+ SetShadowLocation(pShadowItem->GetLocation());
+ }
+ else
+ {
+ SetShadowColor(COL_TRANSPARENT);
+ SetShadowWidth(0);
+ SetShadowLocation(SvxShadowLocation::NONE);
+ }
+
+ const SvxTwoLinesItem& rTwoLinesItem = pAttrSet->Get2Lines();
+ if ( ! rTwoLinesItem.GetValue() )
+ SetVertical( pAttrSet->GetCharRotate().GetValue() );
+ else
+ SetVertical( 0_deg10 );
+ if( pIDocumentSettingAccess && pIDocumentSettingAccess->get( DocumentSettingId::SMALL_CAPS_PERCENTAGE_66 ))
+ {
+ m_aSub[ SwFontScript::Latin ].m_bSmallCapsPercentage66 = true;
+ m_aSub[ SwFontScript::CJK ].m_bSmallCapsPercentage66 = true;
+ m_aSub[ SwFontScript::CTL ].m_bSmallCapsPercentage66 = true;
+ }
+}
+
+SwFont::~SwFont()
+{
+}
+
+SwFont& SwFont::operator=( const SwFont &rFont )
+{
+ if (this != &rFont)
+ {
+ m_aSub[SwFontScript::Latin] = rFont.m_aSub[SwFontScript::Latin];
+ m_aSub[SwFontScript::CJK] = rFont.m_aSub[SwFontScript::CJK];
+ m_aSub[SwFontScript::CTL] = rFont.m_aSub[SwFontScript::CTL];
+ m_nActual = rFont.m_nActual;
+ mxBackColor = rFont.mxBackColor;
+ m_aHighlightColor = rFont.m_aHighlightColor;
+ m_aTopBorder = rFont.m_aTopBorder;
+ m_aBottomBorder = rFont.m_aBottomBorder;
+ m_aRightBorder = rFont.m_aRightBorder;
+ m_aLeftBorder = rFont.m_aLeftBorder;
+ m_nTopBorderDist = rFont.m_nTopBorderDist;
+ m_nBottomBorderDist = rFont.m_nBottomBorderDist;
+ m_nRightBorderDist = rFont.m_nRightBorderDist;
+ m_nLeftBorderDist = rFont.m_nLeftBorderDist;
+ m_aShadowColor = rFont.m_aShadowColor;
+ m_nShadowWidth = rFont.m_nShadowWidth;
+ m_aShadowLocation = rFont.m_aShadowLocation;
+ m_aUnderColor = rFont.GetUnderColor();
+ m_aOverColor = rFont.GetOverColor();
+ m_nToxCount = 0;
+ m_nRefCount = 0;
+ m_nMetaCount = 0;
+ m_nContentControlCount = 0;
+ m_nInputFieldCount = 0;
+ m_bFontChg = rFont.m_bFontChg;
+ m_bOrgChg = rFont.m_bOrgChg;
+ m_bPaintBlank = rFont.m_bPaintBlank;
+ m_bGreyWave = rFont.m_bGreyWave;
+ }
+ return *this;
+}
+
+void SwFont::AllocFontCacheId( SwViewShell const *pSh, SwFontScript nWhich )
+{
+ SwFntAccess aFntAccess( m_aSub[nWhich].m_nFontCacheId, m_aSub[nWhich].m_nFontIndex,
+ &m_aSub[nWhich], pSh, true );
+}
+
+bool SwSubFont::IsSymbol( SwViewShell const *pSh )
+{
+ SwFntAccess aFntAccess( m_nFontCacheId, m_nFontIndex, this, pSh, false );
+ return aFntAccess.Get()->IsSymbol();
+}
+
+bool SwSubFont::ChgFnt( SwViewShell const *pSh, OutputDevice& rOut )
+{
+ if ( pLastFont )
+ pLastFont->Unlock();
+ SwFntAccess aFntAccess( m_nFontCacheId, m_nFontIndex, this, pSh, true );
+ SV_STAT( nChangeFont );
+
+ pLastFont = aFntAccess.Get();
+
+ pLastFont->SetDevFont( pSh, rOut );
+
+ pLastFont->Lock();
+ return LINESTYLE_NONE != GetUnderline() ||
+ LINESTYLE_NONE != GetOverline() ||
+ STRIKEOUT_NONE != GetStrikeout();
+}
+
+void SwFont::ChgPhysFnt( SwViewShell const *pSh, OutputDevice& rOut )
+{
+ if( m_bOrgChg && m_aSub[m_nActual].IsEsc() )
+ {
+ const sal_uInt8 nOldProp = m_aSub[m_nActual].GetPropr();
+ SetProportion( 100 );
+ ChgFnt( pSh, rOut );
+ SwFntAccess aFntAccess( m_aSub[m_nActual].m_nFontCacheId, m_aSub[m_nActual].m_nFontIndex,
+ &m_aSub[m_nActual], pSh );
+ m_aSub[m_nActual].m_nOrgHeight = aFntAccess.Get()->GetFontHeight( pSh, rOut );
+ m_aSub[m_nActual].m_nOrgAscent = aFntAccess.Get()->GetFontAscent( pSh, rOut );
+ SetProportion( nOldProp );
+ m_bOrgChg = false;
+ }
+
+ if( m_bFontChg )
+ {
+ ChgFnt( pSh, rOut );
+ m_bFontChg = m_bOrgChg;
+ }
+ if( rOut.GetTextLineColor() != m_aUnderColor )
+ rOut.SetTextLineColor( m_aUnderColor );
+ if( rOut.GetOverlineColor() != m_aOverColor )
+ rOut.SetOverlineColor( m_aOverColor );
+}
+
+// Height = MaxAscent + MaxDescent
+// MaxAscent = Max (T1_ascent, T2_ascent + (Esc * T1_height) );
+// MaxDescent = Max (T1_height-T1_ascent,
+// T2_height-T2_ascent - (Esc * T1_height)
+sal_uInt16 SwSubFont::CalcEscHeight( const sal_uInt16 nOldHeight,
+ const sal_uInt16 nOldAscent ) const
+{
+ if( DFLT_ESC_AUTO_SUPER != GetEscapement() &&
+ DFLT_ESC_AUTO_SUB != GetEscapement() )
+ {
+ tools::Long nDescent = nOldHeight - nOldAscent -
+ ( static_cast<tools::Long>(m_nOrgHeight) * GetEscapement() ) / 100;
+ const sal_uInt16 nDesc = nDescent>0
+ ? std::max<sal_uInt16>( nDescent, m_nOrgHeight - m_nOrgAscent)
+ : m_nOrgHeight - m_nOrgAscent;
+ return ( nDesc + CalcEscAscent( nOldAscent ) );
+ }
+ return m_nOrgHeight;
+}
+
+short SwSubFont::CheckKerning_( )
+{
+ short nKernx = - short( Font::GetFontSize().Height() / 6 );
+
+ if ( nKernx < GetFixKerning() )
+ return GetFixKerning();
+ return nKernx;
+}
+
+sal_uInt16 SwSubFont::GetAscent( SwViewShell const *pSh, const OutputDevice& rOut )
+{
+ SwFntAccess aFntAccess( m_nFontCacheId, m_nFontIndex, this, pSh );
+ const sal_uInt16 nAscent = aFntAccess.Get()->GetFontAscent( pSh, rOut );
+ return GetEscapement() ? CalcEscAscent( nAscent ) : nAscent;
+}
+
+sal_uInt16 SwSubFont::GetHeight( SwViewShell const *pSh, const OutputDevice& rOut )
+{
+ SV_STAT( nGetTextSize );
+ SwFntAccess aFntAccess( m_nFontCacheId, m_nFontIndex, this, pSh );
+ const sal_uInt16 nHeight = aFntAccess.Get()->GetFontHeight( pSh, rOut );
+ if ( GetEscapement() )
+ {
+ const sal_uInt16 nAscent = aFntAccess.Get()->GetFontAscent( pSh, rOut );
+ return CalcEscHeight( nHeight, nAscent ); // + nLeading;
+ }
+ return nHeight; // + nLeading;
+}
+
+sal_uInt16 SwSubFont::GetHangingBaseline( SwViewShell const *pSh, const OutputDevice& rOut )
+{
+ SwFntAccess aFntAccess( m_nFontCacheId, m_nFontIndex, this, pSh );
+ return aFntAccess.Get()->GetFontHangingBaseline( pSh, rOut );
+}
+
+Size SwSubFont::GetTextSize_( SwDrawTextInfo& rInf )
+{
+ // Robust: the font is supposed to be set already, but better safe than
+ // sorry...
+ if ( !pLastFont || pLastFont->GetOwner() != m_nFontCacheId ||
+ !IsSameInstance( rInf.GetpOut()->GetFont() ) )
+ ChgFnt( rInf.GetShell(), rInf.GetOut() );
+
+ SwDigitModeModifier aDigitModeModifier( rInf.GetOut(), rInf.GetFont()->GetLanguage() );
+
+ Size aTextSize;
+ TextFrameIndex const nLn = rInf.GetLen() == TextFrameIndex(COMPLETE_STRING)
+ ? TextFrameIndex(rInf.GetText().getLength())
+ : rInf.GetLen();
+ rInf.SetLen( nLn );
+
+ if( IsCapital() && nLn )
+ {
+ if (rInf.GetMeasureLen() != TextFrameIndex(COMPLETE_STRING))
+ {
+ rInf.SetLen(rInf.GetMeasureLen());
+ rInf.SetMeasureLen(TextFrameIndex(COMPLETE_STRING));
+ }
+ aTextSize = GetCapitalSize( rInf );
+ }
+ else
+ {
+ SV_STAT( nGetTextSize );
+ tools::Long nOldKern = rInf.GetKern();
+ const OUString oldText = rInf.GetText();
+ rInf.SetKern( CheckKerning() );
+ if ( !IsCaseMap() )
+ aTextSize = pLastFont->GetTextSize( rInf );
+ else
+ {
+ const OUString aTmp = CalcCaseMap( rInf.GetText() );
+ const OUString oldStr = rInf.GetText();
+ bool bCaseMapLengthDiffers(aTmp.getLength() != oldStr.getLength());
+
+ if(bCaseMapLengthDiffers && rInf.GetLen())
+ {
+ // If the length of the original string and the CaseMapped one
+ // are different, it is necessary to handle the given text part as
+ // a single snippet since its size may differ, too.
+ TextFrameIndex const nOldIdx(rInf.GetIdx());
+ TextFrameIndex const nOldLen(rInf.GetLen());
+ TextFrameIndex const nOldMeasureLen(rInf.GetMeasureLen());
+ const OUString aSnippet(oldStr.copy(sal_Int32(nOldIdx), sal_Int32(nOldLen)));
+ const OUString aNewText(CalcCaseMap(aSnippet));
+
+ rInf.SetText( aNewText );
+ rInf.SetIdx( TextFrameIndex(0) );
+ rInf.SetLen( TextFrameIndex(aNewText.getLength()) );
+ if (nOldMeasureLen != TextFrameIndex(COMPLETE_STRING))
+ {
+ const OUString aMeasureSnippet(oldStr.copy(sal_Int32(nOldIdx), sal_Int32(nOldMeasureLen)));
+ const OUString aNewMeasureText(CalcCaseMap(aMeasureSnippet));
+ rInf.SetMeasureLen(TextFrameIndex(aNewMeasureText.getLength()));
+ }
+
+ aTextSize = pLastFont->GetTextSize( rInf );
+
+ rInf.SetIdx( nOldIdx );
+ rInf.SetLen( nOldLen );
+ rInf.SetMeasureLen(nOldMeasureLen);
+ }
+ else
+ {
+ rInf.SetText( aTmp );
+ aTextSize = pLastFont->GetTextSize( rInf );
+ }
+
+ rInf.SetText(oldStr);
+ }
+ rInf.SetKern( nOldKern );
+ rInf.SetText(oldText);
+ // A word that's longer than one line, with escapement at the line
+ // break, must report its effective height.
+ if( GetEscapement() )
+ {
+ const sal_uInt16 nAscent = pLastFont->GetFontAscent( rInf.GetShell(),
+ rInf.GetOut() );
+ aTextSize.setHeight(
+ static_cast<tools::Long>(CalcEscHeight( o3tl::narrowing<sal_uInt16>(aTextSize.Height()), nAscent)) );
+ }
+ }
+
+ if (TextFrameIndex(1) == rInf.GetLen()
+ && CH_TXT_ATR_FIELDSTART == rInf.GetText()[sal_Int32(rInf.GetIdx())])
+ {
+ SAL_WARN("sw", "this is meant to be dead code");
+ TextFrameIndex const nOldIdx(rInf.GetIdx());
+ TextFrameIndex const nOldLen(rInf.GetLen());
+ OUString aOldText(rInf.GetText());
+ const OUString aNewText(CH_TXT_ATR_SUBST_FIELDSTART);
+ rInf.SetTextIdxLen(aNewText, TextFrameIndex(0), TextFrameIndex(aNewText.getLength()));
+ aTextSize = pLastFont->GetTextSize( rInf );
+ rInf.SetTextIdxLen(aOldText, nOldIdx, nOldLen);
+ }
+ else if (TextFrameIndex(1) == rInf.GetLen()
+ && CH_TXT_ATR_FIELDEND == rInf.GetText()[sal_Int32(rInf.GetIdx())])
+ {
+ SAL_WARN("sw", "this is meant to be dead code");
+ TextFrameIndex const nOldIdx(rInf.GetIdx());
+ TextFrameIndex const nOldLen(rInf.GetLen());
+ OUString aOldText(rInf.GetText());
+ const OUString aNewText(CH_TXT_ATR_SUBST_FIELDEND);
+ rInf.SetTextIdxLen(aNewText, TextFrameIndex(0), TextFrameIndex(aNewText.getLength()));
+ aTextSize = pLastFont->GetTextSize( rInf );
+ rInf.SetTextIdxLen(aOldText, nOldIdx, nOldLen);
+ }
+
+ return aTextSize;
+}
+
+void SwSubFont::DrawText_( SwDrawTextInfo &rInf, const bool bGrey )
+{
+ rInf.SetGreyWave( bGrey );
+ TextFrameIndex const nLn(rInf.GetText().getLength());
+ if( !rInf.GetLen() || !nLn )
+ return;
+ if (TextFrameIndex(COMPLETE_STRING) == rInf.GetLen())
+ rInf.SetLen( nLn );
+
+ FontLineStyle nOldUnder = LINESTYLE_NONE;
+ SwUnderlineFont* pUnderFnt = nullptr;
+
+ if( rInf.GetUnderFnt() )
+ {
+ nOldUnder = GetUnderline();
+ SetUnderline( LINESTYLE_NONE );
+ pUnderFnt = rInf.GetUnderFnt();
+ }
+
+ if( !pLastFont || pLastFont->GetOwner() != m_nFontCacheId )
+ ChgFnt( rInf.GetShell(), rInf.GetOut() );
+
+ SwDigitModeModifier aDigitModeModifier( rInf.GetOut(), rInf.GetFont()->GetLanguage() );
+
+ const Point aOldPos(rInf.GetPos());
+ Point aPos( rInf.GetPos() );
+
+ if( GetEscapement() )
+ CalcEsc( rInf, aPos );
+
+ rInf.SetPos( aPos );
+ rInf.SetKern( CheckKerning() + rInf.GetCharacterSpacing() / SPACING_PRECISION_FACTOR );
+
+ if( IsCapital() )
+ DrawCapital( rInf );
+ else
+ {
+ SV_STAT( nDrawText );
+ if ( !IsCaseMap() )
+ pLastFont->DrawText( rInf );
+ else
+ {
+ const OUString oldStr = rInf.GetText();
+ const OUString aString( CalcCaseMap(oldStr) );
+ bool bCaseMapLengthDiffers(aString.getLength() != oldStr.getLength());
+
+ if(bCaseMapLengthDiffers && rInf.GetLen())
+ {
+ // If the length of the original string and the CaseMapped one
+ // are different, it is necessary to handle the given text part as
+ // a single snippet since its size may differ, too.
+ TextFrameIndex const nOldIdx(rInf.GetIdx());
+ TextFrameIndex const nOldLen(rInf.GetLen());
+ const OUString aSnippet(oldStr.copy(sal_Int32(nOldIdx), sal_Int32(nOldLen)));
+ const OUString aNewText = CalcCaseMap(aSnippet);
+
+ rInf.SetText( aNewText );
+ rInf.SetIdx( TextFrameIndex(0) );
+ rInf.SetLen( TextFrameIndex(aNewText.getLength()) );
+
+ pLastFont->DrawText( rInf );
+
+ rInf.SetIdx( nOldIdx );
+ rInf.SetLen( nOldLen );
+ }
+ else
+ {
+ rInf.SetText( aString );
+ pLastFont->DrawText( rInf );
+ }
+
+ rInf.SetText(oldStr);
+ }
+ }
+
+ if( pUnderFnt && nOldUnder != LINESTYLE_NONE )
+ {
+ Size aFontSize = GetTextSize_( rInf );
+ const OUString oldStr = rInf.GetText();
+
+ TextFrameIndex const nOldIdx = rInf.GetIdx();
+ TextFrameIndex const nOldLen = rInf.GetLen();
+ tools::Long nSpace = 0;
+ if( rInf.GetSpace() )
+ {
+ TextFrameIndex nTmpEnd = nOldIdx + nOldLen;
+ if (nTmpEnd > TextFrameIndex(oldStr.getLength()))
+ nTmpEnd = TextFrameIndex(oldStr.getLength());
+
+ const SwScriptInfo* pSI = rInf.GetScriptInfo();
+
+ const bool bAsianFont =
+ ( rInf.GetFont() && SwFontScript::CJK == rInf.GetFont()->GetActual() );
+ for (TextFrameIndex nTmp = nOldIdx; nTmp < nTmpEnd; ++nTmp)
+ {
+ if (CH_BLANK == oldStr[sal_Int32(nTmp)] || bAsianFont ||
+ (nTmp + TextFrameIndex(1) < TextFrameIndex(oldStr.getLength())
+ && pSI
+ && i18n::ScriptType::ASIAN == pSI->ScriptType(nTmp + TextFrameIndex(1))))
+ {
+ ++nSpace;
+ }
+ }
+
+ // if next portion if a hole portion we do not consider any
+ // extra space added because the last character was ASIAN
+ if ( nSpace && rInf.IsSpaceStop() && bAsianFont )
+ --nSpace;
+
+ nSpace *= rInf.GetSpace() / SPACING_PRECISION_FACTOR;
+ }
+
+ rInf.SetWidth( sal_uInt16(aFontSize.Width() + nSpace) );
+ rInf.SetTextIdxLen( " ", TextFrameIndex(0), TextFrameIndex(2) );
+ SetUnderline( nOldUnder );
+ rInf.SetUnderFnt( nullptr );
+
+ // set position for underline font
+ rInf.SetPos( pUnderFnt->GetPos() );
+
+ pUnderFnt->GetFont().DrawStretchText_( rInf );
+
+ rInf.SetUnderFnt( pUnderFnt );
+ rInf.SetTextIdxLen(oldStr, nOldIdx, nOldLen);
+ }
+
+ rInf.SetPos(aOldPos);
+}
+
+void SwSubFont::DrawStretchText_( SwDrawTextInfo &rInf )
+{
+ if( !rInf.GetLen() || !rInf.GetText().getLength() )
+ return;
+
+ FontLineStyle nOldUnder = LINESTYLE_NONE;
+ SwUnderlineFont* pUnderFnt = nullptr;
+
+ if( rInf.GetUnderFnt() )
+ {
+ nOldUnder = GetUnderline();
+ SetUnderline( LINESTYLE_NONE );
+ pUnderFnt = rInf.GetUnderFnt();
+ }
+
+ if ( !pLastFont || pLastFont->GetOwner() != m_nFontCacheId )
+ ChgFnt( rInf.GetShell(), rInf.GetOut() );
+
+ SwDigitModeModifier aDigitModeModifier( rInf.GetOut(), rInf.GetFont()->GetLanguage() );
+
+ rInf.ApplyAutoColor();
+
+ const Point aOldPos(rInf.GetPos());
+ Point aPos( rInf.GetPos() );
+
+ if( GetEscapement() )
+ CalcEsc( rInf, aPos );
+
+ rInf.SetKern( CheckKerning() + rInf.GetCharacterSpacing() / SPACING_PRECISION_FACTOR );
+ rInf.SetPos( aPos );
+
+ if( IsCapital() )
+ DrawStretchCapital( rInf );
+ else
+ {
+ SV_STAT( nDrawStretchText );
+
+ if ( rInf.GetFrame() )
+ {
+ if ( rInf.GetFrame()->IsRightToLeft() )
+ rInf.GetFrame()->SwitchLTRtoRTL( aPos );
+
+ if ( rInf.GetFrame()->IsVertical() )
+ rInf.GetFrame()->SwitchHorizontalToVertical( aPos );
+
+ rInf.SetPos( aPos );
+ }
+
+ if ( !IsCaseMap() )
+ rInf.GetOut().DrawStretchText( aPos, rInf.GetWidth(),
+ rInf.GetText(), sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
+ else
+ rInf.GetOut().DrawStretchText( aPos, rInf.GetWidth(),
+ CalcCaseMap(rInf.GetText()),
+ sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
+ }
+
+ if( pUnderFnt && nOldUnder != LINESTYLE_NONE )
+ {
+ const OUString oldStr = rInf.GetText();
+ TextFrameIndex const nOldIdx = rInf.GetIdx();
+ TextFrameIndex const nOldLen = rInf.GetLen();
+ rInf.SetTextIdxLen( " ", TextFrameIndex(0), TextFrameIndex(2) );
+ SetUnderline( nOldUnder );
+ rInf.SetUnderFnt( nullptr );
+
+ // set position for underline font
+ rInf.SetPos( pUnderFnt->GetPos() );
+
+ pUnderFnt->GetFont().DrawStretchText_( rInf );
+
+ rInf.SetUnderFnt( pUnderFnt );
+ rInf.SetTextIdxLen(oldStr, nOldIdx, nOldLen);
+ }
+
+ rInf.SetPos(aOldPos);
+}
+
+TextFrameIndex SwSubFont::GetModelPositionForViewPoint_( SwDrawTextInfo& rInf )
+{
+ if ( !pLastFont || pLastFont->GetOwner() != m_nFontCacheId )
+ ChgFnt( rInf.GetShell(), rInf.GetOut() );
+
+ SwDigitModeModifier aDigitModeModifier( rInf.GetOut(), rInf.GetFont()->GetLanguage() );
+
+ TextFrameIndex const nLn = rInf.GetLen() == TextFrameIndex(COMPLETE_STRING)
+ ? TextFrameIndex(rInf.GetText().getLength())
+ : rInf.GetLen();
+ rInf.SetLen( nLn );
+ TextFrameIndex nCursor(0);
+ if( IsCapital() && nLn )
+ nCursor = GetCapitalCursorOfst( rInf );
+ else
+ {
+ const OUString oldText = rInf.GetText();
+ tools::Long nOldKern = rInf.GetKern();
+ rInf.SetKern( CheckKerning() );
+ SV_STAT( nGetTextSize );
+ if ( !IsCaseMap() )
+ nCursor = pLastFont->GetModelPositionForViewPoint( rInf );
+ else
+ {
+ rInf.SetText( CalcCaseMap( rInf.GetText() ) );
+ nCursor = pLastFont->GetModelPositionForViewPoint( rInf );
+ }
+ rInf.SetKern( nOldKern );
+ rInf.SetText(oldText);
+ }
+ return nCursor;
+}
+
+void SwSubFont::CalcEsc( SwDrawTextInfo const & rInf, Point& rPos )
+{
+ tools::Long nOfst;
+
+ bool bVert = false;
+ bool bVertLRBT = false;
+ if (rInf.GetFrame())
+ {
+ bVert = rInf.GetFrame()->IsVertical();
+ bVertLRBT = rInf.GetFrame()->IsVertLRBT();
+ }
+ const Degree10 nDir = UnMapDirection(GetOrientation(), bVert, bVertLRBT);
+
+ switch ( GetEscapement() )
+ {
+ case DFLT_ESC_AUTO_SUB :
+ nOfst = m_nOrgHeight - m_nOrgAscent -
+ pLastFont->GetFontHeight( rInf.GetShell(), rInf.GetOut() ) +
+ pLastFont->GetFontAscent( rInf.GetShell(), rInf.GetOut() );
+
+ switch ( nDir.get() )
+ {
+ case 0 :
+ rPos.AdjustY(nOfst );
+ break;
+ case 900 :
+ rPos.AdjustX(nOfst );
+ break;
+ case 2700 :
+ rPos.AdjustX( -nOfst );
+ break;
+ }
+
+ break;
+ case DFLT_ESC_AUTO_SUPER :
+ nOfst = pLastFont->GetFontAscent( rInf.GetShell(), rInf.GetOut() ) -
+ m_nOrgAscent;
+
+ switch ( nDir.get() )
+ {
+ case 0 :
+ rPos.AdjustY(nOfst );
+ break;
+ case 900 :
+ rPos.AdjustX(nOfst );
+ break;
+ case 2700 :
+ rPos.AdjustX( -nOfst );
+ break;
+ }
+
+ break;
+ default :
+ nOfst = (static_cast<tools::Long>(m_nOrgHeight) * GetEscapement()) / 100;
+
+ switch ( nDir.get() )
+ {
+ case 0 :
+ rPos.AdjustY( -nOfst );
+ break;
+ case 900 :
+ rPos.AdjustX( -nOfst );
+ break;
+ case 2700 :
+ rPos.AdjustX(nOfst );
+ break;
+ }
+ }
+}
+
+// used during painting of small capitals
+void SwDrawTextInfo::Shift( Degree10 nDir )
+{
+#ifdef DBG_UTIL
+ OSL_ENSURE( m_bPos, "DrawTextInfo: Undefined Position" );
+ OSL_ENSURE( m_bSize, "DrawTextInfo: Undefined Width" );
+#endif
+
+ const bool bBidiPor = ( GetFrame() && GetFrame()->IsRightToLeft() ) !=
+ ( vcl::text::ComplexTextLayoutFlags::Default != ( vcl::text::ComplexTextLayoutFlags::BiDiRtl & GetpOut()->GetLayoutMode() ) );
+
+ bool bVert = false;
+ bool bVertLRBT = false;
+ if (GetFrame())
+ {
+ bVert = GetFrame()->IsVertical();
+ bVertLRBT = GetFrame()->IsVertLRBT();
+ }
+ nDir = bBidiPor ? 1800_deg10 : UnMapDirection(nDir, bVert, bVertLRBT);
+
+ switch ( nDir.get() )
+ {
+ case 0 :
+ m_aPos.AdjustX(GetSize().Width() );
+ break;
+ case 900 :
+ OSL_ENSURE( m_aPos.Y() >= GetSize().Width(), "Going underground" );
+ m_aPos.AdjustY( -(GetSize().Width()) );
+ break;
+ case 1800 :
+ m_aPos.AdjustX( -(GetSize().Width()) );
+ break;
+ case 2700 :
+ m_aPos.AdjustY(GetSize().Width() );
+ break;
+ }
+}
+
+/**
+ * @note Used for the "continuous underline" feature.
+ **/
+SwUnderlineFont::SwUnderlineFont(SwFont& rFnt, TextFrameIndex const nEnd, const Point& rPoint)
+ : m_aPos( rPoint ), m_nEnd( nEnd ), m_pFont( &rFnt )
+{
+};
+
+SwUnderlineFont::~SwUnderlineFont()
+{
+}
+
+/// Helper for filters to find true lineheight of a font
+tools::Long AttrSetToLineHeight( const IDocumentSettingAccess& rIDocumentSettingAccess,
+ const SwAttrSet &rSet,
+ const vcl::RenderContext &rOut, sal_Int16 nScript)
+{
+ SwFont aFont(&rSet, &rIDocumentSettingAccess);
+ SwFontScript nActual;
+ switch (nScript)
+ {
+ default:
+ case i18n::ScriptType::LATIN:
+ nActual = SwFontScript::Latin;
+ break;
+ case i18n::ScriptType::ASIAN:
+ nActual = SwFontScript::CJK;
+ break;
+ case i18n::ScriptType::COMPLEX:
+ nActual = SwFontScript::CTL;
+ break;
+ }
+ aFont.SetActual(nActual);
+
+ vcl::RenderContext &rMutableOut = const_cast<vcl::RenderContext &>(rOut);
+ const vcl::Font aOldFont(rMutableOut.GetFont());
+
+ rMutableOut.SetFont(aFont.GetActualFont());
+ tools::Long nHeight = rMutableOut.GetTextHeight();
+
+ rMutableOut.SetFont(aOldFont);
+ return nHeight;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/thints.cxx b/sw/source/core/txtnode/thints.cxx
new file mode 100644
index 0000000000..0556facafe
--- /dev/null
+++ b/sw/source/core/txtnode/thints.cxx
@@ -0,0 +1,3588 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <DocumentContentOperationsManager.hxx>
+#include <hintids.hxx>
+#include <editeng/rsiditem.hxx>
+#include <osl/diagnose.h>
+#include <svl/whiter.hxx>
+#include <svl/itemiter.hxx>
+#include <editeng/charhiddenitem.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <txtinet.hxx>
+#include <txtflcnt.hxx>
+#include <fmtfld.hxx>
+#include <fmtrfmrk.hxx>
+#include <fmtanchr.hxx>
+#include <fmtinfmt.hxx>
+#include <txtatr.hxx>
+#include <fchrfmt.hxx>
+#include <fmtautofmt.hxx>
+#include <fmtflcnt.hxx>
+#include <fmtftn.hxx>
+#include <txttxmrk.hxx>
+#include <txtrfmrk.hxx>
+#include <txtftn.hxx>
+#include <textlinebreak.hxx>
+#include <txtfld.hxx>
+#include <txtannotationfld.hxx>
+#include <unotools/fltrcfg.hxx>
+#include <charfmt.hxx>
+#include <frmfmt.hxx>
+#include <ftnidx.hxx>
+#include <fmtruby.hxx>
+#include <fmtmeta.hxx>
+#include <formatcontentcontrol.hxx>
+#include <formatflysplit.hxx>
+#include <textcontentcontrol.hxx>
+#include <breakit.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentFieldsAccess.hxx>
+#include <IDocumentLayoutAccess.hxx>
+#include <IDocumentStylePoolAccess.hxx>
+#include <fldbas.hxx>
+#include <pam.hxx>
+#include <ndtxt.hxx>
+#include <txtfrm.hxx>
+#include <rootfrm.hxx>
+#include <rolbck.hxx>
+#include <ddefld.hxx>
+#include <docufld.hxx>
+#include <expfld.hxx>
+#include <usrfld.hxx>
+#include <poolfmt.hxx>
+#include <istyleaccess.hxx>
+#include <docsh.hxx>
+#include <algorithm>
+#include <map>
+#include <memory>
+
+#include <rdfhelper.hxx>
+#include <hints.hxx>
+
+#ifdef DBG_UTIL
+#define CHECK Check(true);
+#define CHECK_NOTMERGED Check(false);
+#else
+#define CHECK_NOTMERGED
+#endif
+
+using namespace ::com::sun::star::i18n;
+
+SwpHints::SwpHints(const SwTextNode& rParent)
+ : m_rParent(rParent)
+ , m_pHistory(nullptr)
+ , m_bInSplitNode(false)
+ , m_bCalcHiddenParaField(false)
+ , m_bHiddenByParaField(false)
+ , m_bFootnote(false)
+ , m_bDDEFields(false)
+ , m_bStartMapNeedsSorting(false)
+ , m_bEndMapNeedsSorting(false)
+ , m_bWhichMapNeedsSorting(false)
+{
+}
+
+static void TextAttrDelete( SwDoc & rDoc, SwTextAttr * const pAttr )
+{
+ if (RES_TXTATR_META == pAttr->Which() ||
+ RES_TXTATR_METAFIELD == pAttr->Which())
+ {
+ static_txtattr_cast<SwTextMeta *>(pAttr)->ChgTextNode(nullptr); // prevents ASSERT
+ }
+ else if (pAttr->Which() == RES_TXTATR_CONTENTCONTROL)
+ {
+ static_txtattr_cast<SwTextContentControl*>(pAttr)->ChgTextNode(nullptr);
+ }
+ SwTextAttr::Destroy( pAttr, rDoc.GetAttrPool() );
+}
+
+static bool TextAttrContains(const sal_Int32 nPos, const SwTextAttrEnd * const pAttr)
+{
+ return (pAttr->GetStart() < nPos) && (nPos < *pAttr->End());
+}
+
+// a: |-----|
+// b:
+// |---| => valid: b before a
+// |-----| => valid: start == end; b before a
+// |---------| => invalid: overlap (1)
+// |-----------| => valid: same end; b around a
+// |-----------------| => valid: b around a
+// |---| => valid; same start; b within a
+// |-----| => valid; same start and end; b around or within a?
+// |-----------| => valid: same start: b around a
+// |-| => valid: b within a
+// |---| => valid: same end; b within a
+// |---------| => invalid: overlap (2)
+// |-----| => valid: end == start; b after a
+// |---| => valid: b after a
+// ===> 2 invalid overlap cases
+static
+bool isOverlap(const sal_Int32 nStart1, const sal_Int32 nEnd1,
+ const sal_Int32 nStart2, const sal_Int32 nEnd2)
+{
+ return
+ ((nStart1 > nStart2) && (nStart1 < nEnd2) && (nEnd1 > nEnd2)) // (1)
+ || ((nStart1 < nStart2) && (nStart2 < nEnd1) && (nEnd1 < nEnd2)); // (2)
+}
+
+/// #i106930#: now asymmetric: empty hint1 is _not_ nested, but empty hint2 is
+static
+bool isNestedAny(const sal_Int32 nStart1, const sal_Int32 nEnd1,
+ const sal_Int32 nStart2, const sal_Int32 nEnd2)
+{
+ return ((nStart1 == nStart2) || (nEnd1 == nEnd2))
+ // same start/end: nested except if hint1 empty and hint2 not empty
+ ? (nStart1 != nEnd1) || (nStart2 == nEnd2)
+ : ((nStart1 < nStart2) ? (nEnd1 >= nEnd2) : (nEnd1 <= nEnd2));
+}
+
+static
+bool isSelfNestable(const sal_uInt16 nWhich)
+{
+ if ((RES_TXTATR_INETFMT == nWhich) ||
+ (RES_TXTATR_CJK_RUBY == nWhich) ||
+ (RES_TXTATR_INPUTFIELD == nWhich))
+ return false;
+ assert((RES_TXTATR_META == nWhich) ||
+ (RES_TXTATR_METAFIELD == nWhich) ||
+ (RES_TXTATR_CONTENTCONTROL == nWhich));
+ return true;
+}
+
+static
+bool isSplittable(const sal_uInt16 nWhich)
+{
+ if ((RES_TXTATR_INETFMT == nWhich) ||
+ (RES_TXTATR_CJK_RUBY == nWhich))
+ return true;
+ assert((RES_TXTATR_META == nWhich) ||
+ (RES_TXTATR_METAFIELD == nWhich) ||
+ (RES_TXTATR_INPUTFIELD == nWhich) ||
+ (RES_TXTATR_CONTENTCONTROL == nWhich));
+ return false;
+}
+
+namespace {
+
+enum Split_t { FAIL, SPLIT_NEW, SPLIT_OTHER };
+
+}
+
+/**
+ Calculate splitting policy for overlapping hints, based on what kind of
+ hint is inserted, and what kind of existing hint overlaps.
+ */
+static Split_t
+splitPolicy(const sal_uInt16 nWhichNew, const sal_uInt16 nWhichOther)
+{
+ if (!isSplittable(nWhichOther))
+ {
+ if (!isSplittable(nWhichNew))
+ return FAIL;
+ else
+ return SPLIT_NEW;
+ }
+ else
+ {
+ if ( RES_TXTATR_INPUTFIELD == nWhichNew )
+ return FAIL;
+ else if ( (RES_TXTATR_INETFMT == nWhichNew) &&
+ (RES_TXTATR_CJK_RUBY == nWhichOther) )
+ return SPLIT_NEW;
+ else
+ return SPLIT_OTHER;
+ }
+}
+
+void SwTextINetFormat::InitINetFormat(SwTextNode & rNode)
+{
+ ChgTextNode(&rNode);
+ SwCharFormat * const pFormat(
+ rNode.GetDoc().getIDocumentStylePoolAccess().GetCharFormatFromPool(RES_POOLCHR_INET_NORMAL) );
+ pFormat->Add( this );
+}
+
+void SwTextRuby::InitRuby(SwTextNode & rNode)
+{
+ ChgTextNode(&rNode);
+ SwCharFormat * const pFormat(
+ rNode.GetDoc().getIDocumentStylePoolAccess().GetCharFormatFromPool(RES_POOLCHR_RUBYTEXT) );
+ pFormat->Add( this );
+}
+
+/**
+ Create a new nesting text hint.
+ */
+static SwTextAttrNesting *
+MakeTextAttrNesting(SwTextNode & rNode, SwTextAttrNesting & rNesting,
+ const sal_Int32 nStart, const sal_Int32 nEnd)
+{
+ SwTextAttr * const pNew( MakeTextAttr(
+ rNode.GetDoc(), rNesting.GetAttr(), nStart, nEnd ) );
+ switch (pNew->Which())
+ {
+ case RES_TXTATR_INETFMT:
+ {
+ static_txtattr_cast<SwTextINetFormat*>(pNew)->InitINetFormat(rNode);
+ break;
+ }
+ case RES_TXTATR_CJK_RUBY:
+ {
+ static_txtattr_cast<SwTextRuby*>(pNew)->InitRuby(rNode);
+ break;
+ }
+ default:
+ assert(!"MakeTextAttrNesting: what the hell is that?");
+ break;
+ }
+ return static_txtattr_cast<SwTextAttrNesting*>(pNew);
+}
+
+typedef std::vector<SwTextAttrNesting *> NestList_t;
+
+static NestList_t::iterator
+lcl_DoSplitImpl(NestList_t & rSplits, SwTextNode & rNode,
+ NestList_t::iterator const iter, sal_Int32 const nSplitPos,
+ bool const bSplitAtStart, bool const bOtherDummy)
+{
+ const sal_Int32 nStartPos( // skip other's dummy character!
+ (bSplitAtStart && bOtherDummy) ? nSplitPos + 1 : nSplitPos );
+ SwTextAttrNesting * const pNew( MakeTextAttrNesting(
+ rNode, **iter, nStartPos, *(*iter)->GetEnd() ) );
+ (*iter)->SetEnd(nSplitPos);
+ return rSplits.insert(iter + 1, pNew);
+}
+
+static void
+lcl_DoSplitNew(NestList_t & rSplits, SwTextNode & rNode,
+ const sal_Int32 nNewStart,
+ const sal_Int32 nOtherStart, const sal_Int32 nOtherEnd, bool bOtherDummy)
+{
+ const bool bSplitAtStart(nNewStart < nOtherStart);
+ const sal_Int32 nSplitPos( bSplitAtStart ? nOtherStart : nOtherEnd );
+ // first find the portion that is split (not necessarily the last one!)
+ NestList_t::iterator const iter(
+ std::find_if( rSplits.begin(), rSplits.end(),
+ [nSplitPos](SwTextAttrEnd * const pAttr) {
+ return TextAttrContains(nSplitPos, pAttr);
+ } ) );
+ if (iter != rSplits.end()) // already split here?
+ {
+ lcl_DoSplitImpl(rSplits, rNode, iter, nSplitPos, bSplitAtStart, bOtherDummy);
+ }
+}
+
+/**
+ Insert nesting hint into the hints array. Also calls NoteInHistory.
+ @param rNewHint the hint to be inserted (must not overlap existing!)
+ */
+void SwpHints::InsertNesting(SwTextAttrNesting & rNewHint)
+{
+ Insert(& rNewHint);
+ NoteInHistory( & rNewHint, true );
+}
+
+/**
+
+The following hints correspond to well-formed XML elements in ODF:
+RES_TXTATR_INETFMT, RES_TXTATR_CJK_RUBY, RES_TXTATR_META, RES_TXTATR_METAFIELD,
+RES_TXTATR_CONTENTCONTROL
+
+The writer core must ensure that these do not overlap; if they did,
+the document would not be storable as ODF.
+
+Also, a Hyperlink must not be nested within another Hyperlink,
+and a Ruby must not be nested within another Ruby.
+
+The ODF export in xmloff will only put a hyperlink into a ruby, never a ruby
+into a hyperlink.
+
+Unfortunately the UNO API for Hyperlink and Ruby consists of the properties
+Hyperlink* and Ruby* of the css.text.CharacterProperties service. In other
+words, they are treated as formatting attributes, not as content entities.
+Furthermore, for API users it is not possible to easily test whether a certain
+range would be overlapping with other nested attributes, and most importantly,
+<em>which ones</em>, so we can hardly refuse to insert these in cases of
+overlap.
+
+It is possible to split Hyperlink and Ruby into multiple portions, such that
+the result is properly nested.
+
+meta and meta-field must not be split, because they have xml:id.
+
+content controls should not split, either.
+
+These constraints result in the following design:
+
+RES_TXTATR_INETFMT:
+ always succeeds
+ inserts n attributes split at RES_TXTATR_CJK_RUBY, RES_TXTATR_META,
+ RES_TXTATR_METAFIELD
+ may replace existing RES_TXTATR_INETFMT at overlap
+RES_TXTATR_CJK_RUBY:
+ always succeeds
+ inserts n attributes split at RES_TXTATR_META, RES_TXTATR_METAFIELD
+ may replace existing RES_TXTATR_CJK_RUBY at overlap
+ may split existing overlapping RES_TXTATR_INETFMT
+RES_TXTATR_META:
+ may fail if overlapping existing RES_TXTATR_META/RES_TXTATR_METAFIELD
+ may split existing overlapping RES_TXTATR_INETFMT or RES_TXTATR_CJK_RUBY
+ inserts 1 attribute
+RES_TXTATR_METAFIELD:
+ may fail if overlapping existing RES_TXTATR_META/RES_TXTATR_METAFIELD
+ may split existing overlapping RES_TXTATR_INETFMT or RES_TXTATR_CJK_RUBY
+ inserts 1 attribute
+
+The nesting is expressed by the position of the hints.
+RES_TXTATR_META and RES_TXTATR_METAFIELD have a CH_TXTATR, and there can
+only be one such hint starting and ending at a given position.
+Only RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY lack a CH_TXTATR.
+The interpretation given is that RES_TXTATR_CJK_RUBY is always around
+a RES_TXTATR_INETFMT at the same start and end position (which corresponds
+with the UNO API).
+Both of these are always around a nesting hint with CH_TXTATR at the same
+start and end position (if they should be inside, then the start should be
+after the CH_TXTATR).
+It would probably be a bad idea to add another nesting hint without
+CH_TXTATR; on the other hand, it would be difficult adding a CH_TXTATR to
+RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY, due to the overwriting and
+splitting of existing hints that is necessary for backward compatibility.
+
+ @param rNode the text node
+ @param rHint the hint to be inserted
+ @returns true iff hint was successfully inserted
+*/
+bool
+SwpHints::TryInsertNesting( SwTextNode & rNode, SwTextAttrNesting & rNewHint )
+{
+// INVARIANT: the nestable hints in the array are properly nested
+ const sal_uInt16 nNewWhich( rNewHint.Which() );
+ const sal_Int32 nNewStart( rNewHint.GetStart() );
+ const sal_Int32 nNewEnd ( *rNewHint.GetEnd() );
+ const bool bNewSelfNestable( isSelfNestable(nNewWhich) );
+
+ assert( (RES_TXTATR_INETFMT == nNewWhich) ||
+ (RES_TXTATR_CJK_RUBY == nNewWhich) ||
+ (RES_TXTATR_META == nNewWhich) ||
+ (RES_TXTATR_METAFIELD == nNewWhich) ||
+ (RES_TXTATR_CONTENTCONTROL == nNewWhich) ||
+ (RES_TXTATR_INPUTFIELD == nNewWhich));
+
+ NestList_t OverlappingExisting; // existing hints to be split
+ NestList_t OverwrittenExisting; // existing hints to be replaced
+ NestList_t SplitNew; // new hints to be inserted
+
+ SplitNew.push_back(& rNewHint);
+
+ // pass 1: split the inserted hint into fragments if necessary
+ for ( size_t i = 0; i < Count(); ++i )
+ {
+ SwTextAttr * const pOther = GetSortedByEnd(i);
+
+ if (pOther->IsNesting())
+ {
+ const sal_uInt16 nOtherWhich( pOther->Which() );
+ const sal_Int32 nOtherStart( pOther->GetStart() );
+ const sal_Int32 nOtherEnd ( *pOther->GetEnd() );
+ if (isOverlap(nNewStart, nNewEnd, nOtherStart, nOtherEnd ))
+ {
+ switch (splitPolicy(nNewWhich, nOtherWhich))
+ {
+ case FAIL:
+ SAL_INFO("sw.core", "cannot insert hint: overlap");
+ for (const auto& aSplit : SplitNew)
+ TextAttrDelete(rNode.GetDoc(), aSplit);
+ return false;
+ case SPLIT_NEW:
+ lcl_DoSplitNew(SplitNew, rNode, nNewStart,
+ nOtherStart, nOtherEnd, pOther->HasDummyChar());
+ break;
+ case SPLIT_OTHER:
+ OverlappingExisting.push_back(
+ static_txtattr_cast<SwTextAttrNesting*>(pOther));
+ break;
+ default:
+ assert(!"bad code monkey");
+ break;
+ }
+ }
+ else if (isNestedAny(nNewStart, nNewEnd, nOtherStart, nOtherEnd))
+ {
+ if (!bNewSelfNestable && (nNewWhich == nOtherWhich))
+ {
+ // ruby and hyperlink: if there is nesting, _overwrite_
+ OverwrittenExisting.push_back(
+ static_txtattr_cast<SwTextAttrNesting*>(pOther));
+ }
+ else if ((nNewStart == nOtherStart) && pOther->HasDummyChar())
+ {
+ if (rNewHint.HasDummyChar())
+ {
+ assert(!"ERROR: inserting duplicate CH_TXTATR hint");
+ return false;
+ } else if (nNewEnd < nOtherEnd) {
+ // other has dummy char, new is inside other, but
+ // new contains the other's dummy char?
+ // should be corrected because it may lead to problems
+ // in SwXMeta::createEnumeration
+ // SplitNew is sorted, so this is the first split
+ assert(SplitNew.front()->GetStart() == nNewStart);
+ SplitNew.front()->SetStart(nNewStart + 1);
+ }
+ }
+ }
+ }
+ }
+
+ // pass 1b: tragically need to check for fieldmarks here too
+ for (auto iter = SplitNew.begin(); iter != SplitNew.end(); ++iter)
+ {
+ SwPaM const temp(rNode, (*iter)->GetStart(), rNode, *(*iter)->GetEnd());
+ std::vector<std::pair<SwNodeOffset, sal_Int32>> Breaks;
+ sw::CalcBreaks(Breaks, temp, true);
+ if (!Breaks.empty())
+ {
+ if (!isSplittable(nNewWhich))
+ {
+ SAL_INFO("sw.core", "cannot insert hint: fieldmark overlap");
+ assert(SplitNew.size() == 1);
+ TextAttrDelete(rNode.GetDoc(), &rNewHint);
+ return false;
+ }
+ else
+ {
+ for (auto const& rPos : Breaks)
+ {
+ assert(rPos.first == rNode.GetIndex());
+ iter = lcl_DoSplitImpl(SplitNew, rNode, iter,
+ rPos.second, true, true);
+ }
+ }
+ }
+ }
+
+ assert((isSplittable(nNewWhich) || SplitNew.size() == 1) &&
+ "splitting the unsplittable ???");
+
+ // pass 2: split existing hints that overlap/nest with new hint
+ // do not iterate over hints array, but over remembered set of overlapping
+ // hints, to keep things simple w.r.t. insertion/removal
+ // N.B: if there is a hint that splits the inserted hint, then
+ // that hint would also have already split any hint in OverlappingExisting
+ // so any hint in OverlappingExisting can be split at most by one hint
+ // in SplitNew, or even not at all (this is not true for existing hints
+ // that go _around_ new hint, which is the reason d'^etre for pass 4)
+ for (auto& rpOther : OverlappingExisting)
+ {
+ const sal_Int32 nOtherStart( rpOther->GetStart() );
+ const sal_Int32 nOtherEnd ( *rpOther->GetEnd() );
+
+ for (const auto& rpNew : SplitNew)
+ {
+ const sal_Int32 nSplitNewStart( rpNew->GetStart() );
+ const sal_Int32 nSplitNewEnd ( *rpNew->GetEnd() );
+ // 4 cases: within, around, overlap l, overlap r, (OTHER: no action)
+ const bool bRemoveOverlap(
+ !bNewSelfNestable && (nNewWhich == rpOther->Which()) );
+
+ switch (ComparePosition(nSplitNewStart, nSplitNewEnd,
+ nOtherStart, nOtherEnd))
+ {
+ case SwComparePosition::Inside:
+ {
+ assert(!bRemoveOverlap &&
+ "this one should be in OverwrittenExisting?");
+ }
+ break;
+ case SwComparePosition::Outside:
+ case SwComparePosition::Equal:
+ {
+ assert(!"existing hint inside new hint: why?");
+ }
+ break;
+ case SwComparePosition::OverlapBefore:
+ {
+ Delete( rpOther ); // this also does NoteInHistory!
+ rpOther->SetStart(nSplitNewEnd);
+ InsertNesting( *rpOther );
+ if (!bRemoveOverlap)
+ {
+ if ( MAX_HINTS <= Count() )
+ {
+ SAL_INFO("sw.core", "hints array full :-(");
+ return false;
+ }
+ SwTextAttrNesting * const pOtherLeft(
+ MakeTextAttrNesting( rNode, *rpOther,
+ nOtherStart, nSplitNewEnd ) );
+ InsertNesting( *pOtherLeft );
+ }
+ }
+ break;
+ case SwComparePosition::OverlapBehind:
+ {
+ Delete( rpOther ); // this also does NoteInHistory!
+ rpOther->SetEnd(nSplitNewStart);
+ InsertNesting( *rpOther );
+ if (!bRemoveOverlap)
+ {
+ if ( MAX_HINTS <= Count() )
+ {
+ SAL_INFO("sw.core", "hints array full :-(");
+ return false;
+ }
+ SwTextAttrNesting * const pOtherRight(
+ MakeTextAttrNesting( rNode, *rpOther,
+ nSplitNewStart, nOtherEnd ) );
+ InsertNesting( *pOtherRight );
+ }
+ }
+ break;
+ default:
+ break; // overlap resolved by splitting new: nothing to do
+ }
+ }
+ }
+
+ if ( MAX_HINTS <= Count() || MAX_HINTS - Count() <= SplitNew.size() )
+ {
+ SAL_INFO("sw.core", "hints array full :-(");
+ return false;
+ }
+
+ // pass 3: insert new hints
+ for (const auto& rpHint : SplitNew)
+ {
+ InsertNesting(*rpHint);
+ }
+
+ // pass 4: handle overwritten hints
+ // RES_TXTATR_INETFMT and RES_TXTATR_CJK_RUBY should displace attributes
+ // of the same kind.
+ for (auto& rpOther : OverwrittenExisting)
+ {
+ const sal_Int32 nOtherStart( rpOther->GetStart() );
+ const sal_Int32 nOtherEnd ( *rpOther->GetEnd() );
+
+ // overwritten portion is given by start/end of inserted hint
+ if ((nNewStart <= nOtherStart) && (nOtherEnd <= nNewEnd))
+ {
+ Delete(rpOther);
+ rNode.DestroyAttr( rpOther );
+ }
+ else
+ {
+ assert((nOtherStart < nNewStart) || (nNewEnd < nOtherEnd));
+ // scenario: there is a RUBY, and contained within that a META;
+ // now a RUBY is inserted within the META => the existing RUBY is split:
+ // here it is not possible to simply insert the left/right fragment
+ // of the existing RUBY because they <em>overlap</em> with the META!
+ Delete( rpOther ); // this also does NoteInHistory!
+ if (nNewEnd < nOtherEnd)
+ {
+ SwTextAttrNesting * const pOtherRight(
+ MakeTextAttrNesting(
+ rNode, *rpOther, nNewEnd, nOtherEnd ) );
+ bool const bSuccess( TryInsertNesting(rNode, *pOtherRight) );
+ SAL_WARN_IF(!bSuccess, "sw.core", "recursive call 1 failed?");
+ }
+ if (nOtherStart < nNewStart)
+ {
+ rpOther->SetEnd(nNewStart);
+ bool const bSuccess( TryInsertNesting(rNode, *rpOther) );
+ SAL_WARN_IF(!bSuccess, "sw.core", "recursive call 2 failed?");
+ }
+ else
+ {
+ rNode.DestroyAttr(rpOther);
+ }
+ }
+ }
+
+ return true;
+}
+
+// This function takes care for the following text attribute:
+// RES_TXTATR_CHARFMT, RES_TXTATR_AUTOFMT
+// These attributes have to be handled in a special way (Portion building).
+
+// The new attribute will be split by any existing RES_TXTATR_AUTOFMT or
+// RES_TXTATR_CHARFMT. The new attribute itself will
+// split any existing RES_TXTATR_AUTOFMT or RES_TXTATR_CHARFMT.
+
+void SwpHints::BuildPortions( SwTextNode& rNode, SwTextAttr& rNewHint,
+ const SetAttrMode nMode )
+{
+ const sal_uInt16 nWhich = rNewHint.Which();
+
+ const sal_Int32 nThisStart = rNewHint.GetStart();
+ const sal_Int32 nThisEnd = *rNewHint.GetEnd();
+ const bool bNoLengthAttribute = nThisStart == nThisEnd;
+
+ std::vector<SwTextAttr*> aInsDelHints;
+
+ assert( RES_TXTATR_CHARFMT == rNewHint.Which() ||
+ RES_TXTATR_AUTOFMT == rNewHint.Which() );
+
+ // 2. Find the hints which cover the start and end position
+ // of the new hint. These hints have to be split into two portions:
+
+ if ( !bNoLengthAttribute ) // nothing to do for no length attributes
+ {
+ for ( size_t i = 0; i < Count(); ++i )
+ {
+ // we're modifying stuff here which affects the sorting, and we
+ // don't want it changing underneath us
+ SwTextAttr* pOther = GetWithoutResorting(i);
+
+ if ( RES_TXTATR_CHARFMT != pOther->Which() &&
+ RES_TXTATR_AUTOFMT != pOther->Which() )
+ continue;
+
+ sal_Int32 nOtherStart = pOther->GetStart();
+ const sal_Int32 nOtherEnd = *pOther->GetEnd();
+
+ // Check if start of new attribute overlaps with pOther:
+ // Split pOther if necessary:
+ if ( nOtherStart < nThisStart && nThisStart < nOtherEnd )
+ {
+ SwTextAttr* pNewAttr = MakeTextAttr( rNode.GetDoc(),
+ pOther->GetAttr(), nOtherStart, nThisStart );
+ if ( RES_TXTATR_CHARFMT == pOther->Which() )
+ {
+ static_txtattr_cast<SwTextCharFormat*>(pNewAttr)->SetSortNumber(
+ static_txtattr_cast<SwTextCharFormat*>(pOther)->GetSortNumber() );
+ }
+ aInsDelHints.push_back( pNewAttr );
+
+ NoteInHistory( pOther );
+ pOther->SetStart(nThisStart);
+ NoteInHistory( pOther, true );
+
+ nOtherStart = nThisStart;
+ }
+
+ // Check if end of new attribute overlaps with pOther:
+ // Split pOther if necessary:
+ if ( nOtherStart < nThisEnd && nThisEnd < nOtherEnd )
+ {
+ SwTextAttr* pNewAttr = MakeTextAttr( rNode.GetDoc(),
+ pOther->GetAttr(), nOtherStart, nThisEnd );
+ if ( RES_TXTATR_CHARFMT == pOther->Which() )
+ {
+ static_txtattr_cast<SwTextCharFormat*>(pNewAttr)->SetSortNumber(
+ static_txtattr_cast<SwTextCharFormat*>(pOther)->GetSortNumber());
+ }
+ aInsDelHints.push_back( pNewAttr );
+
+ NoteInHistory( pOther );
+ pOther->SetStart(nThisEnd);
+ NoteInHistory( pOther, true );
+ }
+ }
+
+ // Insert the newly created attributes:
+ for ( const auto& rpHint : aInsDelHints )
+ {
+ Insert( rpHint );
+ NoteInHistory( rpHint, true );
+ }
+ }
+
+#ifdef DBG_UTIL
+ if( !rNode.GetDoc().IsInReading() )
+ CHECK_NOTMERGED; // ignore flags not set properly yet, don't check them
+#endif
+
+ // 4. Split rNewHint into 1 ... n new hints:
+
+ o3tl::sorted_vector<sal_Int32> aBounds;
+ aBounds.insert( nThisStart );
+ aBounds.insert( nThisEnd );
+
+ if ( !bNoLengthAttribute ) // nothing to do for no length attributes
+ {
+ for ( size_t i = 0; i < Count(); ++i )
+ {
+ const SwTextAttr* pOther = Get(i);
+
+ if ( RES_TXTATR_CHARFMT != pOther->Which() &&
+ RES_TXTATR_AUTOFMT != pOther->Which() )
+ continue;
+
+ const sal_Int32 nOtherStart = pOther->GetStart();
+ const sal_Int32 nOtherEnd = *pOther->End();
+
+ if (nThisStart <= nOtherStart && nOtherStart <= nThisEnd)
+ aBounds.insert( nOtherStart );
+ if (nThisStart <= nOtherEnd && nOtherEnd <= nThisEnd)
+ aBounds.insert( nOtherEnd );
+ }
+ }
+
+ auto aStartIter = aBounds.lower_bound( nThisStart );
+ auto aEndIter = aBounds.upper_bound( nThisEnd );
+ sal_Int32 nPorStart = *aStartIter;
+ ++aStartIter;
+ bool bDestroyHint = true;
+
+ // Insert the 1...n new parts of the new attribute:
+
+ while ( aStartIter != aEndIter || bNoLengthAttribute )
+ {
+ OSL_ENSURE( bNoLengthAttribute || nPorStart < *aStartIter, "AUTOSTYLES: BuildPortion trouble" );
+
+ const sal_Int32 nPorEnd = bNoLengthAttribute ? nPorStart : *aStartIter;
+ aInsDelHints.clear();
+
+ // Get all hints that are in [nPorStart, nPorEnd[:
+ for ( size_t i = 0; i < Count(); ++i )
+ {
+ // we get called from TryInsertHint, which changes ordering
+ SwTextAttr *pOther = GetWithoutResorting(i);
+
+ if ( RES_TXTATR_CHARFMT != pOther->Which() &&
+ RES_TXTATR_AUTOFMT != pOther->Which() )
+ continue;
+
+ const sal_Int32 nOtherStart = pOther->GetStart();
+
+ if ( nOtherStart > nPorStart )
+ break;
+
+ if ( pOther->GetEnd() && *pOther->GetEnd() == nPorEnd && nOtherStart == nPorStart )
+ {
+ OSL_ENSURE( *pOther->GetEnd() == nPorEnd, "AUTOSTYLES: BuildPortion trouble" );
+ aInsDelHints.push_back( pOther );
+ }
+ }
+
+ SwTextAttr* pNewAttr = nullptr;
+ if ( RES_TXTATR_CHARFMT == nWhich )
+ {
+ // pNewHint can be inserted after calculating the sort value.
+ // This should ensure, that pNewHint comes behind the already present
+ // character style
+ sal_uInt16 nCharStyleCount = 0;
+ for ( const auto& rpHint : aInsDelHints )
+ {
+ if ( RES_TXTATR_CHARFMT == rpHint->Which() )
+ {
+ // #i74589#
+ const SwFormatCharFormat& rOtherCharFormat = rpHint->GetCharFormat();
+ const SwFormatCharFormat& rThisCharFormat = rNewHint.GetCharFormat();
+ const bool bSameCharFormat = rOtherCharFormat.GetCharFormat() == rThisCharFormat.GetCharFormat();
+
+ // #i90311#
+ // Do not remove existing character format hint during XML import
+ if ( !rNode.GetDoc().IsInXMLImport() &&
+ ( !( SetAttrMode::DONTREPLACE & nMode ) ||
+ bNoLengthAttribute ||
+ bSameCharFormat ) )
+ {
+ // Remove old hint
+ Delete( rpHint );
+ rNode.DestroyAttr( rpHint );
+ }
+ else
+ ++nCharStyleCount;
+ }
+ else
+ {
+ // remove all attributes from auto styles, which are explicitly set in
+ // the new character format:
+ OSL_ENSURE( RES_TXTATR_AUTOFMT == rpHint->Which(), "AUTOSTYLES - Misc trouble" );
+ SwTextAttr* pOther = rpHint;
+ const std::shared_ptr<SfxItemSet> & pOldStyle = static_cast<const SwFormatAutoFormat&>(pOther->GetAttr()).GetStyleHandle();
+
+ // For each attribute in the automatic style check if it
+ // is also set the new character style:
+ SfxItemSet aNewSet( *pOldStyle->GetPool(),
+ aCharAutoFormatSetRange);
+ SfxItemIter aItemIter( *pOldStyle );
+ const SfxPoolItem* pItem = aItemIter.GetCurItem();
+ do
+ {
+ if ( !CharFormat::IsItemIncluded( pItem->Which(), &rNewHint ) )
+ {
+ aNewSet.Put( *pItem );
+ }
+
+ pItem = aItemIter.NextItem();
+ } while (pItem);
+
+ // Remove old hint
+ Delete( pOther );
+ rNode.DestroyAttr( pOther );
+
+ // Create new AutoStyle
+ if ( aNewSet.Count() )
+ {
+ pNewAttr = MakeTextAttr( rNode.GetDoc(),
+ aNewSet, nPorStart, nPorEnd );
+ Insert( pNewAttr );
+ NoteInHistory( pNewAttr, true );
+ }
+ }
+ }
+
+ // If there is no current hint and start and end of rNewHint
+ // is ok, we do not need to create a new txtattr.
+ if ( nPorStart == nThisStart &&
+ nPorEnd == nThisEnd &&
+ !nCharStyleCount )
+ {
+ pNewAttr = &rNewHint;
+ bDestroyHint = false;
+ }
+ else
+ {
+ pNewAttr = MakeTextAttr( rNode.GetDoc(), rNewHint.GetAttr(),
+ nPorStart, nPorEnd );
+ static_txtattr_cast<SwTextCharFormat*>(pNewAttr)->SetSortNumber(nCharStyleCount);
+ }
+ }
+ else
+ {
+ // Find the current autostyle. Mix attributes if necessary.
+ SwTextAttr* pCurrentAutoStyle = nullptr;
+ SwTextAttr* pCurrentCharFormat = nullptr;
+ for ( const auto& rpHint : aInsDelHints )
+ {
+ if ( RES_TXTATR_AUTOFMT == rpHint->Which() )
+ pCurrentAutoStyle = rpHint;
+ else if ( RES_TXTATR_CHARFMT == rpHint->Which() )
+ pCurrentCharFormat = rpHint;
+ }
+
+ std::shared_ptr<SfxItemSet> pNewStyle = static_cast<const SwFormatAutoFormat&>(rNewHint.GetAttr()).GetStyleHandle();
+ if ( pCurrentAutoStyle )
+ {
+ const std::shared_ptr<SfxItemSet> & pCurrentStyle = static_cast<const SwFormatAutoFormat&>(pCurrentAutoStyle->GetAttr()).GetStyleHandle();
+
+ // Merge attributes
+ SfxItemSet aNewSet( *pCurrentStyle );
+ aNewSet.Put( *pNewStyle );
+
+ // #i75750# Remove attributes already set at whole paragraph
+ // #i81764# This should not be applied for no length attributes!!! <--
+ if ( !bNoLengthAttribute && rNode.HasSwAttrSet() && aNewSet.Count() )
+ {
+ SfxItemIter aIter2( aNewSet );
+ const SfxPoolItem* pItem = aIter2.GetCurItem();
+ const SfxItemSet& rWholeParaAttrSet = rNode.GetSwAttrSet();
+
+ do
+ {
+ const SfxPoolItem* pTmpItem = nullptr;
+ if ( SfxItemState::SET == rWholeParaAttrSet.GetItemState( pItem->Which(), false, &pTmpItem ) &&
+ SfxPoolItem::areSame(pTmpItem, pItem) )
+ {
+ // Do not clear item if the attribute is set in a character format:
+ if ( !pCurrentCharFormat || nullptr == CharFormat::GetItem( *pCurrentCharFormat, pItem->Which() ) )
+ aIter2.ClearItem();
+ }
+ }
+ while ((pItem = aIter2.NextItem()));
+ }
+
+ // Remove old hint
+ Delete( pCurrentAutoStyle );
+ rNode.DestroyAttr( pCurrentAutoStyle );
+
+ // Create new AutoStyle
+ if ( aNewSet.Count() )
+ pNewAttr = MakeTextAttr( rNode.GetDoc(), aNewSet,
+ nPorStart, nPorEnd );
+ }
+ else
+ {
+ // Remove any attributes which are already set at the whole paragraph:
+ bool bOptimizeAllowed = true;
+
+ // #i75750# Remove attributes already set at whole paragraph
+ // #i81764# This should not be applied for no length attributes!!! <--
+ if ( !bNoLengthAttribute && rNode.HasSwAttrSet() && pNewStyle->Count() )
+ {
+ std::unique_ptr<SfxItemSet> pNewSet;
+
+ SfxItemIter aIter2( *pNewStyle );
+ const SfxPoolItem* pItem = aIter2.GetCurItem();
+ const SfxItemSet& rWholeParaAttrSet = rNode.GetSwAttrSet();
+
+ do
+ {
+ const SfxPoolItem* pTmpItem = nullptr;
+ // here direct SfxPoolItem ptr comp was wrong, found using SfxPoolItem::areSame
+ if ( SfxItemState::SET == rWholeParaAttrSet.GetItemState( pItem->Which(), false, &pTmpItem ) &&
+ SfxPoolItem::areSame(pTmpItem, pItem) )
+ {
+ // Do not clear item if the attribute is set in a character format:
+ if ( !pCurrentCharFormat || nullptr == CharFormat::GetItem( *pCurrentCharFormat, pItem->Which() ) )
+ {
+ if ( !pNewSet )
+ pNewSet = pNewStyle->Clone();
+ pNewSet->ClearItem( pItem->Which() );
+ }
+ }
+ }
+ while ((pItem = aIter2.NextItem()));
+
+ if ( pNewSet )
+ {
+ bOptimizeAllowed = false;
+ if ( pNewSet->Count() )
+ pNewStyle = rNode.getIDocumentStyleAccess().getAutomaticStyle( *pNewSet, IStyleAccess::AUTO_STYLE_CHAR );
+ else
+ pNewStyle.reset();
+ }
+ }
+
+ // Create new AutoStyle
+ // If there is no current hint and start and end of rNewHint
+ // is ok, we do not need to create a new txtattr.
+ if ( bOptimizeAllowed &&
+ nPorStart == nThisStart &&
+ nPorEnd == nThisEnd )
+ {
+ pNewAttr = &rNewHint;
+ bDestroyHint = false;
+ }
+ else if ( pNewStyle )
+ {
+ pNewAttr = MakeTextAttr( rNode.GetDoc(), *pNewStyle,
+ nPorStart, nPorEnd );
+ }
+ }
+ }
+
+ if ( pNewAttr )
+ {
+ Insert( pNewAttr );
+// if ( bDestroyHint )
+ NoteInHistory( pNewAttr, true );
+ }
+
+ if ( !bNoLengthAttribute )
+ {
+ nPorStart = *aStartIter;
+ ++aStartIter;
+ }
+ else
+ break;
+ }
+
+ if ( bDestroyHint )
+ rNode.DestroyAttr( &rNewHint );
+}
+
+SwTextAttr* MakeRedlineTextAttr( SwDoc & rDoc, SfxPoolItem const & rAttr )
+{
+ // this is intended _only_ for special-purpose redline attributes!
+ switch (rAttr.Which())
+ {
+ case RES_CHRATR_COLOR:
+ case RES_CHRATR_WEIGHT:
+ case RES_CHRATR_CJK_WEIGHT:
+ case RES_CHRATR_CTL_WEIGHT:
+ case RES_CHRATR_POSTURE:
+ case RES_CHRATR_CJK_POSTURE:
+ case RES_CHRATR_CTL_POSTURE:
+ case RES_CHRATR_UNDERLINE:
+ case RES_CHRATR_CROSSEDOUT:
+ case RES_CHRATR_CASEMAP:
+ case RES_CHRATR_BACKGROUND:
+ break;
+ default:
+ assert(!"unsupported redline attribute");
+ break;
+ }
+
+ // Put new attribute into pool
+ // FIXME: this const_cast is evil!
+ SfxPoolItem& rNew =
+ const_cast<SfxPoolItem&>( rDoc.GetAttrPool().DirectPutItemInPool( rAttr ) );
+ return new SwTextAttrEnd( rNew, 0, 0 );
+}
+
+// create new text attribute
+SwTextAttr* MakeTextAttr(
+ SwDoc & rDoc,
+ SfxPoolItem& rAttr,
+ sal_Int32 const nStt,
+ sal_Int32 const nEnd,
+ CopyOrNewType const bIsCopy,
+ SwTextNode *const pTextNode )
+{
+ if ( isCHRATR(rAttr.Which()) )
+ {
+ // Somebody wants to build a SwTextAttr for a character attribute.
+ // Sorry, this is not allowed any longer.
+ // You'll get a brand new autostyle attribute:
+ SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aItemSet( rDoc.GetAttrPool() );
+ aItemSet.Put( rAttr );
+ return MakeTextAttr( rDoc, aItemSet, nStt, nEnd );
+ }
+ else if ( RES_TXTATR_AUTOFMT == rAttr.Which() &&
+ static_cast<const SwFormatAutoFormat&>(rAttr).GetStyleHandle()->
+ GetPool() != &rDoc.GetAttrPool() )
+ {
+ // If the attribute is an auto-style which refers to a pool that is
+ // different from rDoc's pool, we have to correct this:
+ const std::shared_ptr<SfxItemSet> & pAutoStyle = static_cast<const SwFormatAutoFormat&>(rAttr).GetStyleHandle();
+ std::unique_ptr<const SfxItemSet> pNewSet(
+ pAutoStyle->SfxItemSet::Clone( true, &rDoc.GetAttrPool() ));
+ SwTextAttr* pNew = MakeTextAttr( rDoc, *pNewSet, nStt, nEnd );
+ return pNew;
+ }
+
+ // Put new attribute into pool
+ // FIXME: this const_cast is evil!
+ SfxPoolItem& rNew =
+ const_cast<SfxPoolItem&>( rDoc.GetAttrPool().DirectPutItemInPool( rAttr ) );
+
+ SwTextAttr* pNew = nullptr;
+ switch( rNew.Which() )
+ {
+ case RES_TXTATR_CHARFMT:
+ {
+ SwFormatCharFormat &rFormatCharFormat = static_cast<SwFormatCharFormat&>(rNew);
+ if( !rFormatCharFormat.GetCharFormat() )
+ {
+ rFormatCharFormat.SetCharFormat( rDoc.GetDfltCharFormat() );
+ }
+
+ pNew = new SwTextCharFormat( rFormatCharFormat, nStt, nEnd );
+ }
+ break;
+ case RES_TXTATR_INETFMT:
+ pNew = new SwTextINetFormat( static_cast<SwFormatINetFormat&>(rNew), nStt, nEnd );
+ break;
+
+ case RES_TXTATR_FIELD:
+ pNew = new SwTextField( static_cast<SwFormatField &>(rNew), nStt,
+ rDoc.IsClipBoard() );
+ break;
+
+ case RES_TXTATR_ANNOTATION:
+ {
+ pNew = new SwTextAnnotationField( static_cast<SwFormatField &>(rNew), nStt, rDoc.IsClipBoard() );
+ if (bIsCopy == CopyOrNewType::Copy)
+ {
+ // On copy of the annotation field do not keep the annotated text range by removing
+ // the relation to its annotation mark (relation established via annotation field's name).
+ // If the annotation mark is also copied, the relation and thus the annotated text range will be reestablished,
+ // when the annotation mark is created and inserted into the document.
+ auto& pField = const_cast<SwPostItField&>(dynamic_cast<const SwPostItField&>(*(pNew->GetFormatField().GetField())));
+ pField.SetName(OUString());
+ pField.SetPostItId();
+ }
+ }
+ break;
+
+ case RES_TXTATR_INPUTFIELD:
+ pNew = new SwTextInputField( static_cast<SwFormatField &>(rNew), nStt, nEnd,
+ rDoc.IsClipBoard() );
+ break;
+
+ case RES_TXTATR_FLYCNT:
+ {
+ // finally, copy the frame format (with content)
+ pNew = new SwTextFlyCnt( static_cast<SwFormatFlyCnt&>(rNew), nStt );
+ if ( static_cast<const SwFormatFlyCnt &>(rAttr).GetTextFlyCnt() )
+ {
+ // if it has an existing attr then the format must be copied
+ static_cast<SwTextFlyCnt *>(pNew)->CopyFlyFormat( rDoc );
+ }
+ }
+ break;
+ case RES_TXTATR_FTN:
+ pNew = new SwTextFootnote( static_cast<SwFormatFootnote&>(rNew), nStt );
+ // copy note's SeqNo
+ if( static_cast<SwFormatFootnote&>(rAttr).GetTextFootnote() )
+ static_cast<SwTextFootnote*>(pNew)->SetSeqNo( static_cast<SwFormatFootnote&>(rAttr).GetTextFootnote()->GetSeqRefNo() );
+ break;
+ case RES_TXTATR_REFMARK:
+ pNew = nStt == nEnd
+ ? new SwTextRefMark( static_cast<SwFormatRefMark&>(rNew), nStt )
+ : new SwTextRefMark( static_cast<SwFormatRefMark&>(rNew), nStt, &nEnd );
+ break;
+ case RES_TXTATR_TOXMARK:
+ {
+ SwTOXMark& rMark = static_cast<SwTOXMark&>(rNew);
+
+ // tdf#98868 if the SwTOXType is from a different document that the
+ // target, re-register the TOXMark against a matching SwTOXType from
+ // the target document instead
+ const SwTOXType* pTOXType = rMark.GetTOXType();
+ if (pTOXType && &pTOXType->GetDoc() != &rDoc)
+ {
+ SwTOXType* pToxType = SwHistorySetTOXMark::GetSwTOXType(rDoc, pTOXType->GetType(),
+ pTOXType->GetTypeName());
+ rMark.RegisterToTOXType(*pToxType);
+ }
+
+ pNew = new SwTextTOXMark(rMark, nStt, &nEnd);
+ break;
+ }
+ case RES_TXTATR_CJK_RUBY:
+ pNew = new SwTextRuby( static_cast<SwFormatRuby&>(rNew), nStt, nEnd );
+ break;
+ case RES_TXTATR_META:
+ case RES_TXTATR_METAFIELD:
+ pNew = SwTextMeta::CreateTextMeta( rDoc.GetMetaFieldManager(), pTextNode,
+ static_cast<SwFormatMeta&>(rNew), nStt, nEnd, bIsCopy == CopyOrNewType::Copy );
+ break;
+ case RES_TXTATR_LINEBREAK:
+ pNew = new SwTextLineBreak(static_cast<SwFormatLineBreak&>(rNew), nStt);
+ break;
+ case RES_TXTATR_CONTENTCONTROL:
+ pNew = SwTextContentControl::CreateTextContentControl(
+ rDoc, pTextNode, static_cast<SwFormatContentControl&>(rNew), nStt, nEnd,
+ bIsCopy == CopyOrNewType::Copy);
+ break;
+ default:
+ assert(RES_TXTATR_AUTOFMT == rNew.Which());
+ pNew = new SwTextAttrEnd( rNew, nStt, nEnd );
+ break;
+ }
+
+ return pNew;
+}
+
+SwTextAttr* MakeTextAttr( SwDoc & rDoc, const SfxItemSet& rSet,
+ sal_Int32 nStt, sal_Int32 nEnd )
+{
+ IStyleAccess& rStyleAccess = rDoc.GetIStyleAccess();
+ const std::shared_ptr<SfxItemSet> pAutoStyle = rStyleAccess.getAutomaticStyle( rSet, IStyleAccess::AUTO_STYLE_CHAR );
+ SwFormatAutoFormat aNewAutoFormat;
+ aNewAutoFormat.SetStyleHandle( pAutoStyle );
+ SwTextAttr* pNew = MakeTextAttr( rDoc, aNewAutoFormat, nStt, nEnd );
+ return pNew;
+}
+
+// delete the text attribute and unregister its item at the pool
+void SwTextNode::DestroyAttr( SwTextAttr* pAttr )
+{
+ if( !pAttr )
+ return;
+
+ // some things need to be done before deleting the formatting attribute
+ SwDoc& rDoc = GetDoc();
+ switch( pAttr->Which() )
+ {
+ case RES_TXTATR_FLYCNT:
+ {
+ SwFrameFormat* pFormat = pAttr->GetFlyCnt().GetFrameFormat();
+ if( pFormat ) // set to 0 by Undo?
+ rDoc.getIDocumentLayoutAccess().DelLayoutFormat( pFormat );
+ }
+ break;
+
+ case RES_CHRATR_HIDDEN:
+ SetCalcHiddenCharFlags();
+ break;
+
+ case RES_TXTATR_FTN:
+ static_cast<SwTextFootnote*>(pAttr)->SetStartNode( nullptr );
+ static_cast<SwFormatFootnote&>(pAttr->GetAttr()).InvalidateFootnote();
+ break;
+
+ case RES_TXTATR_FIELD:
+ case RES_TXTATR_ANNOTATION:
+ case RES_TXTATR_INPUTFIELD:
+ if( !rDoc.IsInDtor() )
+ {
+ SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pAttr));
+ SwFieldType* pFieldType = pAttr->GetFormatField().GetField()->GetTyp();
+
+ if (SwFieldIds::Dde != pFieldType->Which()
+ && !pTextField->GetpTextNode())
+ {
+ break; // was not yet inserted
+ }
+
+ //JP 06-08-95: DDE-fields are an exception
+ assert(SwFieldIds::Dde == pFieldType->Which() ||
+ this == pTextField->GetpTextNode());
+
+ // certain fields must update the SwDoc's calculation flags
+
+ // Certain fields (like HiddenParaField) must trigger recalculation of visible flag
+ if (GetDoc().FieldCanHideParaWeight(pFieldType->Which()))
+ SetCalcHiddenParaField();
+
+ switch( pFieldType->Which() )
+ {
+ case SwFieldIds::HiddenPara:
+ case SwFieldIds::DbSetNumber:
+ case SwFieldIds::GetExp:
+ case SwFieldIds::Database:
+ case SwFieldIds::SetExp:
+ case SwFieldIds::HiddenText:
+ case SwFieldIds::DbNumSet:
+ case SwFieldIds::DbNextSet:
+ if( !rDoc.getIDocumentFieldsAccess().IsNewFieldLst() && GetNodes().IsDocNodes() )
+ rDoc.getIDocumentFieldsAccess().InsDelFieldInFieldLst(false, *pTextField);
+ break;
+ case SwFieldIds::Dde:
+ if (GetNodes().IsDocNodes() && pTextField->GetpTextNode())
+ static_cast<SwDDEFieldType*>(pFieldType)->DecRefCnt();
+ break;
+ case SwFieldIds::Postit:
+ {
+ const_cast<SwFormatField&>(pAttr->GetFormatField()).Broadcast(
+ SwFormatFieldHint(&pTextField->GetFormatField(), SwFormatFieldHintWhich::REMOVED));
+ break;
+ }
+ default: break;
+ }
+ }
+ static_cast<SwFormatField&>(pAttr->GetAttr()).InvalidateField();
+ break;
+
+ case RES_TXTATR_TOXMARK:
+ static_cast<SwTOXMark&>(pAttr->GetAttr()).InvalidateTOXMark();
+ break;
+
+ case RES_TXTATR_REFMARK:
+ static_cast<SwFormatRefMark&>(pAttr->GetAttr()).InvalidateRefMark();
+ break;
+
+ case RES_TXTATR_META:
+ case RES_TXTATR_METAFIELD:
+ {
+ auto pTextMeta = static_txtattr_cast<SwTextMeta*>(pAttr);
+ SwFormatMeta & rFormatMeta( static_cast<SwFormatMeta &>(pTextMeta->GetAttr()) );
+ if (::sw::Meta* pMeta = rFormatMeta.GetMeta())
+ {
+ if (SwDocShell* pDocSh = rDoc.GetDocShell())
+ {
+ static constexpr OUStringLiteral metaNS(u"urn:bails");
+ const css::uno::Reference<css::rdf::XResource> xSubject = pMeta->MakeUnoObject();
+ uno::Reference<frame::XModel> xModel = pDocSh->GetBaseModel();
+ SwRDFHelper::clearStatements(xModel, metaNS, xSubject);
+ }
+ }
+
+ static_txtattr_cast<SwTextMeta*>(pAttr)->ChgTextNode(nullptr);
+ }
+ break;
+ case RES_TXTATR_CONTENTCONTROL:
+ {
+ static_txtattr_cast<SwTextContentControl*>(pAttr)->ChgTextNode(nullptr);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ SwTextAttr::Destroy( pAttr, rDoc.GetAttrPool() );
+}
+
+SwTextAttr* SwTextNode::InsertItem(
+ SfxPoolItem& rAttr,
+ const sal_Int32 nStart,
+ const sal_Int32 nEnd,
+ const SetAttrMode nMode )
+{
+ // character attributes will be inserted as automatic styles:
+ assert( !isCHRATR(rAttr.Which()) && "AUTOSTYLES - "
+ "SwTextNode::InsertItem should not be called with character attributes");
+
+ SwTextAttr *const pNew =
+ MakeTextAttr(
+ GetDoc(),
+ rAttr,
+ nStart,
+ nEnd,
+ (nMode & SetAttrMode::IS_COPY) ? CopyOrNewType::Copy : CopyOrNewType::New,
+ this );
+
+ if ( pNew )
+ {
+ const bool bSuccess( InsertHint( pNew, nMode ) );
+ // N.B.: also check that the hint is actually in the hints array,
+ // because hints of certain types may be merged after successful
+ // insertion, and thus destroyed!
+ if (!bSuccess || !m_pSwpHints->Contains( pNew ))
+ {
+ return nullptr;
+ }
+ }
+
+ return pNew;
+}
+
+// take ownership of pAttr; if insertion fails, delete pAttr
+bool SwTextNode::InsertHint( SwTextAttr * const pAttr, const SetAttrMode nMode )
+{
+ bool bHiddenPara = false;
+
+ assert(pAttr && pAttr->GetStart() <= Len());
+ assert(!pAttr->GetEnd() || (*pAttr->GetEnd() <= Len()));
+
+ // translate from SetAttrMode to InsertMode (for hints with CH_TXTATR)
+ const SwInsertFlags nInsertFlags =
+ (nMode & SetAttrMode::NOHINTEXPAND)
+ ? SwInsertFlags::NOHINTEXPAND
+ : (nMode & SetAttrMode::FORCEHINTEXPAND)
+ ? (SwInsertFlags::FORCEHINTEXPAND | SwInsertFlags::EMPTYEXPAND)
+ : SwInsertFlags::EMPTYEXPAND;
+
+ // need this after TryInsertHint, when pAttr may be deleted
+ const sal_Int32 nStart( pAttr->GetStart() );
+ const bool bDummyChar( pAttr->HasDummyChar() );
+ if (bDummyChar)
+ {
+ SetAttrMode nInsMode = nMode;
+ switch( pAttr->Which() )
+ {
+ case RES_TXTATR_FLYCNT:
+ {
+ SwTextFlyCnt *pFly = static_cast<SwTextFlyCnt *>(pAttr);
+ SwFrameFormat* pFormat = pAttr->GetFlyCnt().GetFrameFormat();
+ if( !(SetAttrMode::NOTXTATRCHR & nInsMode) )
+ {
+ // Need to insert char first, because SetAnchor() reads
+ // GetStart().
+ //JP 11.05.98: if the anchor is already set correctly,
+ // fix it after inserting the char, so that clients don't
+ // have to worry about it.
+ const SwFormatAnchor* pAnchor = pFormat->GetItemIfSet( RES_ANCHOR, false );
+
+ SwContentIndex aIdx( this, pAttr->GetStart() );
+ const OUString c(GetCharOfTextAttr(*pAttr));
+ OUString const ins( InsertText(c, aIdx, nInsertFlags) );
+ if (ins.isEmpty())
+ {
+ // do not record deletion of Format!
+ ::sw::UndoGuard const ug(
+ pFormat->GetDoc()->GetIDocumentUndoRedo());
+ DestroyAttr(pAttr);
+ return false; // text node full :(
+ }
+ nInsMode |= SetAttrMode::NOTXTATRCHR;
+
+ if (pAnchor &&
+ (RndStdIds::FLY_AS_CHAR == pAnchor->GetAnchorId()) &&
+ pAnchor->GetAnchorNode() &&
+ *pAnchor->GetAnchorNode() == *this &&
+ pAnchor->GetAnchorContentOffset() == aIdx.GetIndex() )
+ {
+ const_cast<SwPosition*>(pAnchor->GetContentAnchor())->AdjustContent(-1);
+ }
+ }
+ pFly->SetAnchor( this );
+
+ // format pointer could have changed in SetAnchor,
+ // when copying to other docs!
+ pFormat = pAttr->GetFlyCnt().GetFrameFormat();
+ SwDoc *pDoc = pFormat->GetDoc();
+
+ // OD 26.06.2003 - allow drawing objects in header/footer.
+ // But don't allow control objects in header/footer
+ if( RES_DRAWFRMFMT == pFormat->Which() &&
+ pDoc->IsInHeaderFooter( *pFormat->GetAnchor().GetAnchorNode() ) )
+ {
+ bool bCheckControlLayer = false;
+ pFormat->CallSwClientNotify(sw::CheckDrawFrameFormatLayerHint(&bCheckControlLayer));
+ if( bCheckControlLayer )
+ {
+ // This should not be allowed, prevent it here.
+ // The dtor of the SwTextAttr does not delete the
+ // char, so delete it explicitly here.
+ if( SetAttrMode::NOTXTATRCHR & nInsMode )
+ {
+ // delete the char from the string
+ assert(CH_TXTATR_BREAKWORD == m_Text[pAttr->GetStart()]
+ || CH_TXTATR_INWORD == m_Text[pAttr->GetStart()]);
+ m_Text = m_Text.replaceAt(pAttr->GetStart(), 1, u"");
+ // Update SwIndexes
+ SwContentIndex aTmpIdx( this, pAttr->GetStart() );
+ Update(aTmpIdx, 1, UpdateMode::Negative);
+ }
+ // do not record deletion of Format!
+ ::sw::UndoGuard const ug(pDoc->GetIDocumentUndoRedo());
+ DestroyAttr( pAttr );
+ return false;
+ }
+ }
+ break;
+ }
+
+ case RES_TXTATR_FTN :
+ {
+ // Footnotes: create text node and put it into Inserts-section
+ SwDoc& rDoc = GetDoc();
+ SwNodes &rNodes = rDoc.GetNodes();
+
+ // check that footnote is inserted into body or redline section
+ bool bSplitFly = false;
+ if (StartOfSectionIndex() < rNodes.GetEndOfAutotext().GetIndex()
+ && StartOfSectionIndex() >= rNodes.GetEndOfInserts().GetIndex())
+ {
+ // This is a frame, header or footer. Check if it's a split frame.
+ SwFrameFormat* pFlyFormat = StartOfSectionNode()->GetFlyFormat();
+ bSplitFly = pFlyFormat && pFlyFormat->GetFlySplit().GetValue();
+ }
+
+ if (StartOfSectionIndex() < rNodes.GetEndOfAutotext().GetIndex() && !bSplitFly)
+ {
+ // This should not be allowed, prevent it here.
+ // The dtor of the SwTextAttr does not delete the
+ // char, so delete it explicitly here.
+ if( SetAttrMode::NOTXTATRCHR & nInsMode )
+ {
+ // delete the char from the string
+ assert(CH_TXTATR_BREAKWORD == m_Text[pAttr->GetStart()]
+ || CH_TXTATR_INWORD == m_Text[pAttr->GetStart()]);
+ m_Text = m_Text.replaceAt(pAttr->GetStart(), 1, u"");
+ // Update SwIndexes
+ SwContentIndex aTmpIdx( this, pAttr->GetStart() );
+ Update(aTmpIdx, 1, UpdateMode::Negative);
+ }
+ DestroyAttr( pAttr );
+ return false;
+ }
+
+ // is a new footnote being inserted?
+ bool bNewFootnote = nullptr == static_cast<SwTextFootnote*>(pAttr)->GetStartNode();
+ if( bNewFootnote )
+ {
+ static_cast<SwTextFootnote*>(pAttr)->MakeNewTextSection( GetNodes() );
+ SwRegHistory* pHist = GetpSwpHints()
+ ? GetpSwpHints()->GetHistory() : nullptr;
+ if( pHist )
+ pHist->ChangeNodeIndex( GetIndex() );
+ }
+ else if ( !GetpSwpHints() || !GetpSwpHints()->IsInSplitNode() )
+ {
+ // existing footnote: delete all layout frames of its
+ // footnote section
+ SwNodeOffset nSttIdx =
+ static_cast<SwTextFootnote*>(pAttr)->GetStartNode()->GetIndex();
+ SwNodeOffset nEndIdx = rNodes[ nSttIdx++ ]->EndOfSectionIndex();
+ for( ; nSttIdx < nEndIdx; ++nSttIdx )
+ {
+ SwContentNode* pCNd = rNodes[ nSttIdx ]->GetContentNode();
+ if( nullptr != pCNd )
+ pCNd->DelFrames(nullptr);
+ else if (SwTableNode *const pTable = rNodes[nSttIdx]->GetTableNode())
+ {
+ pTable->DelFrames();
+ }
+ }
+ }
+
+ if( !(SetAttrMode::NOTXTATRCHR & nInsMode) )
+ {
+ // must insert first, to prevent identical indexes
+ // that could later prevent insertion into SwDoc's
+ // footnote array
+ SwContentIndex aNdIdx( this, pAttr->GetStart() );
+ const OUString c(GetCharOfTextAttr(*pAttr));
+ OUString const ins( InsertText(c, aNdIdx, nInsertFlags) );
+ if (ins.isEmpty())
+ {
+ DestroyAttr(pAttr);
+ return false; // text node full :(
+ }
+ nInsMode |= SetAttrMode::NOTXTATRCHR;
+ }
+
+ // insert into SwDoc's footnote index array
+ SwTextFootnote* pTextFootnote = nullptr;
+ if( !bNewFootnote )
+ {
+ // moving an existing footnote (e.g. SplitNode)
+ for( size_t n = 0; n < rDoc.GetFootnoteIdxs().size(); ++n )
+ if( pAttr == rDoc.GetFootnoteIdxs()[n] )
+ {
+ // assign new index by removing and re-inserting
+ pTextFootnote = rDoc.GetFootnoteIdxs()[n];
+ rDoc.GetFootnoteIdxs().erase( rDoc.GetFootnoteIdxs().begin() + n );
+ break;
+ }
+ // if the Undo set the StartNode, the Index isn't
+ // in the doc's array yet!
+ }
+ if( !pTextFootnote )
+ pTextFootnote = static_cast<SwTextFootnote*>(pAttr);
+
+ // to update the numbers and for sorting, the Node must be set
+ static_cast<SwTextFootnote*>(pAttr)->ChgTextNode( this );
+
+ // do not insert footnote in redline section into footnote array
+ if (StartOfSectionIndex() > rNodes.GetEndOfRedlines().GetIndex() || bSplitFly)
+ {
+ const bool bSuccess = rDoc.GetFootnoteIdxs().insert(pTextFootnote).second;
+ OSL_ENSURE( bSuccess, "FootnoteIdx not inserted." );
+ }
+ rDoc.GetFootnoteIdxs().UpdateFootnote( *this );
+ static_cast<SwTextFootnote*>(pAttr)->SetSeqRefNo();
+ }
+ break;
+
+ case RES_TXTATR_FIELD:
+ {
+ // trigger notification for relevant fields, like HiddenParaFields
+ if (GetDoc().FieldCanHideParaWeight(
+ pAttr->GetFormatField().GetField()->GetTyp()->Which()))
+ {
+ bHiddenPara = true;
+ }
+ }
+ break;
+ case RES_TXTATR_LINEBREAK :
+ {
+ static_cast<SwTextLineBreak*>(pAttr)->SetTextNode(this);
+ }
+ break;
+
+ }
+ // CH_TXTATR_* are inserted for SwTextHints without EndIndex
+ // If the caller is SwTextNode::Copy, the char has already been copied,
+ // and SETATTR_NOTXTATRCHR prevents inserting it again here.
+ if( !(SetAttrMode::NOTXTATRCHR & nInsMode) )
+ {
+ SwContentIndex aIdx( this, pAttr->GetStart() );
+ OUString const ins( InsertText(OUString(GetCharOfTextAttr(*pAttr)),
+ aIdx, nInsertFlags) );
+ if (ins.isEmpty())
+ {
+ DestroyAttr(pAttr);
+ return false; // text node full :(
+ }
+
+ // adjust end of hint to account for inserted CH_TXTATR
+ const sal_Int32* pEnd(pAttr->GetEnd());
+ if (pEnd)
+ {
+ pAttr->SetEnd(*pEnd + 1);
+ }
+
+ if (pAttr->Which() == RES_TXTATR_CONTENTCONTROL)
+ {
+ // Content controls have a dummy character at their end as well.
+ SwContentIndex aEndIdx(this, *pAttr->GetEnd());
+ OUString aEnd
+ = InsertText(OUString(GetCharOfTextAttr(*pAttr)), aEndIdx, nInsertFlags);
+ if (aEnd.isEmpty())
+ {
+ DestroyAttr(pAttr);
+ return false;
+ }
+
+ pEnd = pAttr->GetEnd();
+ if (pEnd)
+ {
+ pAttr->SetEnd(*pEnd + 1);
+ }
+ }
+ }
+ }
+
+ // handle attributes which provide content
+ sal_Int32 nEnd = nStart;
+ bool bInputFieldStartCharInserted = false;
+ bool bInputFieldEndCharInserted = false;
+ const bool bHasContent( pAttr->HasContent() );
+ if ( bHasContent )
+ {
+ switch( pAttr->Which() )
+ {
+ case RES_TXTATR_INPUTFIELD:
+ {
+ SwTextInputField* pTextInputField = dynamic_cast<SwTextInputField*>(pAttr);
+ if ( pTextInputField )
+ {
+ if( !(SetAttrMode::NOTXTATRCHR & nMode) )
+ {
+ SwContentIndex aIdx( this, pAttr->GetStart() );
+ const OUString aContent = OUStringChar(CH_TXT_ATR_INPUTFIELDSTART)
+ + pTextInputField->GetFieldContent() + OUStringChar(CH_TXT_ATR_INPUTFIELDEND);
+ InsertText( aContent, aIdx, nInsertFlags );
+
+ const sal_Int32* const pEnd(pAttr->GetEnd());
+ assert(pEnd != nullptr);
+ pAttr->SetEnd(*pEnd + aContent.getLength());
+ nEnd = *pAttr->GetEnd();
+ }
+ else
+ {
+ // assure that CH_TXT_ATR_INPUTFIELDSTART and CH_TXT_ATR_INPUTFIELDEND are inserted.
+ if ( m_Text[ pAttr->GetStart() ] != CH_TXT_ATR_INPUTFIELDSTART )
+ {
+ SwContentIndex aIdx( this, pAttr->GetStart() );
+ InsertText( OUString(CH_TXT_ATR_INPUTFIELDSTART), aIdx, nInsertFlags );
+ bInputFieldStartCharInserted = true;
+ const sal_Int32* const pEnd(pAttr->GetEnd());
+ assert(pEnd != nullptr);
+ pAttr->SetEnd(*pEnd + 1);
+ nEnd = *pAttr->GetEnd();
+ }
+
+ const sal_Int32* const pEnd(pAttr->GetEnd());
+ assert(pEnd != nullptr);
+ if (m_Text[ *pEnd - 1 ] != CH_TXT_ATR_INPUTFIELDEND)
+ {
+ SwContentIndex aIdx( this, *pEnd );
+ InsertText( OUString(CH_TXT_ATR_INPUTFIELDEND), aIdx, nInsertFlags );
+ bInputFieldEndCharInserted = true;
+ pAttr->SetEnd(*pEnd + 1);
+ nEnd = *pAttr->GetEnd();
+ }
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ GetOrCreateSwpHints();
+
+ // handle overlap with an existing InputField
+ bool bInsertHint = true;
+ {
+ const SwTextInputField* pTextInputField = GetOverlappingInputField( *pAttr );
+ if ( pTextInputField != nullptr )
+ {
+ if ( pAttr->End() == nullptr )
+ {
+ bInsertHint = false;
+ DestroyAttr(pAttr);
+ }
+ else
+ {
+ if ( pAttr->GetStart() > pTextInputField->GetStart() )
+ {
+ pAttr->SetStart( pTextInputField->GetStart() );
+ }
+ if ( *(pAttr->End()) < *(pTextInputField->End()) )
+ {
+ pAttr->SetEnd(*(pTextInputField->End()));
+ }
+ }
+ }
+ }
+
+ if (bInsertHint)
+ {
+ // Handle the invariant that a plain text content control has the same character formatting
+ // for all of its content.
+ auto* pTextContentControl = static_txtattr_cast<SwTextContentControl*>(
+ GetTextAttrAt(pAttr->GetStart(), RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent));
+ if (pTextContentControl)
+ {
+ auto& rFormatContentControl
+ = static_cast<SwFormatContentControl&>(pTextContentControl->GetAttr());
+ std::shared_ptr<SwContentControl> pContentControl
+ = rFormatContentControl.GetContentControl();
+ if (pAttr->End() != nullptr && pContentControl->GetPlainText())
+ {
+ if (pAttr->GetStart() > pTextContentControl->GetStart())
+ {
+ pAttr->SetStart(pTextContentControl->GetStart());
+ }
+ if (*pAttr->End() < *pTextContentControl->End())
+ {
+ pAttr->SetEnd(*pTextContentControl->End());
+ }
+ }
+ }
+ }
+
+ const bool bRet = bInsertHint
+ && m_pSwpHints->TryInsertHint( pAttr, *this, nMode );
+
+ if ( !bRet )
+ {
+ if ( bDummyChar
+ && !(SetAttrMode::NOTXTATRCHR & nMode) )
+ {
+ // undo insertion of dummy character
+ // N.B. cannot insert the dummy character after inserting the hint,
+ // because if the hint has no extent it will be moved in InsertText,
+ // resulting in infinite recursion
+ assert((CH_TXTATR_BREAKWORD == m_Text[nStart] ||
+ CH_TXTATR_INWORD == m_Text[nStart] ));
+ SwContentIndex aIdx( this, nStart );
+ EraseText( aIdx, 1 );
+ }
+
+ if ( bHasContent )
+ {
+ if ( !(SetAttrMode::NOTXTATRCHR & nMode)
+ && (nEnd - nStart) > 0 )
+ {
+ SwContentIndex aIdx( this, nStart );
+ EraseText( aIdx, (nEnd - nStart) );
+ }
+ else
+ {
+ if ( bInputFieldEndCharInserted
+ && (nEnd - nStart) > 0 )
+ {
+ SwContentIndex aIdx( this, nEnd - 1 );
+ EraseText( aIdx, 1 );
+ }
+
+ if ( bInputFieldStartCharInserted )
+ {
+ SwContentIndex aIdx( this, nStart );
+ EraseText( aIdx, 1 );
+ }
+ }
+ }
+ }
+
+ if ( bHiddenPara )
+ {
+ SetCalcHiddenParaField();
+ }
+
+ return bRet;
+}
+
+void SwTextNode::DeleteAttribute( SwTextAttr * const pAttr )
+{
+ if ( !HasHints() )
+ {
+ OSL_FAIL("DeleteAttribute called, but text node without hints?");
+ return;
+ }
+
+ if ( pAttr->HasDummyChar() )
+ {
+ // copy index!
+ const SwContentIndex aIdx( this, pAttr->GetStart() );
+ // erase the CH_TXTATR, which will also delete pAttr
+ EraseText( aIdx, 1 );
+ }
+ else if ( pAttr->HasContent() )
+ {
+ const SwContentIndex aIdx( this, pAttr->GetStart() );
+ assert(pAttr->End() != nullptr);
+ EraseText( aIdx, *pAttr->End() - pAttr->GetStart() );
+ }
+ else
+ {
+ // create MsgHint before start/end become invalid
+ SwUpdateAttr aHint(
+ pAttr->GetStart(),
+ *pAttr->GetEnd(),
+ pAttr->Which());
+
+ m_pSwpHints->Delete( pAttr );
+ SwTextAttr::Destroy( pAttr, GetDoc().GetAttrPool() );
+ CallSwClientNotify(sw::LegacyModifyHint(nullptr, &aHint));
+
+ TryDeleteSwpHints();
+ }
+}
+
+//FIXME: this does NOT respect SORT NUMBER (for CHARFMT)!
+void SwTextNode::DeleteAttributes(
+ const sal_uInt16 nWhich,
+ const sal_Int32 nStart,
+ const sal_Int32 nEnd )
+{
+ if ( !HasHints() )
+ return;
+
+ for ( size_t nPos = 0; m_pSwpHints && nPos < m_pSwpHints->Count(); ++nPos )
+ {
+ SwTextAttr * const pTextHt = m_pSwpHints->Get( nPos );
+ const sal_Int32 nHintStart = pTextHt->GetStart();
+ if (nStart < nHintStart)
+ {
+ break; // sorted by start
+ }
+ else if ( (nStart == nHintStart) && (nWhich == pTextHt->Which()) )
+ {
+ if ( nWhich == RES_CHRATR_HIDDEN )
+ {
+ assert(!"hey, that's a CHRATR! how did that get in?");
+ SetCalcHiddenCharFlags();
+ }
+ else if ( nWhich == RES_TXTATR_CHARFMT )
+ {
+ // Check if character format contains hidden attribute:
+ const SwCharFormat* pFormat = pTextHt->GetCharFormat().GetCharFormat();
+ if ( SfxItemState::SET == pFormat->GetItemState( RES_CHRATR_HIDDEN ) )
+ SetCalcHiddenCharFlags();
+ }
+ // #i75430# Recalc hidden flags if necessary
+ else if ( nWhich == RES_TXTATR_AUTOFMT )
+ {
+ // Check if auto style contains hidden attribute:
+ const SfxPoolItem* pHiddenItem = CharFormat::GetItem( *pTextHt, RES_CHRATR_HIDDEN );
+ if ( pHiddenItem )
+ SetCalcHiddenCharFlags();
+ // for auto styles DeleteAttributes is only called from Undo
+ // so it shouldn't need to care about ignore start/end flags
+ }
+
+ sal_Int32 const * const pEndIdx = pTextHt->GetEnd();
+
+ if ( pTextHt->HasDummyChar() )
+ {
+ // copy index!
+ const SwContentIndex aIdx( this, nStart );
+ // erase the CH_TXTATR, which will also delete pTextHt
+ EraseText( aIdx, 1 );
+ }
+ else if ( pTextHt->HasContent() )
+ {
+ const SwContentIndex aIdx( this, nStart );
+ OSL_ENSURE( pTextHt->End() != nullptr, "<SwTextNode::DeleteAttributes(..)> - missing End() at <SwTextAttr> instance which has content" );
+ EraseText( aIdx, *pTextHt->End() - nStart );
+ }
+ else if( *pEndIdx == nEnd )
+ {
+ // Create MsgHint before Start and End are gone.
+ // For HiddenParaFields it's not necessary to call
+ // SetCalcHiddenParaField because the dtor does that.
+ SwUpdateAttr aHint(
+ nStart,
+ *pEndIdx,
+ nWhich);
+
+ m_pSwpHints->DeleteAtPos( nPos );
+ SwTextAttr::Destroy( pTextHt, GetDoc().GetAttrPool() );
+ CallSwClientNotify(sw::LegacyModifyHint(nullptr, &aHint));
+ }
+ }
+ }
+ TryDeleteSwpHints();
+}
+
+void SwTextNode::DelSoftHyph( const sal_Int32 nStt, const sal_Int32 nEnd )
+{
+ sal_Int32 nFndPos = nStt;
+ sal_Int32 nEndPos = nEnd;
+ for (;;)
+ {
+ nFndPos = m_Text.indexOf(CHAR_SOFTHYPHEN, nFndPos);
+ if (nFndPos<0 || nFndPos>=nEndPos )
+ {
+ break;
+ }
+ const SwContentIndex aIdx( this, nFndPos );
+ EraseText( aIdx, 1 );
+ --nEndPos;
+ }
+}
+
+bool SwTextNode::IsIgnoredCharFormatForNumbering(const sal_uInt16 nWhich, bool bIsCharStyle)
+{
+ // LO can save the char background as either shading or highlight, so check which mode is currently chosen.
+ // Shading does not affect the numbering. Highlighting does (but isn't allowed in a char style).
+ if (nWhich == RES_CHRATR_BACKGROUND)
+ return bIsCharStyle || SvtFilterOptions::Get().IsCharBackground2Shading();
+
+ return (nWhich == RES_CHRATR_UNDERLINE
+ || nWhich == RES_CHRATR_ESCAPEMENT);
+}
+
+// Set these attributes on SwTextNode. If they apply to the entire paragraph
+// text, set them in the SwTextNode's item set (SwContentNode::SetAttr).
+bool SwTextNode::SetAttr(
+ const SfxItemSet& rSet,
+ const sal_Int32 nStt,
+ const sal_Int32 nEnd,
+ const SetAttrMode nMode,
+ SwTextAttr **ppNewTextAttr )
+{
+ if( !rSet.Count() )
+ return false;
+
+ // split sets (for selection in nodes)
+ const SfxItemSet* pSet = &rSet;
+ SfxItemSetFixed<RES_TXTATR_BEGIN, RES_TXTATR_END-1> aTextSet( *rSet.GetPool() );
+
+ // entire paragraph
+ if ( !nStt && (nEnd == m_Text.getLength()) &&
+ !(nMode & SetAttrMode::NOFORMATATTR ) )
+ {
+ // if the node already has CharFormat hints, the new attributes must
+ // be set as hints too to override those.
+ bool bHasCharFormats = false;
+ if ( HasHints() )
+ {
+ for ( size_t n = 0; n < m_pSwpHints->Count(); ++n )
+ {
+ if ( m_pSwpHints->Get( n )->IsCharFormatAttr() )
+ {
+ bHasCharFormats = true;
+ break;
+ }
+ }
+ }
+
+ if( !bHasCharFormats )
+ {
+ aTextSet.Put( rSet );
+ // If there are any character attributes in rSet,
+ // we want to set them at the paragraph:
+ if( aTextSet.Count() != rSet.Count() )
+ {
+ const bool bRet = SetAttr( rSet );
+ if( !aTextSet.Count() )
+ return bRet;
+ }
+
+ // check for auto style:
+ if ( const SwFormatAutoFormat* pItem = aTextSet.GetItemIfSet( RES_TXTATR_AUTOFMT, false ) )
+ {
+ const bool bRet = SetAttr( *pItem->GetStyleHandle() );
+ if( 1 == aTextSet.Count() )
+ return bRet;
+ }
+
+ // Continue with the text attributes:
+ pSet = &aTextSet;
+ }
+ }
+
+ GetOrCreateSwpHints();
+
+ SfxItemSet aCharSet( *rSet.GetPool(), aCharAutoFormatSetRange );
+
+ size_t nCount = 0;
+ SfxItemIter aIter( *pSet );
+ const SfxPoolItem* pItem = aIter.GetCurItem();
+
+ do
+ {
+ if (!IsInvalidItem(pItem))
+ {
+ const sal_uInt16 nWhich = pItem->Which();
+ OSL_ENSURE( isCHRATR(nWhich) || isTXTATR(nWhich),
+ "SwTextNode::SetAttr(): unknown attribute" );
+ if ( isCHRATR(nWhich) || isTXTATR(nWhich) )
+ {
+ if ((RES_TXTATR_CHARFMT == nWhich) &&
+ (GetDoc().GetDfltCharFormat() ==
+ static_cast<const SwFormatCharFormat*>(pItem)->GetCharFormat()))
+ {
+ RstTextAttr( nStt, nEnd - nStt, RES_TXTATR_CHARFMT );
+ DontExpandFormat( nStt );
+ }
+ else
+ {
+ if (isCHRATR(nWhich) ||
+ (RES_TXTATR_UNKNOWN_CONTAINER == nWhich))
+ {
+ aCharSet.Put( *pItem );
+ }
+ else
+ {
+
+ SwTextAttr *const pNew = MakeTextAttr( GetDoc(),
+ const_cast<SfxPoolItem&>(*pItem), nStt, nEnd );
+ if ( pNew )
+ {
+ // store the first one we create into the pp
+ if (ppNewTextAttr && !*ppNewTextAttr)
+ *ppNewTextAttr = pNew;
+ if ( nEnd != nStt && !pNew->GetEnd() )
+ {
+ OSL_FAIL("Attribute without end, but area marked");
+ DestroyAttr( pNew ); // do not insert
+ }
+ else if ( InsertHint( pNew, nMode ) )
+ {
+ ++nCount;
+ }
+ }
+ }
+ }
+ }
+ }
+ pItem = aIter.NextItem();
+ } while(pItem);
+
+ if ( aCharSet.Count() )
+ {
+ SwTextAttr* pTmpNew = MakeTextAttr( GetDoc(), aCharSet, nStt, nEnd );
+ if ( InsertHint( pTmpNew, nMode ) )
+ {
+ ++nCount;
+ }
+ }
+
+ TryDeleteSwpHints();
+
+ return nCount != 0;
+}
+
+static void lcl_MergeAttr( SfxItemSet& rSet, const SfxPoolItem& rAttr )
+{
+ if ( RES_TXTATR_AUTOFMT == rAttr.Which() )
+ {
+ const SfxItemSet* pCFSet = CharFormat::GetItemSet( rAttr );
+ if ( !pCFSet )
+ return;
+ SfxWhichIter aIter( *pCFSet );
+ sal_uInt16 nWhich = aIter.FirstWhich();
+ while( nWhich )
+ {
+ const SfxPoolItem* pItem = nullptr;
+ if( ( nWhich < RES_CHRATR_END ||
+ RES_TXTATR_UNKNOWN_CONTAINER == nWhich ) &&
+ ( SfxItemState::SET == aIter.GetItemState( true, &pItem ) ) )
+ rSet.Put( *pItem );
+ nWhich = aIter.NextWhich();
+ }
+ }
+ else
+ rSet.Put( rAttr );
+}
+
+static void lcl_MergeAttr_ExpandChrFormat( SfxItemSet& rSet, const SfxPoolItem& rAttr )
+{
+ if( RES_TXTATR_CHARFMT == rAttr.Which() ||
+ RES_TXTATR_INETFMT == rAttr.Which() ||
+ RES_TXTATR_AUTOFMT == rAttr.Which() )
+ {
+ const SfxItemSet* pCFSet = CharFormat::GetItemSet( rAttr );
+
+ if ( pCFSet )
+ {
+ SfxWhichIter aIter( *pCFSet );
+ sal_uInt16 nWhich = aIter.FirstWhich();
+ while( nWhich )
+ {
+ const SfxPoolItem* pItem = nullptr;
+ if( ( nWhich < RES_CHRATR_END ||
+ ( RES_TXTATR_AUTOFMT == rAttr.Which() && RES_TXTATR_UNKNOWN_CONTAINER == nWhich ) ) &&
+ ( SfxItemState::SET == aIter.GetItemState( true, &pItem ) ) )
+ rSet.Put( *pItem );
+ nWhich = aIter.NextWhich();
+ }
+ }
+ }
+
+/* If multiple attributes overlap, the last one wins!
+ Probably this can only happen between a RES_TXTATR_INETFMT and one of the
+ other hints, because BuildPortions ensures that CHARFMT/AUTOFMT don't
+ overlap. But there may be multiple CHARFMT/AUTOFMT with exactly the same
+ start/end, sorted by BuildPortions, in which case the same logic applies.
+
+ 1234567890123456789
+ |------------| Font1
+ |------| Font2
+ ^ ^
+ |--| query range: -> Font2
+*/
+ // merge into set
+ rSet.Put( rAttr );
+}
+
+namespace {
+
+struct SwPoolItemEndPair
+{
+public:
+ const SfxPoolItem* mpItem;
+ sal_Int32 mnEndPos;
+
+ SwPoolItemEndPair() : mpItem( nullptr ), mnEndPos( 0 ) {};
+};
+
+}
+
+static void lcl_MergeListLevelIndentAsLRSpaceItem( const SwTextNode& rTextNode,
+ SfxItemSet& rSet )
+{
+ ::sw::ListLevelIndents const indents(rTextNode.AreListLevelIndentsApplicable());
+ if (indents == ::sw::ListLevelIndents::No)
+ return;
+
+ const SwNumRule* pRule = rTextNode.GetNumRule();
+ if ( pRule && rTextNode.GetActualListLevel() >= 0 )
+ {
+ const SwNumFormat& rFormat = pRule->Get(o3tl::narrowing<sal_uInt16>(rTextNode.GetActualListLevel()));
+ if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ if (indents & ::sw::ListLevelIndents::FirstLine)
+ {
+ SvxFirstLineIndentItem const firstLine(static_cast<short>(rFormat.GetFirstLineIndent()), RES_MARGIN_FIRSTLINE);
+ rSet.Put(firstLine);
+ }
+ if (indents & ::sw::ListLevelIndents::LeftMargin)
+ {
+ SvxTextLeftMarginItem const leftMargin(rFormat.GetIndentAt(), RES_MARGIN_TEXTLEFT);
+ rSet.Put(leftMargin);
+ }
+ }
+ }
+}
+
+// request the attributes of the TextNode at the range
+bool SwTextNode::GetParaAttr(SfxItemSet& rSet, sal_Int32 nStt, sal_Int32 nEnd,
+ const bool bOnlyTextAttr, const bool bGetFromChrFormat,
+ const bool bMergeIndentValuesOfNumRule,
+ SwRootFrame const*const pLayout) const
+{
+ assert(!rSet.Count()); // handled inconsistently, typically an error?
+
+ if (pLayout && pLayout->HasMergedParas())
+ {
+ if (GetRedlineMergeFlag() == SwNode::Merge::Hidden)
+ {
+ return false; // ignore deleted node
+ }
+ }
+
+ // get the node's automatic attributes
+ SfxItemSet aFormatSet( *rSet.GetPool(), rSet.GetRanges() );
+ if (!bOnlyTextAttr)
+ {
+ SwTextNode const& rParaPropsNode(
+ sw::GetAttrMerged(aFormatSet, *this, pLayout));
+ if (bMergeIndentValuesOfNumRule)
+ {
+ lcl_MergeListLevelIndentAsLRSpaceItem(rParaPropsNode, aFormatSet);
+ }
+ }
+
+ if( HasHints() )
+ {
+ // First, check which text attributes are valid in the range.
+ // cases:
+ // Ambiguous, if
+ // * the attribute is wholly contained in the range
+ // * the attribute end is in the range
+ // * the attribute start is in the range
+ // Unambiguous (merge into set), if
+ // * the attribute wholly contains the range
+ // Ignored, if
+ // * the attribute is wholly outside the range
+
+ void (*fnMergeAttr)( SfxItemSet&, const SfxPoolItem& )
+ = bGetFromChrFormat ? &lcl_MergeAttr_ExpandChrFormat
+ : &lcl_MergeAttr;
+
+ const size_t nSize = m_pSwpHints->Count();
+
+ if (nStt == nEnd) // no range:
+ {
+ for (size_t n = 0; n < nSize; ++n)
+ {
+ const SwTextAttr* pHt = m_pSwpHints->Get(n);
+ const sal_Int32 nAttrStart = pHt->GetStart();
+ if (nAttrStart > nEnd) // behind the range
+ break;
+
+ const sal_Int32* pAttrEnd = pHt->End();
+ if ( ! pAttrEnd ) // no attributes without end
+ continue;
+
+ if( ( nAttrStart < nStt &&
+ ( pHt->DontExpand() ? nStt < *pAttrEnd
+ : nStt <= *pAttrEnd )) ||
+ ( nStt == nAttrStart &&
+ ( nAttrStart == *pAttrEnd || !nStt )))
+ (*fnMergeAttr)( rSet, pHt->GetAttr() );
+ }
+ }
+ else // a query range is defined
+ {
+ // #i75299#
+ std::optional< std::vector< SwPoolItemEndPair > > pAttrArr;
+
+ const size_t coArrSz = RES_TXTATR_WITHEND_END - RES_CHRATR_BEGIN;
+
+ for (size_t n = 0; n < nSize; ++n)
+ {
+ const SwTextAttr* pHt = m_pSwpHints->Get(n);
+ const sal_Int32 nAttrStart = pHt->GetStart();
+ if (nAttrStart > nEnd) // outside, behind
+ break;
+
+ const sal_Int32* pAttrEnd = pHt->End();
+ if ( ! pAttrEnd ) // no attributes without end
+ continue;
+
+ bool bChkInvalid = false;
+ if (nAttrStart <= nStt) // before or exactly Start
+ {
+ if (*pAttrEnd <= nStt) // outside, before
+ continue;
+
+ if (nEnd <= *pAttrEnd) // behind or exactly End
+ (*fnMergeAttr)( aFormatSet, pHt->GetAttr() );
+ else
+// else if( pHt->GetAttr() != aFormatSet.Get( pHt->Which() ) )
+ // ambiguous
+ bChkInvalid = true;
+ }
+ else if (nAttrStart < nEnd // starts in the range
+)// && pHt->GetAttr() != aFormatSet.Get( pHt->Which() ) )
+ bChkInvalid = true;
+
+ if( bChkInvalid )
+ {
+ // ambiguous?
+ std::optional< SfxItemIter > oItemIter;
+ const SfxPoolItem* pItem = nullptr;
+
+ if ( RES_TXTATR_AUTOFMT == pHt->Which() )
+ {
+ const SfxItemSet* pAutoSet = CharFormat::GetItemSet( pHt->GetAttr() );
+ if ( pAutoSet )
+ {
+ oItemIter.emplace( *pAutoSet );
+ pItem = oItemIter->GetCurItem();
+ }
+ }
+ else
+ pItem = &pHt->GetAttr();
+
+ const sal_Int32 nHintEnd = *pAttrEnd;
+
+ for (; pItem; pItem = oItemIter ? oItemIter->NextItem() : nullptr)
+ {
+ const sal_uInt16 nHintWhich = pItem->Which();
+ OSL_ENSURE(!isUNKNOWNATR(nHintWhich),
+ "SwTextNode::GetAttr(): unknown attribute?");
+
+ if (!pAttrArr)
+ {
+ pAttrArr = std::vector< SwPoolItemEndPair >(coArrSz);
+ }
+
+ std::vector< SwPoolItemEndPair >::iterator pPrev = pAttrArr->begin();
+ if (isCHRATR(nHintWhich) ||
+ isTXTATR_WITHEND(nHintWhich))
+ {
+ pPrev += nHintWhich - RES_CHRATR_BEGIN;
+ }
+ else
+ {
+ pPrev = pAttrArr->end();
+ }
+
+ if( pPrev != pAttrArr->end() )
+ {
+ if( !pPrev->mpItem )
+ {
+ if ( bOnlyTextAttr || *pItem != aFormatSet.Get( nHintWhich ) )
+ {
+ if( nAttrStart > nStt )
+ {
+ rSet.InvalidateItem( nHintWhich );
+ pPrev->mpItem = INVALID_POOL_ITEM;
+ }
+ else
+ {
+ pPrev->mpItem = pItem;
+ pPrev->mnEndPos = nHintEnd;
+ }
+ }
+ }
+ else if( !IsInvalidItem(pPrev->mpItem) )
+ {
+ if( pPrev->mnEndPos == nAttrStart &&
+ *pPrev->mpItem == *pItem )
+ {
+ pPrev->mpItem = pItem;
+ pPrev->mnEndPos = nHintEnd;
+ }
+ else
+ {
+ rSet.InvalidateItem( nHintWhich );
+ pPrev->mpItem = INVALID_POOL_ITEM;
+ }
+ }
+ }
+ } // end while
+ }
+ }
+
+ if (pAttrArr)
+ {
+ for (size_t n = 0; n < coArrSz; ++n)
+ {
+ const SwPoolItemEndPair& rItemPair = (*pAttrArr)[ n ];
+ if( rItemPair.mpItem && !IsInvalidItem(rItemPair.mpItem) )
+ {
+ const sal_uInt16 nWh =
+ o3tl::narrowing<sal_uInt16>(n + RES_CHRATR_BEGIN);
+
+ if (nEnd <= rItemPair.mnEndPos) // behind or exactly end
+ {
+ if( *rItemPair.mpItem != aFormatSet.Get( nWh ) )
+ (*fnMergeAttr)( rSet, *rItemPair.mpItem );
+ }
+ else
+ // ambiguous
+ rSet.InvalidateItem( nWh );
+ }
+ }
+ }
+ }
+ if( aFormatSet.Count() )
+ {
+ // remove all from the format-set that are also set in the text-set
+ aFormatSet.Differentiate( rSet );
+ }
+ }
+
+ if (aFormatSet.Count())
+ {
+ // now "merge" everything
+ rSet.Put( aFormatSet );
+ }
+
+ return rSet.Count() != 0;
+}
+
+namespace
+{
+
+typedef std::pair<sal_Int32, sal_Int32> AttrSpan_t;
+typedef std::multimap<AttrSpan_t, const SwTextAttr*> AttrSpanMap_t;
+
+struct IsAutoStyle
+{
+ bool
+ operator()(const AttrSpanMap_t::value_type& i_rAttrSpan)
+ const
+ {
+ return i_rAttrSpan.second && i_rAttrSpan.second->Which() == RES_TXTATR_AUTOFMT;
+ }
+};
+
+/** Removes from io_rAttrSet all items that are set by style on the
+ given span.
+ */
+struct RemovePresentAttrs
+{
+ explicit RemovePresentAttrs(SfxItemSet& io_rAttrSet)
+ : m_rAttrSet(io_rAttrSet)
+ {
+ }
+
+ void
+ operator()(const AttrSpanMap_t::value_type& i_rAttrSpan)
+ const
+ {
+ if (!i_rAttrSpan.second)
+ {
+ return;
+ }
+
+ const SwTextAttr* const pAutoStyle(i_rAttrSpan.second);
+ SfxItemIter aIter(m_rAttrSet);
+ for (const SfxPoolItem* pItem(aIter.GetCurItem()); pItem; pItem = aIter.NextItem())
+ {
+ const sal_uInt16 nWhich(pItem->Which());
+ if (CharFormat::IsItemIncluded(nWhich, pAutoStyle))
+ {
+ aIter.ClearItem();
+ }
+ }
+ }
+
+private:
+ SfxItemSet& m_rAttrSet;
+};
+
+/** Collects all style-covered spans from i_rHints to o_rSpanMap. In
+ addition inserts dummy spans with pointer to format equal to 0 for
+ all gaps (i.e. spans not covered by any style). This simplifies
+ creation of autostyles for all needed spans, but it means all code
+ that tries to access the pointer has to check if it's non-null!
+ */
+void
+lcl_CollectHintSpans(const SwpHints& i_rHints, const sal_Int32 nLength,
+ AttrSpanMap_t& o_rSpanMap)
+{
+ sal_Int32 nLastEnd(0);
+
+ for (size_t i = 0; i < i_rHints.Count(); ++i)
+ {
+ const SwTextAttr* pHint = i_rHints.Get(i);
+ const sal_uInt16 nWhich(pHint->Which());
+ if (nWhich == RES_TXTATR_CHARFMT || nWhich == RES_TXTATR_AUTOFMT)
+ {
+ const AttrSpan_t aSpan(pHint->GetStart(), *pHint->End());
+ o_rSpanMap.emplace(aSpan, pHint);
+
+ // < not != because there may be multiple CHARFMT at same range
+ if (nLastEnd < aSpan.first)
+ {
+ // insert dummy span covering the gap
+ o_rSpanMap.emplace( AttrSpan_t(nLastEnd, aSpan.first), nullptr );
+ }
+
+ nLastEnd = aSpan.second;
+ }
+ }
+
+ // no hints at the end (special case: no hints at all in i_rHints)
+ if (nLastEnd != nLength && nLength != 0)
+ {
+ o_rSpanMap.emplace(AttrSpan_t(nLastEnd, nLength), nullptr);
+ }
+}
+
+void
+lcl_FillWhichIds(const SfxItemSet& i_rAttrSet, std::vector<sal_uInt16>& o_rClearIds)
+{
+ o_rClearIds.reserve(i_rAttrSet.Count());
+ SfxItemIter aIter(i_rAttrSet);
+ for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem())
+ {
+ o_rClearIds.push_back(pItem->Which());
+ }
+}
+
+struct SfxItemSetClearer
+{
+ SfxItemSet & m_rItemSet;
+ explicit SfxItemSetClearer(SfxItemSet & rItemSet) : m_rItemSet(rItemSet) { }
+ void operator()(sal_uInt16 const nWhich) { m_rItemSet.ClearItem(nWhich); }
+};
+
+}
+
+/** Does the hard work of SwTextNode::FormatToTextAttr: the real conversion
+ of items to automatic styles.
+ */
+void
+SwTextNode::impl_FormatToTextAttr(const SfxItemSet& i_rAttrSet)
+{
+ typedef AttrSpanMap_t::iterator AttrSpanMap_iterator_t;
+ AttrSpanMap_t aAttrSpanMap;
+
+ if (i_rAttrSet.Count() == 0)
+ {
+ return;
+ }
+
+ // 1. Identify all spans in hints' array
+
+ lcl_CollectHintSpans(*m_pSwpHints, m_Text.getLength(), aAttrSpanMap);
+
+ // 2. Go through all spans and insert new attrs
+
+ AttrSpanMap_iterator_t aCurRange(aAttrSpanMap.begin());
+ const AttrSpanMap_iterator_t aEnd(aAttrSpanMap.end());
+ while (aCurRange != aEnd)
+ {
+ typedef std::pair<AttrSpanMap_iterator_t, AttrSpanMap_iterator_t>
+ AttrSpanMapRange_t;
+ AttrSpanMapRange_t aRange(aAttrSpanMap.equal_range(aCurRange->first));
+
+ // 2a. Collect attributes to insert
+
+ SfxItemSet aCurSet(i_rAttrSet);
+ std::for_each(aRange.first, aRange.second, RemovePresentAttrs(aCurSet));
+
+ // 2b. Insert automatic style containing the collected attributes
+
+ if (aCurSet.Count() != 0)
+ {
+ AttrSpanMap_iterator_t aAutoStyleIt(
+ std::find_if(aRange.first, aRange.second, IsAutoStyle()));
+ if (aAutoStyleIt != aRange.second)
+ {
+ // there already is an automatic style on that span:
+ // create new one and remove the original one
+ SwTextAttr* const pAutoStyle(const_cast<SwTextAttr*>(aAutoStyleIt->second));
+ const std::shared_ptr<SfxItemSet> pOldStyle(
+ static_cast<const SwFormatAutoFormat&>(
+ pAutoStyle->GetAttr()).GetStyleHandle());
+ aCurSet.Put(*pOldStyle);
+
+ // remove the old hint
+ m_pSwpHints->Delete(pAutoStyle);
+ DestroyAttr(pAutoStyle);
+ }
+ m_pSwpHints->Insert(
+ MakeTextAttr(GetDoc(), aCurSet,
+ aCurRange->first.first, aCurRange->first.second));
+ }
+
+ aCurRange = aRange.second;
+ }
+
+ // hints were directly inserted, so need to fix the Ignore flags now
+ m_pSwpHints->MergePortions(*this);
+
+ // 3. Clear items from the node
+ std::vector<sal_uInt16> aClearedIds;
+ lcl_FillWhichIds(i_rAttrSet, aClearedIds);
+ ClearItemsFromAttrSet(aClearedIds);
+}
+
+void SwTextNode::FormatToTextAttr( SwTextNode* pNd )
+{
+ SfxItemSet aThisSet( GetDoc().GetAttrPool(), aCharFormatSetRange );
+ if( HasSwAttrSet() && GetpSwAttrSet()->Count() )
+ aThisSet.Put( *GetpSwAttrSet() );
+
+ GetOrCreateSwpHints();
+
+ if( pNd == this )
+ {
+ impl_FormatToTextAttr(aThisSet);
+ }
+ else
+ {
+ // There are five possible combinations of items from this and
+ // pNd (pNd is the 'main' node):
+
+ // case pNd this action
+
+ // 1 - - do nothing
+ // 2 - a convert item to attr of this
+ // 3 a - convert item to attr of pNd
+ // 4 a a clear item in this
+ // 5 a b convert item to attr of this
+
+ SfxItemSet aNdSet( pNd->GetDoc().GetAttrPool(), aCharFormatSetRange );
+ if( pNd->HasSwAttrSet() && pNd->GetpSwAttrSet()->Count() )
+ aNdSet.Put( *pNd->GetpSwAttrSet() );
+
+ pNd->GetOrCreateSwpHints();
+
+ std::vector<sal_uInt16> aProcessedIds;
+
+ if( aThisSet.Count() )
+ {
+ SfxItemIter aIter( aThisSet );
+ const SfxPoolItem* pItem = aIter.GetCurItem(), *pNdItem = nullptr;
+ SfxItemSet aConvertSet( GetDoc().GetAttrPool(), aCharFormatSetRange );
+ std::vector<sal_uInt16> aClearWhichIds;
+
+ do
+ {
+ if( SfxItemState::SET == aNdSet.GetItemState( pItem->Which(), false, &pNdItem ) )
+ {
+ if (*pItem == *pNdItem) // 4
+ {
+ aClearWhichIds.push_back( pItem->Which() );
+ }
+ else // 5
+ {
+ aConvertSet.Put(*pItem);
+ }
+ aProcessedIds.push_back(pItem->Which());
+ }
+ else // 2
+ {
+ aConvertSet.Put(*pItem);
+ }
+
+ pItem = aIter.NextItem();
+ } while (pItem);
+
+ // 4/ clear items of this that are set with the same value on pNd
+ ClearItemsFromAttrSet( aClearWhichIds );
+
+ // 2, 5/ convert all other items to attrs
+ impl_FormatToTextAttr(aConvertSet);
+ }
+
+ {
+ std::for_each(aProcessedIds.begin(), aProcessedIds.end(),
+ SfxItemSetClearer(aNdSet));
+
+ // 3/ convert items to attrs
+ pNd->impl_FormatToTextAttr(aNdSet);
+
+ if( aNdSet.Count() )
+ {
+ SwFormatChg aTmp1( pNd->GetFormatColl() );
+ pNd->CallSwClientNotify(sw::LegacyModifyHint(&aTmp1, &aTmp1));
+ }
+ }
+ }
+
+ SetCalcHiddenCharFlags();
+
+ pNd->TryDeleteSwpHints();
+}
+
+void SwpHints::CalcFlags()
+{
+ m_bDDEFields = m_bFootnote = false;
+ const size_t nSize = Count();
+ for( size_t nPos = 0; nPos < nSize; ++nPos )
+ {
+ const SwTextAttr* pAttr = Get( nPos );
+ switch( pAttr->Which() )
+ {
+ case RES_TXTATR_FTN:
+ m_bFootnote = true;
+ if ( m_bDDEFields )
+ return;
+ break;
+ case RES_TXTATR_FIELD:
+ {
+ const SwField* pField = pAttr->GetFormatField().GetField();
+ if( SwFieldIds::Dde == pField->GetTyp()->Which() )
+ {
+ m_bDDEFields = true;
+ if ( m_bFootnote )
+ return;
+ }
+ }
+ break;
+ }
+ }
+}
+
+bool SwpHints::CalcHiddenParaField() const
+{
+ m_bCalcHiddenParaField = false;
+ const bool bOldHiddenByParaField = m_bHiddenByParaField;
+ bool bNewHiddenByParaField = false;
+ int nNewResultWeight = 0;
+ const size_t nSize = Count();
+ const SwTextAttr* pTextHt;
+
+ for (size_t nPos = 0; nPos < nSize; ++nPos)
+ {
+ pTextHt = Get(nPos);
+ const sal_uInt16 nWhich = pTextHt->Which();
+
+ if (RES_TXTATR_FIELD == nWhich)
+ {
+ // see also SwTextFrame::IsHiddenNow()
+ const SwFormatField& rField = pTextHt->GetFormatField();
+ int nCurWeight = m_rParent.GetDoc().FieldCanHideParaWeight(rField.GetField()->GetTyp()->Which());
+ if (nCurWeight > nNewResultWeight)
+ {
+ nNewResultWeight = nCurWeight;
+ bNewHiddenByParaField = m_rParent.GetDoc().FieldHidesPara(*rField.GetField());
+ }
+ else if (nCurWeight == nNewResultWeight && bNewHiddenByParaField)
+ {
+ // Currently, for both supported hiding types (HiddenPara, Database), "Don't hide"
+ // takes precedence - i.e., if there's a "Don't hide" field of that weight, we only
+ // care about fields of higher weight.
+ bNewHiddenByParaField = m_rParent.GetDoc().FieldHidesPara(*rField.GetField());
+ }
+ }
+ }
+ SetHiddenByParaField(bNewHiddenByParaField);
+ return bOldHiddenByParaField != bNewHiddenByParaField;
+}
+
+void SwpHints::NoteInHistory( SwTextAttr *pAttr, const bool bNew )
+{
+ if ( m_pHistory ) { m_pHistory->AddHint( pAttr, bNew ); }
+}
+
+namespace {
+struct Portion {
+ SwTextAttr* pTextAttr;
+ sal_Int32 nKey;
+ bool isRsidOnlyAutoFormat;
+};
+typedef std::vector< Portion > PortionMap;
+enum MergeResult { MATCH, DIFFER_ONLY_RSID, DIFFER };
+}
+
+static MergeResult lcl_Compare_Attributes(
+ int i, int j,
+ const std::pair< PortionMap::const_iterator, PortionMap::const_iterator >& aRange1,
+ const std::pair< PortionMap::const_iterator, PortionMap::const_iterator >& aRange2,
+ std::vector<bool>& RsidOnlyAutoFormatFlagMap);
+
+bool SwpHints::MergePortions( SwTextNode& rNode )
+{
+ if ( !Count() )
+ return false;
+
+ // sort before merging
+ Resort();
+
+ bool bRet = false;
+ PortionMap aPortionMap;
+ aPortionMap.reserve(Count() + 1);
+ std::vector<bool> RsidOnlyAutoFormatFlagMap;
+ RsidOnlyAutoFormatFlagMap.resize(Count() + 1);
+ sal_Int32 nLastPorStart = COMPLETE_STRING;
+ sal_Int32 nKey = 0;
+
+ // get portions by start position:
+ for ( size_t i = 0; i < Count(); ++i )
+ {
+ SwTextAttr *pHt = Get( i );
+ if ( RES_TXTATR_CHARFMT != pHt->Which() &&
+ RES_TXTATR_AUTOFMT != pHt->Which() )
+ //&&
+ //RES_TXTATR_INETFMT != pHt->Which() )
+ continue;
+
+ bool isRsidOnlyAutoFormat(false);
+ // check for RSID-only AUTOFMT
+ if (RES_TXTATR_AUTOFMT == pHt->Which())
+ {
+ std::shared_ptr<SfxItemSet> const & pSet(
+ pHt->GetAutoFormat().GetStyleHandle());
+ if ((pSet->Count() == 1) && pSet->GetItem(RES_CHRATR_RSID, false))
+ {
+ // fdo#70201: eliminate no-extent RSID-only AUTOFMT
+ // could be produced by ReplaceText or (maybe?) RstAttr
+ if (pHt->GetStart() == *pHt->GetEnd())
+ {
+ DeleteAtPos(i); // kill it without History!
+ SwTextAttr::Destroy(pHt, rNode.GetDoc().GetAttrPool());
+ --i;
+ continue;
+ }
+ // fdo#52028: this one has _only_ RSID => ignore it completely
+ if (!pHt->IsFormatIgnoreStart() || !pHt->IsFormatIgnoreEnd())
+ {
+ NoteInHistory(pHt);
+ pHt->SetFormatIgnoreStart(true);
+ pHt->SetFormatIgnoreEnd (true);
+ NoteInHistory(pHt, true);
+ }
+ isRsidOnlyAutoFormat = true;
+ }
+ }
+
+ if (pHt->GetStart() == *pHt->GetEnd())
+ {
+ // no-length hints are a disease. ignore them here.
+ // the SwAttrIter::SeekFwd will not call Rst/Chg for them.
+ continue;
+ }
+
+ const sal_Int32 nPorStart = pHt->GetStart();
+ if (nPorStart != nLastPorStart)
+ ++nKey;
+ nLastPorStart = nPorStart;
+ aPortionMap.push_back(Portion {pHt, nKey, isRsidOnlyAutoFormat});
+ RsidOnlyAutoFormatFlagMap[nKey] = isRsidOnlyAutoFormat;
+ }
+
+ // we add data strictly in-order, so we can forward-search the vector
+ auto equal_range = [](PortionMap::const_iterator startIt, PortionMap::const_iterator endIt, int i)
+ {
+ auto it1 = startIt;
+ while (it1 != endIt && it1->nKey < i)
+ ++it1;
+ auto it2 = it1;
+ while (it2 != endIt && it2->nKey == i)
+ ++it2;
+ return std::pair<PortionMap::const_iterator, PortionMap::const_iterator>{ it1, it2 };
+ };
+
+ // check if portion i can be merged with portion i+1:
+ // note: need to include i=0 to set IgnoreStart and j=nKey+1 to reset
+ // IgnoreEnd at first / last portion
+ int i = 0;
+ int j = i + 1;
+ // Store this outside the loop, because we limit the search area on subsequent searches.
+ std::pair< PortionMap::const_iterator, PortionMap::const_iterator > aRange1 { aPortionMap.begin(), aPortionMap.begin() + aPortionMap.size() };
+ while ( i <= nKey )
+ {
+ aRange1 = equal_range( aRange1.first, aPortionMap.begin() + aPortionMap.size(), i );
+ // start the search for this one from where the first search ended.
+ std::pair< PortionMap::const_iterator, PortionMap::const_iterator > aRange2
+ = equal_range( aRange1.second, aPortionMap.begin() + aPortionMap.size(), j );
+
+ MergeResult eMerge = lcl_Compare_Attributes(i, j, aRange1, aRange2, RsidOnlyAutoFormatFlagMap);
+
+ if (MATCH == eMerge)
+ {
+ // important: delete second range so any IgnoreStart on the first
+ // range is still valid
+ // erase all elements with key i + 1
+ sal_Int32 nNewPortionEnd = 0;
+ for ( auto aIter2 = aRange2.first; aIter2 != aRange2.second; ++aIter2 )
+ {
+ SwTextAttr *const p2 = aIter2->pTextAttr;
+ nNewPortionEnd = *p2->GetEnd();
+
+ const size_t nCountBeforeDelete = Count();
+ Delete( p2 );
+
+ // robust: check if deletion actually took place before destroying attribute:
+ if ( Count() < nCountBeforeDelete )
+ rNode.DestroyAttr( p2 );
+ }
+ aPortionMap.erase( aRange2.first, aRange2.second );
+ ++j;
+
+ // change all attributes with key i
+ aRange1 = equal_range( aRange1.first, aPortionMap.begin() + aPortionMap.size(), i );
+ for ( auto aIter1 = aRange1.first; aIter1 != aRange1.second; ++aIter1 )
+ {
+ SwTextAttr *const p1 = aIter1->pTextAttr;
+ NoteInHistory( p1 );
+ p1->SetEnd(nNewPortionEnd);
+ NoteInHistory( p1, true );
+ bRet = true;
+ }
+
+ if (bRet)
+ {
+ Resort();
+ }
+ }
+ else
+ {
+ // when not merging the ignore flags need to be either set or reset
+ // (reset too in case one of the autofmts was recently changed)
+ bool const bSetIgnoreFlag(DIFFER_ONLY_RSID == eMerge);
+ for (auto aIter1 = aRange1.first; aIter1 != aRange1.second; ++aIter1)
+ {
+ if (!aIter1->isRsidOnlyAutoFormat) // already set above, don't change
+ {
+ SwTextAttr *const pCurrent(aIter1->pTextAttr);
+ if (pCurrent->IsFormatIgnoreEnd() != bSetIgnoreFlag)
+ {
+ NoteInHistory(pCurrent);
+ pCurrent->SetFormatIgnoreEnd(bSetIgnoreFlag);
+ NoteInHistory(pCurrent, true);
+ }
+ }
+ }
+ for (auto aIter2 = aRange2.first; aIter2 != aRange2.second; ++aIter2)
+ {
+ if (!aIter2->isRsidOnlyAutoFormat) // already set above, don't change
+ {
+ SwTextAttr *const pCurrent(aIter2->pTextAttr);
+ if (pCurrent->IsFormatIgnoreStart() != bSetIgnoreFlag)
+ {
+ NoteInHistory(pCurrent);
+ pCurrent->SetFormatIgnoreStart(bSetIgnoreFlag);
+ NoteInHistory(pCurrent, true);
+ }
+ }
+ }
+ i = j; // ++i not enough: i + 1 may have been deleted (MATCH)!
+ ++j;
+ }
+ }
+
+ return bRet;
+}
+
+
+static MergeResult lcl_Compare_Attributes(
+ int i, int j,
+ const std::pair< PortionMap::const_iterator, PortionMap::const_iterator >& aRange1,
+ const std::pair< PortionMap::const_iterator, PortionMap::const_iterator >& aRange2,
+ std::vector<bool>& RsidOnlyAutoFormatFlagMap)
+{
+ PortionMap::const_iterator aIter1 = aRange1.first;
+ PortionMap::const_iterator aIter2 = aRange2.first;
+
+ size_t const nAttributesInPor1 = std::distance(aRange1.first, aRange1.second);
+ size_t const nAttributesInPor2 = std::distance(aRange2.first, aRange2.second);
+ bool const isRsidOnlyAutoFormat1 = i < sal_Int32(RsidOnlyAutoFormatFlagMap.size()) && RsidOnlyAutoFormatFlagMap[i];
+ bool const isRsidOnlyAutoFormat2 = j < sal_Int32(RsidOnlyAutoFormatFlagMap.size()) && RsidOnlyAutoFormatFlagMap[j];
+
+ // if both have one they could be equal, but not if only one has it
+ bool const bSkipRsidOnlyAutoFormat(nAttributesInPor1 != nAttributesInPor2);
+
+ // this loop needs to handle the case where one has a CHARFMT and the
+ // other CHARFMT + RSID-only AUTOFMT, so...
+ // want to skip over RSID-only AUTOFMT here, hence the -1
+ if (!((nAttributesInPor1 - (isRsidOnlyAutoFormat1 ? 1 : 0)) ==
+ (nAttributesInPor2 - (isRsidOnlyAutoFormat2 ? 1 : 0))
+ && (nAttributesInPor1 != 0 || nAttributesInPor2 != 0)))
+ {
+ return DIFFER;
+ }
+
+ MergeResult eMerge(MATCH);
+
+ // _if_ there is one element more either in aRange1 or aRange2
+ // it _must_ be an RSID-only AUTOFMT, which can be ignored here...
+ // But if both have RSID-only AUTOFMT they could be equal, no skip!
+ while (aIter1 != aRange1.second || aIter2 != aRange2.second)
+ {
+ // first of all test if there's no gap (before skipping stuff!)
+ if (aIter1 != aRange1.second && aIter2 != aRange2.second &&
+ *aIter1->pTextAttr->End() < aIter2->pTextAttr->GetStart())
+ {
+ return DIFFER;
+ }
+ // skip it - cannot be equal if bSkipRsidOnlyAutoFormat is set
+ if (bSkipRsidOnlyAutoFormat
+ && aIter1 != aRange1.second && aIter1->isRsidOnlyAutoFormat)
+ {
+ assert(DIFFER != eMerge);
+ eMerge = DIFFER_ONLY_RSID;
+ ++aIter1;
+ continue;
+ }
+ if (bSkipRsidOnlyAutoFormat
+ && aIter2 != aRange2.second && aIter2->isRsidOnlyAutoFormat)
+ {
+ assert(DIFFER != eMerge);
+ eMerge = DIFFER_ONLY_RSID;
+ ++aIter2;
+ continue;
+ }
+ assert(aIter1 != aRange1.second && aIter2 != aRange2.second);
+ SwTextAttr const*const p1 = aIter1->pTextAttr;
+ SwTextAttr const*const p2 = aIter2->pTextAttr;
+ if (p1->Which() != p2->Which())
+ {
+ return DIFFER;
+ }
+ if (!(*p1 == *p2))
+ {
+ // fdo#52028: for auto styles, check if they differ only
+ // in the RSID, which should have no effect on text layout
+ if (RES_TXTATR_AUTOFMT != p1->Which())
+ return DIFFER;
+
+ const SfxItemSet& rSet1 = *p1->GetAutoFormat().GetStyleHandle();
+ const SfxItemSet& rSet2 = *p2->GetAutoFormat().GetStyleHandle();
+
+ // sadly SfxItemSet::operator== does not seem to work?
+ SfxItemIter iter1(rSet1);
+ SfxItemIter iter2(rSet2);
+ for (SfxPoolItem const* pItem1 = iter1.GetCurItem(),
+ * pItem2 = iter2.GetCurItem();;)
+ {
+ if (pItem1 && pItem1->Which() == RES_CHRATR_RSID)
+ pItem1 = iter1.NextItem();
+ if (pItem2 && pItem2->Which() == RES_CHRATR_RSID)
+ pItem2 = iter2.NextItem();
+
+ if (nullptr == pItem1 && nullptr == pItem2)
+ {
+ eMerge = DIFFER_ONLY_RSID;
+ break;
+ }
+
+ if (nullptr == pItem1 || nullptr == pItem2)
+ {
+ // one ptr is nullptr, not both, that would
+ // have triggered above
+ return DIFFER;
+ }
+
+ if (!SfxPoolItem::areSame(*pItem1, *pItem2))
+ {
+ return DIFFER;
+ }
+ pItem1 = iter1.NextItem();
+ pItem2 = iter2.NextItem();
+ }
+ }
+ ++aIter1;
+ ++aIter2;
+ }
+ return eMerge;
+}
+
+
+// check if there is already a character format and adjust the sort numbers
+static void lcl_CheckSortNumber( const SwpHints& rHints, SwTextCharFormat& rNewCharFormat )
+{
+ const sal_Int32 nHtStart = rNewCharFormat.GetStart();
+ const sal_Int32 nHtEnd = *rNewCharFormat.GetEnd();
+ sal_uInt16 nSortNumber = 0;
+
+ for ( size_t i = 0; i < rHints.Count(); ++i )
+ {
+ const SwTextAttr* pOtherHt = rHints.Get(i);
+
+ const sal_Int32 nOtherStart = pOtherHt->GetStart();
+
+ if ( nOtherStart > nHtStart )
+ break;
+
+ if ( RES_TXTATR_CHARFMT == pOtherHt->Which() )
+ {
+ const sal_Int32 nOtherEnd = *pOtherHt->End();
+
+ if ( nOtherStart == nHtStart && nOtherEnd == nHtEnd )
+ {
+ nSortNumber = static_txtattr_cast<const SwTextCharFormat*>(pOtherHt)->GetSortNumber() + 1;
+ }
+ }
+ }
+
+ if ( nSortNumber > 0 )
+ rNewCharFormat.SetSortNumber( nSortNumber );
+}
+
+/*
+ * Try to insert the new hint.
+ * Depending on the type of the hint, this either always succeeds, or may fail.
+ * Depending on the type of the hint, other hints may be deleted or
+ * overwritten.
+ * The return value indicates successful insertion.
+ */
+bool SwpHints::TryInsertHint(
+ SwTextAttr* const pHint,
+ SwTextNode &rNode,
+ const SetAttrMode nMode )
+{
+ if ( MAX_HINTS <= Count() ) // we're sorry, this flight is overbooked...
+ {
+ OSL_FAIL("hints array full :-(");
+ rNode.DestroyAttr(pHint);
+ return false;
+ }
+
+ const sal_Int32 *pHtEnd = pHint->GetEnd();
+ const sal_uInt16 nWhich = pHint->Which();
+ std::vector<sal_uInt16> aWhichSublist;
+
+ switch( nWhich )
+ {
+ case RES_TXTATR_CHARFMT:
+ {
+ // Check if character format contains hidden attribute:
+ const SwCharFormat* pFormat = pHint->GetCharFormat().GetCharFormat();
+ if ( SfxItemState::SET == pFormat->GetItemState( RES_CHRATR_HIDDEN ) )
+ rNode.SetCalcHiddenCharFlags();
+
+ static_txtattr_cast<SwTextCharFormat*>(pHint)->ChgTextNode( &rNode );
+ break;
+ }
+ // #i75430# Recalc hidden flags if necessary
+ case RES_TXTATR_AUTOFMT:
+ {
+ std::shared_ptr<SfxItemSet> const & pSet( pHint->GetAutoFormat().GetStyleHandle() );
+ if (pHint->GetStart() == *pHint->GetEnd())
+ {
+ if (pSet->Count() == 1 && pSet->GetItem(RES_CHRATR_RSID, false))
+ { // empty range RSID-only hints could cause trouble, there's no
+ rNode.DestroyAttr(pHint); // need for them so don't insert
+ return false;
+ }
+ }
+ // Check if auto style contains hidden attribute:
+ const SfxPoolItem* pHiddenItem = CharFormat::GetItem( *pHint, RES_CHRATR_HIDDEN );
+ if ( pHiddenItem )
+ rNode.SetCalcHiddenCharFlags();
+
+ // fdo#71556: populate aWhichFormatAttr member of SwMsgPoolItem
+ const WhichRangesContainer& pRanges = pSet->GetRanges();
+ for(auto const & rPair : pRanges)
+ {
+ const sal_uInt16 nBeg = rPair.first;
+ const sal_uInt16 nEnd = rPair.second;
+ for( sal_uInt16 nSubElem = nBeg; nSubElem <= nEnd; ++nSubElem )
+ if( pSet->HasItem( nSubElem ) )
+ aWhichSublist.push_back( nSubElem );
+ }
+ break;
+ }
+ case RES_TXTATR_INETFMT:
+ static_txtattr_cast<SwTextINetFormat*>(pHint)->InitINetFormat(rNode);
+ break;
+
+ case RES_TXTATR_FIELD:
+ case RES_TXTATR_ANNOTATION:
+ case RES_TXTATR_INPUTFIELD:
+ {
+ SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pHint));
+ bool bDelFirst = nullptr != pTextField->GetpTextNode();
+ pTextField->ChgTextNode( &rNode );
+ SwDoc& rDoc = rNode.GetDoc();
+ const SwField* pField = pTextField->GetFormatField().GetField();
+
+ if( !rDoc.getIDocumentFieldsAccess().IsNewFieldLst() )
+ {
+ // certain fields must update the SwDoc's calculation flags
+ switch( pField->GetTyp()->Which() )
+ {
+ case SwFieldIds::Database:
+ case SwFieldIds::SetExp:
+ case SwFieldIds::HiddenPara:
+ case SwFieldIds::HiddenText:
+ case SwFieldIds::DbNumSet:
+ case SwFieldIds::DbNextSet:
+ {
+ if( bDelFirst )
+ rDoc.getIDocumentFieldsAccess().InsDelFieldInFieldLst(false, *pTextField);
+ if( rNode.GetNodes().IsDocNodes() )
+ rDoc.getIDocumentFieldsAccess().InsDelFieldInFieldLst(true, *pTextField);
+ }
+ break;
+ case SwFieldIds::Dde:
+ if( rNode.GetNodes().IsDocNodes() )
+ static_cast<SwDDEFieldType*>(pField->GetTyp())->IncRefCnt();
+ break;
+ default: break;
+ }
+ }
+
+ // insert into real document's nodes-array?
+ if( rNode.GetNodes().IsDocNodes() )
+ {
+ bool bInsFieldType = false;
+ switch( pField->GetTyp()->Which() )
+ {
+ case SwFieldIds::SetExp:
+ bInsFieldType = static_cast<SwSetExpFieldType*>(pField->GetTyp())->IsDeleted();
+ if( nsSwGetSetExpType::GSE_SEQ & static_cast<SwSetExpFieldType*>(pField->GetTyp())->GetType() )
+ {
+ // register the field at its FieldType before setting
+ // the sequence reference number!
+ SwSetExpFieldType* pFieldType = static_cast<SwSetExpFieldType*>(
+ rDoc.getIDocumentFieldsAccess().InsertFieldType( *pField->GetTyp() ) );
+ if( pFieldType != pField->GetTyp() )
+ {
+ SwFormatField* pFormatField = const_cast<SwFormatField*>(&pTextField->GetFormatField());
+ pFormatField->RegisterToFieldType( *pFieldType );
+ pFormatField->GetField()->ChgTyp( pFieldType );
+ }
+ pFieldType->SetSeqRefNo( *const_cast<SwSetExpField*>(static_cast<const SwSetExpField*>(pField)) );
+ }
+ break;
+ case SwFieldIds::User:
+ bInsFieldType = static_cast<SwUserFieldType*>(pField->GetTyp())->IsDeleted();
+ break;
+
+ case SwFieldIds::Dde:
+ if( rDoc.getIDocumentFieldsAccess().IsNewFieldLst() )
+ static_cast<SwDDEFieldType*>(pField->GetTyp())->IncRefCnt();
+ bInsFieldType = static_cast<SwDDEFieldType*>(pField->GetTyp())->IsDeleted();
+ break;
+
+ case SwFieldIds::Postit:
+ if ( rDoc.GetDocShell() )
+ {
+ rDoc.GetDocShell()->Broadcast( SwFormatFieldHint(
+ &pTextField->GetFormatField(), SwFormatFieldHintWhich::INSERTED));
+ }
+ break;
+ default: break;
+ }
+ if( bInsFieldType )
+ rDoc.getIDocumentFieldsAccess().InsDeletedFieldType( *pField->GetTyp() );
+ }
+ }
+ break;
+ case RES_TXTATR_FTN :
+ static_cast<SwTextFootnote*>(pHint)->ChgTextNode( &rNode );
+ break;
+ case RES_TXTATR_REFMARK:
+ static_txtattr_cast<SwTextRefMark*>(pHint)->ChgTextNode( &rNode );
+ if( rNode.GetNodes().IsDocNodes() )
+ {
+ // search for a reference with the same name
+ SwTextAttr* pTmpHt;
+ for( size_t n = 0, nEnd = Count(); n < nEnd; ++n )
+ {
+ const sal_Int32 *pTmpHtEnd;
+ const sal_Int32 *pTmpHintEnd;
+ if (RES_TXTATR_REFMARK == (pTmpHt = Get(n))->Which() &&
+ pHint->GetAttr() == pTmpHt->GetAttr() &&
+ nullptr != ( pTmpHtEnd = pTmpHt->GetEnd() ) &&
+ nullptr != ( pTmpHintEnd = pHint->GetEnd() ) )
+ {
+ SwComparePosition eCmp = ::ComparePosition(
+ pTmpHt->GetStart(), *pTmpHtEnd,
+ pHint->GetStart(), *pTmpHintEnd );
+ bool bDelOld = true, bChgStart = false, bChgEnd = false;
+ switch( eCmp )
+ {
+ case SwComparePosition::Before:
+ case SwComparePosition::Behind: bDelOld = false; break;
+
+ case SwComparePosition::Outside: bChgStart = bChgEnd = true; break;
+
+ case SwComparePosition::CollideEnd:
+ case SwComparePosition::OverlapBefore: bChgStart = true; break;
+ case SwComparePosition::CollideStart:
+ case SwComparePosition::OverlapBehind: bChgEnd = true; break;
+ default: break;
+ }
+
+ if( bChgStart )
+ {
+ pHint->SetStart( pTmpHt->GetStart() );
+ }
+ if( bChgEnd )
+ pHint->SetEnd(*pTmpHtEnd);
+
+ if( bDelOld )
+ {
+ NoteInHistory( pTmpHt );
+ rNode.DestroyAttr( Cut( n-- ) );
+ --nEnd;
+ }
+ }
+ }
+ }
+ break;
+ case RES_TXTATR_TOXMARK:
+ static_txtattr_cast<SwTextTOXMark*>(pHint)->ChgTextNode( &rNode );
+ break;
+
+ case RES_TXTATR_CJK_RUBY:
+ static_txtattr_cast<SwTextRuby*>(pHint)->InitRuby(rNode);
+ break;
+
+ case RES_TXTATR_META:
+ case RES_TXTATR_METAFIELD:
+ static_txtattr_cast<SwTextMeta *>(pHint)->ChgTextNode( &rNode );
+ break;
+
+ case RES_TXTATR_CONTENTCONTROL:
+ static_txtattr_cast<SwTextContentControl*>(pHint)->ChgTextNode( &rNode );
+ break;
+
+ case RES_CHRATR_HIDDEN:
+ rNode.SetCalcHiddenCharFlags();
+ break;
+ }
+
+ if( SetAttrMode::DONTEXPAND & nMode )
+ pHint->SetDontExpand( true );
+
+ // special handling for SwTextAttrs without end:
+ // 1) they cannot overlap
+ // 2) if two fields are adjacent, they must not be merged into one
+ // this is guaranteed by inserting a CH_TXTATR_* into the paragraph text!
+ sal_Int32 nHtStart = pHint->GetStart();
+ if( !pHtEnd )
+ {
+ Insert( pHint );
+ NoteInHistory(pHint, true);
+ CalcFlags();
+#ifdef DBG_UTIL
+ if( !rNode.GetDoc().IsInReading() )
+ CHECK;
+#endif
+ // ... and notify listeners
+ if(rNode.HasWriterListeners())
+ {
+ SwUpdateAttr aHint(
+ nHtStart,
+ nHtStart,
+ nWhich);
+
+ rNode.TriggerNodeUpdate(sw::LegacyModifyHint(&aHint, &aHint));
+ }
+
+ return true;
+ }
+
+ // from here on, pHint is known to have an end index!
+
+ if( *pHtEnd < nHtStart )
+ {
+ assert(*pHtEnd >= nHtStart);
+
+ // just swap the nonsense:
+ pHint->SetStart(*pHtEnd);
+ pHint->SetEnd(nHtStart);
+ nHtStart = pHint->GetStart();
+ }
+
+ // I need this value later on for notification but the pointer may become invalid
+ const sal_Int32 nHintEnd = *pHtEnd;
+ const bool bNoHintAdjustMode = bool(SetAttrMode::NOHINTADJUST & nMode);
+
+ // handle nesting attributes: inserting may fail due to overlap!
+ if (pHint->IsNesting())
+ {
+ const bool bRet(
+ TryInsertNesting(rNode, *static_txtattr_cast<SwTextAttrNesting*>(pHint)));
+ if (!bRet) return false;
+ }
+ // Currently REFMARK and TOXMARK have OverlapAllowed set to true.
+ // These attributes may be inserted directly.
+ // Also attributes without length may be inserted directly.
+ // SETATTR_NOHINTADJUST is set e.g., during undo.
+ // Portion building in not necessary during XML import.
+ else if ( !bNoHintAdjustMode &&
+ !pHint->IsOverlapAllowedAttr() &&
+ !rNode.GetDoc().IsInXMLImport() &&
+ ( RES_TXTATR_AUTOFMT == nWhich ||
+ RES_TXTATR_CHARFMT == nWhich ) )
+ {
+ assert( nWhich != RES_TXTATR_AUTOFMT ||
+ static_cast<const SwFormatAutoFormat&>(pHint->GetAttr()).GetStyleHandle()->GetPool() ==
+ &rNode.GetDoc().GetAttrPool());
+
+ BuildPortions( rNode, *pHint, nMode );
+
+ if ( nHtStart < nHintEnd ) // skip merging for 0-length attributes
+ MergePortions( rNode );
+ }
+ else
+ {
+ // There may be more than one character style at the current position.
+ // Take care of the sort number.
+ // FME 2007-11-08 #i82989# in NOHINTADJUST mode, we want to insert
+ // character attributes directly
+ if (!bNoHintAdjustMode
+ && ( (RES_TXTATR_CHARFMT == nWhich)
+ // tdf#149978 also for import of automatic styles, could be produced by non-LO application
+ || (RES_TXTATR_AUTOFMT == nWhich && rNode.GetDoc().IsInXMLImport())))
+ {
+ BuildPortions( rNode, *pHint, nMode );
+ }
+ else
+ {
+ // #i82989# Check sort numbers in NoHintAdjustMode
+ if ( RES_TXTATR_CHARFMT == nWhich )
+ lcl_CheckSortNumber(*this, *static_txtattr_cast<SwTextCharFormat*>(pHint));
+
+ Insert( pHint );
+ NoteInHistory( pHint, true );
+ }
+ }
+
+ // ... and notify listeners
+ if ( rNode.HasWriterListeners() )
+ {
+ const SwUpdateAttr aHint(nHtStart, nHintEnd, nWhich, std::move(aWhichSublist));
+ rNode.TriggerNodeUpdate(sw::LegacyModifyHint(&aHint, &aHint));
+ }
+
+#ifdef DBG_UTIL
+ if( !bNoHintAdjustMode && !rNode.GetDoc().IsInReading() )
+ CHECK;
+#endif
+
+ return true;
+}
+
+void SwpHints::DeleteAtPos( const size_t nPos )
+{
+ assert(!m_bStartMapNeedsSorting && "deleting at pos and the list needs sorting?");
+
+ SwTextAttr *pHint = Get(nPos);
+ assert( pHint->m_pHints == this );
+ // ChainDelete( pHint );
+ NoteInHistory( pHint );
+
+ // optimization: nPos is the position in the Starts array
+ SwTextAttr *pHt = m_HintsByStart[ nPos ];
+ m_HintsByStart.erase( m_HintsByStart.begin() + nPos );
+
+ if (m_bStartMapNeedsSorting)
+ ResortStartMap();
+ if (m_bEndMapNeedsSorting)
+ ResortEndMap();
+ if (m_bWhichMapNeedsSorting)
+ ResortWhichMap();
+
+ auto findIt = std::lower_bound(m_HintsByEnd.begin(), m_HintsByEnd.end(), pHt, CompareSwpHtEnd());
+ assert(*findIt == pHt);
+ m_HintsByEnd.erase(findIt);
+
+ auto findIt2 = std::lower_bound(m_HintsByWhichAndStart.begin(), m_HintsByWhichAndStart.end(), pHt, CompareSwpHtWhichStart());
+ assert(*findIt2 == pHt);
+ m_HintsByWhichAndStart.erase(findIt2);
+
+ pHt->m_pHints = nullptr;
+
+ if( pHint->Which() == RES_TXTATR_FIELD )
+ {
+ SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pHint));
+ const SwFieldType* pFieldTyp = pTextField->GetFormatField().GetField()->GetTyp();
+ if( SwFieldIds::Dde == pFieldTyp->Which() )
+ {
+ const SwTextNode* pNd = pTextField->GetpTextNode();
+ if( pNd && pNd->GetNodes().IsDocNodes() )
+ const_cast<SwDDEFieldType*>(static_cast<const SwDDEFieldType*>(pFieldTyp))->DecRefCnt();
+ pTextField->ChgTextNode(nullptr);
+ }
+ else if (m_bHiddenByParaField
+ && m_rParent.GetDoc().FieldCanHideParaWeight(pFieldTyp->Which()))
+ {
+ m_bCalcHiddenParaField = true;
+ }
+ }
+ else if ( pHint->Which() == RES_TXTATR_ANNOTATION )
+ {
+ SwTextField *const pTextField(static_txtattr_cast<SwTextField*>(pHint));
+ const_cast<SwFormatField&>(pTextField->GetFormatField()).Broadcast(
+ SwFormatFieldHint(&pTextField->GetFormatField(), SwFormatFieldHintWhich::REMOVED));
+ }
+
+ CalcFlags();
+ CHECK_NOTMERGED; // called from BuildPortions
+}
+
+/// delete the hint
+/// precondition: pTextHt must be in this array
+void SwpHints::Delete( SwTextAttr const * pTextHt )
+{
+ const size_t nPos = GetIndexOf( pTextHt );
+ assert(SAL_MAX_SIZE != nPos);
+ if( SAL_MAX_SIZE != nPos )
+ DeleteAtPos( nPos );
+}
+
+void SwTextNode::ClearSwpHintsArr( bool bDelFields )
+{
+ if ( !HasHints() )
+ return;
+
+ size_t nPos = 0;
+ while ( nPos < m_pSwpHints->Count() )
+ {
+ SwTextAttr* pDel = m_pSwpHints->Get( nPos );
+ bool bDel = false;
+
+ switch( pDel->Which() )
+ {
+ case RES_TXTATR_FLYCNT:
+ case RES_TXTATR_FTN:
+ break;
+
+ case RES_TXTATR_FIELD:
+ case RES_TXTATR_ANNOTATION:
+ case RES_TXTATR_INPUTFIELD:
+ if( bDelFields )
+ bDel = true;
+ break;
+ default:
+ bDel = true; break;
+ }
+
+ if( bDel )
+ {
+ m_pSwpHints->DeleteAtPos( nPos );
+ DestroyAttr( pDel );
+ }
+ else
+ ++nPos;
+ }
+}
+
+LanguageType SwTextNode::GetLang( const sal_Int32 nBegin, const sal_Int32 nLen,
+ sal_uInt16 nScript ) const
+{
+ LanguageType nRet = LANGUAGE_DONTKNOW;
+
+ if ( ! nScript )
+ {
+ nScript = g_pBreakIt->GetRealScriptOfText( m_Text, nBegin );
+ }
+
+ // #i91465# Consider nScript if pSwpHints == 0
+ const TypedWhichId<SvxLanguageItem> nWhichId = GetWhichOfScript( RES_CHRATR_LANGUAGE, nScript );
+
+ if ( HasHints() )
+ {
+ const sal_Int32 nEnd = nBegin + nLen;
+ const size_t nSize = m_pSwpHints->Count();
+ for ( size_t i = 0; i < nSize; ++i )
+ {
+ const SwTextAttr *pHt = m_pSwpHints->Get(i);
+ const sal_Int32 nAttrStart = pHt->GetStart();
+ if( nEnd < nAttrStart )
+ break;
+
+ const sal_uInt16 nWhich = pHt->Which();
+
+ if( nWhichId == nWhich ||
+ ( ( pHt->IsCharFormatAttr() || RES_TXTATR_AUTOFMT == nWhich ) && CharFormat::IsItemIncluded( nWhichId, pHt ) ) )
+ {
+ const sal_Int32 *pEndIdx = pHt->End();
+ // do the attribute and the range overlap?
+ if( !pEndIdx )
+ continue;
+ if( nLen )
+ {
+ if( nAttrStart >= nEnd || nBegin >= *pEndIdx )
+ continue;
+ }
+ else if( nBegin != nAttrStart || ( nAttrStart != *pEndIdx && nBegin ))
+ {
+ if( nAttrStart >= nBegin )
+ continue;
+ if( pHt->DontExpand() ? nBegin >= *pEndIdx : nBegin > *pEndIdx)
+ continue;
+ }
+ const SvxLanguageItem* pItem = CharFormat::GetItem( *pHt, nWhichId );
+ const LanguageType nLng = pItem->GetLanguage();
+
+ // does the attribute completely cover the range?
+ if( nAttrStart <= nBegin && nEnd <= *pEndIdx )
+ nRet = nLng;
+ else if( LANGUAGE_DONTKNOW == nRet )
+ nRet = nLng; // partial overlap, the first one wins
+ }
+ }
+ }
+ if( LANGUAGE_DONTKNOW == nRet )
+ {
+ nRet = GetSwAttrSet().Get( nWhichId ).GetLanguage();
+ if( LANGUAGE_DONTKNOW == nRet )
+ nRet = GetAppLanguage();
+ }
+ return nRet;
+}
+
+sal_Unicode GetCharOfTextAttr( const SwTextAttr& rAttr )
+{
+ sal_Unicode cRet = CH_TXTATR_BREAKWORD;
+ switch ( rAttr.Which() )
+ {
+ case RES_TXTATR_REFMARK:
+ case RES_TXTATR_TOXMARK:
+ case RES_TXTATR_ANNOTATION:
+ cRet = CH_TXTATR_INWORD;
+ break;
+
+ case RES_TXTATR_FIELD:
+ case RES_TXTATR_FLYCNT:
+ case RES_TXTATR_FTN:
+ case RES_TXTATR_META:
+ case RES_TXTATR_METAFIELD:
+ case RES_TXTATR_CONTENTCONTROL:
+ {
+ cRet = CH_TXTATR_BREAKWORD;
+ }
+ break;
+ case RES_TXTATR_LINEBREAK:
+ {
+ cRet = CH_TXTATR_NEWLINE;
+ }
+ break;
+
+ default:
+ assert(!"GetCharOfTextAttr: unknown attr");
+ break;
+ }
+ return cRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/txatbase.cxx b/sw/source/core/txtnode/txatbase.cxx
new file mode 100644
index 0000000000..1d57f0e0dd
--- /dev/null
+++ b/sw/source/core/txtnode/txatbase.cxx
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <optional>
+#include <libxml/xmlwriter.h>
+#include <svl/itempool.hxx>
+#include <txatbase.hxx>
+#include <fmtfld.hxx>
+
+SwTextAttr::SwTextAttr( SfxPoolItem& rAttr, sal_Int32 nStart )
+ : m_pAttr( &rAttr )
+ , m_nStart( nStart )
+ , m_bDontExpand( false )
+ , m_bLockExpandFlag( false )
+ , m_bDontMoveAttr( false )
+ , m_bCharFormatAttr( false )
+ , m_bOverlapAllowedAttr( false )
+ , m_bPriorityAttr( false )
+ , m_bDontExpandStart( false )
+ , m_bNesting( false )
+ , m_bHasDummyChar( false )
+ , m_bFormatIgnoreStart(false)
+ , m_bFormatIgnoreEnd(false)
+ , m_bHasContent( false )
+{
+}
+
+SwTextAttr::~SwTextAttr() COVERITY_NOEXCEPT_FALSE
+{
+}
+
+const sal_Int32* SwTextAttr::GetEnd() const
+{
+ return nullptr;
+}
+
+void SwTextAttr::SetEnd(sal_Int32 )
+{
+ assert(false);
+}
+
+void SwTextAttr::Destroy( SwTextAttr * pToDestroy, SfxItemPool& rPool )
+{
+ if (!pToDestroy) return;
+ SfxPoolItem * const pAttr = pToDestroy->m_pAttr;
+ delete pToDestroy;
+ rPool.DirectRemoveItemFromPool( *pAttr );
+}
+
+bool SwTextAttr::operator==( const SwTextAttr& rAttr ) const
+{
+ return GetAttr() == rAttr.GetAttr();
+}
+
+SwTextAttrEnd::SwTextAttrEnd( SfxPoolItem& rAttr,
+ sal_Int32 nStart, sal_Int32 nEnd ) :
+ SwTextAttr( rAttr, nStart ), m_nEnd( nEnd )
+{
+}
+
+const sal_Int32* SwTextAttrEnd::GetEnd() const
+{
+ return & m_nEnd;
+}
+
+void SwTextAttrEnd::SetEnd(sal_Int32 n)
+{
+ m_nEnd = n;
+ if (m_pHints)
+ m_pHints->EndPosChanged();
+}
+
+void SwTextAttr::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwTextAttr"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s",
+ BAD_CAST(typeid(*this).name()));
+
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("start"), BAD_CAST(OString::number(m_nStart).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("dont-expand"),
+ BAD_CAST(OString::boolean(m_bDontExpand).getStr()));
+ if (End())
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("end"), BAD_CAST(OString::number(*End()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr()));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("m_pAttr"), "%p", m_pAttr);
+ const char* pWhich = nullptr;
+ std::optional<OString> oValue;
+ switch (Which())
+ {
+ case RES_TXTATR_AUTOFMT:
+ pWhich = "autofmt";
+ break;
+ case RES_TXTATR_ANNOTATION:
+ pWhich = "annotation";
+ break;
+ case RES_TXTATR_FLYCNT:
+ pWhich = "fly content";
+ break;
+ case RES_TXTATR_INETFMT:
+ {
+ pWhich = "inet format";
+ const SwFormatINetFormat& rFormat = GetINetFormat();
+ oValue = OString("url: " + rFormat.GetValue().toUtf8());
+ break;
+ }
+ case RES_TXTATR_CJK_RUBY:
+ {
+ pWhich = "ruby";
+ const SwFormatRuby& rFormat = GetRuby();
+ oValue = OString("rubytext: " + rFormat.GetText().toUtf8());
+ break;
+ }
+ case RES_TXTATR_META:
+ {
+ pWhich = "meta";
+ break;
+ }
+ case RES_TXTATR_FIELD:
+ {
+ pWhich = "field";
+ break;
+ }
+ default:
+ break;
+ }
+ if (pWhich)
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("which"), BAD_CAST(pWhich));
+ if (oValue)
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(oValue->getStr()));
+ switch (Which())
+ {
+ case RES_TXTATR_AUTOFMT:
+ GetAutoFormat().dumpAsXml(pWriter);
+ break;
+ case RES_TXTATR_FIELD:
+ case RES_TXTATR_INPUTFIELD:
+ GetFormatField().dumpAsXml(pWriter);
+ break;
+ case RES_TXTATR_FTN:
+ GetFootnote().dumpAsXml(pWriter);
+ break;
+ case RES_TXTATR_LINEBREAK:
+ GetLineBreak().dumpAsXml(pWriter);
+ break;
+ case RES_TXTATR_META:
+ break;
+ case RES_TXTATR_CONTENTCONTROL:
+ GetContentControl().dumpAsXml(pWriter);
+ break;
+ case RES_TXTATR_FLYCNT:
+ GetFlyCnt().dumpAsXml(pWriter);
+ break;
+ case RES_TXTATR_CHARFMT:
+ GetCharFormat().dumpAsXml(pWriter);
+ break;
+ case RES_TXTATR_REFMARK:
+ GetRefMark().dumpAsXml(pWriter);
+ break;
+ case RES_TXTATR_INETFMT:
+ GetINetFormat().dumpAsXml(pWriter);
+ break;
+ default:
+ SAL_WARN("sw.core", "Unhandled TXTATR");
+ break;
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/txatritr.cxx b/sw/source/core/txtnode/txatritr.cxx
new file mode 100644
index 0000000000..1bc97b62b2
--- /dev/null
+++ b/sw/source/core/txtnode/txatritr.cxx
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <txatritr.hxx>
+
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <fchrfmt.hxx>
+#include <charfmt.hxx>
+#include <breakit.hxx>
+#include <ndtxt.hxx>
+#include <txatbase.hxx>
+
+using namespace ::com::sun::star;
+
+SwScriptIterator::SwScriptIterator(
+ const OUString& rStr, sal_Int32 nStt, bool const bFrwrd)
+ : m_rText(rStr)
+ , m_nChgPos(rStr.getLength())
+ , m_nCurScript(i18n::ScriptType::WEAK)
+ , m_bForward(bFrwrd)
+{
+ assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
+ if ( ! bFrwrd && nStt )
+ --nStt;
+
+ sal_Int32 nPos = nStt;
+ m_nCurScript = g_pBreakIt->GetBreakIter()->getScriptType(m_rText, nPos);
+ if( i18n::ScriptType::WEAK == m_nCurScript )
+ {
+ if( nPos )
+ {
+ nPos = g_pBreakIt->GetBreakIter()->beginOfScript(
+ m_rText, nPos, m_nCurScript);
+ if (nPos > 0 && nPos < m_rText.getLength())
+ {
+ nStt = --nPos;
+ m_nCurScript =
+ g_pBreakIt->GetBreakIter()->getScriptType(m_rText,nPos);
+ }
+ }
+ }
+
+ m_nChgPos = m_bForward
+ ? g_pBreakIt->GetBreakIter()->endOfScript(
+ m_rText, nStt, m_nCurScript)
+ : g_pBreakIt->GetBreakIter()->beginOfScript(
+ m_rText, nStt, m_nCurScript);
+}
+
+void SwScriptIterator::Next()
+{
+ assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
+ if (m_bForward && m_nChgPos >= 0 && m_nChgPos < m_rText.getLength())
+ {
+ m_nCurScript =
+ g_pBreakIt->GetBreakIter()->getScriptType(m_rText, m_nChgPos);
+ m_nChgPos = g_pBreakIt->GetBreakIter()->endOfScript(
+ m_rText, m_nChgPos, m_nCurScript);
+ }
+ else if (!m_bForward && m_nChgPos > 0)
+ {
+ --m_nChgPos;
+ m_nCurScript =
+ g_pBreakIt->GetBreakIter()->getScriptType(m_rText, m_nChgPos);
+ m_nChgPos = g_pBreakIt->GetBreakIter()->beginOfScript(
+ m_rText, m_nChgPos, m_nCurScript);
+ }
+}
+
+SwLanguageIterator::SwLanguageIterator( const SwTextNode& rTNd,
+ sal_Int32 nStt )
+ : m_aScriptIter( rTNd.GetText(), nStt ),
+ m_rTextNode( rTNd ),
+ m_pParaItem( nullptr ),
+ m_nAttrPos( 0 ),
+ m_nChgPos( nStt )
+{
+ SearchNextChg();
+}
+
+bool SwLanguageIterator::Next()
+{
+ bool bRet = false;
+ if (m_nChgPos < m_aScriptIter.GetText().getLength())
+ {
+ bRet = true;
+ if( !m_aStack.empty() )
+ {
+ do {
+ const SwTextAttr* pHt = m_aStack.front();
+ const sal_Int32 nEndPos = *pHt->End();
+ if( m_nChgPos >= nEndPos )
+ m_aStack.pop_front();
+ else
+ break;
+ } while( !m_aStack.empty() );
+ }
+
+ if( !m_aStack.empty() )
+ {
+ const size_t nSavePos = m_nAttrPos;
+ SearchNextChg();
+ if( !m_aStack.empty() )
+ {
+ const SwTextAttr* pHt = m_aStack.front();
+ const sal_Int32 nEndPos = *pHt->End();
+ if( m_nChgPos >= nEndPos )
+ {
+ m_nChgPos = nEndPos;
+ m_nAttrPos = nSavePos;
+
+ const TypedWhichId<SvxLanguageItem> nWId = GetWhichOfScript( RES_CHRATR_LANGUAGE, m_aScriptIter.GetCurrScript() );
+ m_pCurrentItem = CharFormat::GetItem(*pHt, nWId);
+
+ m_aStack.pop_front();
+ }
+ }
+ }
+ else
+ SearchNextChg();
+ }
+ return bRet;
+}
+
+void SwLanguageIterator::AddToStack( const SwTextAttr& rAttr )
+{
+ size_t nIns = 0;
+ const sal_Int32 nEndPos = *rAttr.End();
+ for( ; nIns < m_aStack.size(); ++nIns )
+ if( *m_aStack[ nIns ]->End() > nEndPos )
+ break;
+
+ m_aStack.insert( m_aStack.begin() + nIns, &rAttr );
+}
+
+void SwLanguageIterator::SearchNextChg()
+{
+ TypedWhichId<SvxLanguageItem> nWh(0);
+ if( m_nChgPos == m_aScriptIter.GetScriptChgPos() )
+ {
+ m_aScriptIter.Next();
+ m_pParaItem = nullptr;
+ m_nAttrPos = 0; // must be restart at the beginning, because
+ // some attributes can start before or inside
+ // the current scripttype!
+ m_aStack.clear();
+ }
+ if( !m_pParaItem )
+ {
+ nWh = GetWhichOfScript( RES_CHRATR_LANGUAGE, m_aScriptIter.GetCurrScript() );
+ m_pParaItem = &m_rTextNode.GetSwAttrSet().Get( nWh );
+ }
+
+ sal_Int32 nStt = m_nChgPos;
+ m_nChgPos = m_aScriptIter.GetScriptChgPos();
+ m_pCurrentItem = m_pParaItem;
+
+ const SwpHints* pHts = m_rTextNode.GetpSwpHints();
+ if( !pHts )
+ return;
+
+ if( !nWh )
+ {
+ nWh = GetWhichOfScript( RES_CHRATR_LANGUAGE, m_aScriptIter.GetCurrScript() );
+ }
+
+ const SvxLanguageItem* pItem = nullptr;
+ for( ; m_nAttrPos < pHts->Count(); ++m_nAttrPos )
+ {
+ const SwTextAttr* pHt = pHts->Get( m_nAttrPos );
+ const sal_Int32* pEnd = pHt->End();
+ const sal_Int32 nHtStt = pHt->GetStart();
+ if( nHtStt < nStt && ( !pEnd || *pEnd <= nStt ))
+ continue;
+
+ if( nHtStt >= m_nChgPos )
+ break;
+
+ pItem = CharFormat::GetItem( *pHt, nWh );
+ if ( pItem )
+ {
+ if( nHtStt > nStt )
+ {
+ if( m_nChgPos > nHtStt )
+ m_nChgPos = nHtStt;
+ break;
+ }
+ AddToStack( *pHt );
+ m_pCurrentItem = pItem;
+ if( *pEnd < m_nChgPos )
+ m_nChgPos = *pEnd;
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/txtatr2.cxx b/sw/source/core/txtnode/txtatr2.cxx
new file mode 100644
index 0000000000..cc150fc184
--- /dev/null
+++ b/sw/source/core/txtnode/txtatr2.cxx
@@ -0,0 +1,314 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <hintids.hxx>
+#include <hints.hxx>
+#include <osl/diagnose.h>
+#include <txtinet.hxx>
+#include <txtatr.hxx>
+#include <fchrfmt.hxx>
+#include <fmtinfmt.hxx>
+#include <charfmt.hxx>
+#include <ndtxt.hxx>
+#include <poolfmt.hxx>
+#include <doc.hxx>
+#include <fmtruby.hxx>
+#include <fmtmeta.hxx>
+#include <IDocumentState.hxx>
+#include <IDocumentStylePoolAccess.hxx>
+
+
+namespace {
+
+bool lcl_CheckAutoFormatHint(const SfxHint& rHint)
+{
+ if (rHint.GetId() != SfxHintId::SwAutoFormatUsedHint)
+ return false;
+ auto& rAutoFormatUsed = static_cast<const sw::AutoFormatUsedHint&>(rHint);
+ rAutoFormatUsed.SetUsed();
+ return true;
+}
+bool lcl_CheckAutoFormatHint(const SfxHint& rHint, const SwTextNode* pTextNode)
+{
+ if (rHint.GetId() != SfxHintId::SwAutoFormatUsedHint)
+ return false;
+ auto& rAutoFormatUsed = static_cast<const sw::AutoFormatUsedHint&>(rHint);
+ rAutoFormatUsed.CheckNode(pTextNode);
+ return true;
+}
+}
+
+SwTextCharFormat::SwTextCharFormat( SwFormatCharFormat& rAttr,
+ sal_Int32 nStt, sal_Int32 nEnd )
+ : SwTextAttr( rAttr, nStt )
+ , SwTextAttrEnd( rAttr, nStt, nEnd )
+ , m_pTextNode( nullptr )
+ , m_nSortNumber( 0 )
+{
+ rAttr.m_pTextAttribute = this;
+ SetCharFormatAttr( true );
+}
+
+SwTextCharFormat::~SwTextCharFormat( )
+{
+}
+
+void SwTextCharFormat::TriggerNodeUpdate(const sw::LegacyModifyHint& rHint)
+{
+ const auto nWhich = rHint.GetWhich();
+ SAL_WARN_IF(
+ !isCHRATR(nWhich) &&
+ RES_OBJECTDYING != nWhich &&
+ RES_ATTRSET_CHG != nWhich &&
+ RES_FMT_CHG != nWhich, "sw.core", "SwTextCharFormat::TriggerNodeUpdate: unknown hint type");
+
+ if(m_pTextNode)
+ {
+ SwUpdateAttr aUpdateAttr(
+ GetStart(),
+ *GetEnd(),
+ nWhich);
+ m_pTextNode->TriggerNodeUpdate(sw::LegacyModifyHint(&aUpdateAttr, &aUpdateAttr));
+ }
+}
+
+void SwTextCharFormat::HandleAutoFormatUsedHint(const sw::AutoFormatUsedHint& rHint)
+{
+ rHint.CheckNode(m_pTextNode);
+}
+
+SwTextAttrNesting::SwTextAttrNesting( SfxPoolItem & i_rAttr,
+ const sal_Int32 i_nStart, const sal_Int32 i_nEnd )
+ : SwTextAttr( i_rAttr, i_nStart )
+ , SwTextAttrEnd( i_rAttr, i_nStart, i_nEnd )
+{
+ SetDontExpand( true ); // never expand this attribute
+ // lock the expand flag: simple guarantee that nesting will not be
+ // invalidated by expand operations
+ SetLockExpandFlag( true );
+ SetDontExpandStartAttr( true );
+ SetNesting( true );
+}
+
+SwTextAttrNesting::~SwTextAttrNesting()
+{
+}
+
+SwTextINetFormat::SwTextINetFormat( SwFormatINetFormat& rAttr,
+ sal_Int32 nStart, sal_Int32 nEnd )
+ : SwTextAttr( rAttr, nStart )
+ , SwTextAttrNesting( rAttr, nStart, nEnd )
+ , SwClient( nullptr )
+ , m_pTextNode( nullptr )
+ , m_bVisited( false )
+ , m_bVisitedValid( false )
+{
+ rAttr.mpTextAttr = this;
+ SetCharFormatAttr( true );
+}
+
+SwTextINetFormat::~SwTextINetFormat( )
+{
+}
+
+SwCharFormat* SwTextINetFormat::GetCharFormat()
+{
+ const SwFormatINetFormat& rFormat = SwTextAttrEnd::GetINetFormat();
+ SwCharFormat* pRet = nullptr;
+
+ if (!rFormat.GetValue().isEmpty())
+ {
+ SwDoc& rDoc = GetTextNode().GetDoc();
+ if( !IsVisitedValid() )
+ {
+ SetVisited( rDoc.IsVisitedURL( rFormat.GetValue() ) );
+ SetVisitedValid( true );
+ }
+
+ const sal_uInt16 nId = IsVisited() ? rFormat.GetVisitedFormatId() : rFormat.GetINetFormatId();
+ const OUString& rStr = IsVisited() ? rFormat.GetVisitedFormat() : rFormat.GetINetFormat();
+ if (rStr.isEmpty())
+ {
+ OSL_ENSURE( false, "<SwTextINetFormat::GetCharFormat()> - missing character format at hyperlink attribute");
+ }
+
+ // JP 10.02.2000, Bug 72806: don't modify the doc for getting the
+ // correct charstyle.
+ bool bModifiedEnabled = rDoc.getIDocumentState().IsEnableSetModified();
+ rDoc.getIDocumentState().SetEnableSetModified(false);
+
+ pRet = IsPoolUserFormat( nId )
+ ? rDoc.FindCharFormatByName( rStr )
+ : rDoc.getIDocumentStylePoolAccess().GetCharFormatFromPool( nId );
+
+ rDoc.getIDocumentState().SetEnableSetModified(bModifiedEnabled);
+ }
+
+ if ( pRet )
+ pRet->Add( this );
+ else
+ EndListeningAll();
+
+ return pRet;
+}
+
+void SwTextINetFormat::SwClientNotify(const SwModify&, const SfxHint& rHint)
+{
+ if(lcl_CheckAutoFormatHint(rHint))
+ return;
+
+ if (rHint.GetId() != SfxHintId::SwLegacyModify)
+ return;
+ auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ const auto nWhich = pLegacy->GetWhich();
+ OSL_ENSURE(isCHRATR(nWhich) || (RES_OBJECTDYING == nWhich)
+ || (RES_ATTRSET_CHG == nWhich) || (RES_FMT_CHG == nWhich),
+ "SwTextINetFormat::SwClientNotify: unknown hint.");
+ if(!m_pTextNode)
+ return;
+
+ const SwUpdateAttr aUpdateAttr(GetStart(), *GetEnd(), nWhich);
+ m_pTextNode->TriggerNodeUpdate(sw::LegacyModifyHint(&aUpdateAttr, &aUpdateAttr));
+}
+
+bool SwTextINetFormat::IsProtect( ) const
+{
+ return m_pTextNode && m_pTextNode->IsProtect();
+}
+
+SwTextRuby::SwTextRuby( SwFormatRuby& rAttr,
+ sal_Int32 nStart, sal_Int32 nEnd )
+ : SwTextAttr( rAttr, nStart )
+ , SwTextAttrNesting( rAttr, nStart, nEnd )
+ , SwClient( nullptr )
+ , m_pTextNode( nullptr )
+{
+ rAttr.m_pTextAttr = this;
+}
+
+SwTextRuby::~SwTextRuby()
+{
+}
+
+void SwTextRuby::SwClientNotify(const SwModify&, const SfxHint& rHint)
+{
+ if(lcl_CheckAutoFormatHint(rHint, m_pTextNode))
+ return;
+ if (rHint.GetId() != SfxHintId::SwLegacyModify)
+ return;
+ auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ const auto nWhich = pLegacy->GetWhich();
+ SAL_WARN_IF( !isCHRATR(nWhich)
+ && (RES_OBJECTDYING == nWhich)
+ && (RES_ATTRSET_CHG == nWhich)
+ && (RES_FMT_CHG == nWhich), "sw.core", "SwTextRuby::SwClientNotify(): unknown legacy hint");
+ if(!m_pTextNode)
+ return;
+ SwUpdateAttr aUpdateAttr(GetStart(), *GetEnd(), nWhich);
+ m_pTextNode->TriggerNodeUpdate(sw::LegacyModifyHint(&aUpdateAttr, &aUpdateAttr));
+}
+
+SwCharFormat* SwTextRuby::GetCharFormat()
+{
+ const SwFormatRuby& rFormat = SwTextAttrEnd::GetRuby();
+ SwCharFormat* pRet = nullptr;
+
+ if( !rFormat.GetText().isEmpty() )
+ {
+ const SwDoc& rDoc = GetTextNode().GetDoc();
+ const OUString& rStr = rFormat.GetCharFormatName();
+ const sal_uInt16 nId = rStr.isEmpty()
+ ? o3tl::narrowing<sal_uInt16>(RES_POOLCHR_RUBYTEXT)
+ : rFormat.GetCharFormatId();
+
+ // JP 10.02.2000, Bug 72806: don't modify the doc for getting the
+ // correct charstyle.
+ const bool bResetMod = !rDoc.getIDocumentState().IsModified();
+ Link<bool,void> aOle2Lnk;
+ if( bResetMod )
+ {
+ aOle2Lnk = rDoc.GetOle2Link();
+ const_cast<SwDoc&>(rDoc).SetOle2Link( Link<bool,void>() );
+ }
+
+ pRet = IsPoolUserFormat( nId )
+ ? rDoc.FindCharFormatByName( rStr )
+ : const_cast<SwDoc&>(rDoc).getIDocumentStylePoolAccess().GetCharFormatFromPool( nId );
+
+ if( bResetMod )
+ {
+ const_cast<SwDoc&>(rDoc).getIDocumentState().ResetModified();
+ const_cast<SwDoc&>(rDoc).SetOle2Link( aOle2Lnk );
+ }
+ }
+
+ if( pRet )
+ pRet->Add( this );
+ else
+ EndListeningAll();
+
+ return pRet;
+}
+
+SwTextMeta *
+SwTextMeta::CreateTextMeta(
+ ::sw::MetaFieldManager & i_rTargetDocManager,
+ SwTextNode *const i_pTargetTextNode,
+ SwFormatMeta & i_rAttr,
+ sal_Int32 const i_nStart,
+ sal_Int32 const i_nEnd,
+ bool const i_bIsCopy)
+{
+ if (i_bIsCopy)
+ { // i_rAttr is already cloned, now call DoCopy to copy the sw::Meta
+ OSL_ENSURE(i_pTargetTextNode, "cannot copy Meta without target node");
+ i_rAttr.DoCopy(i_rTargetDocManager, *i_pTargetTextNode);
+ }
+ SwTextMeta *const pTextMeta(new SwTextMeta(i_rAttr, i_nStart, i_nEnd));
+ return pTextMeta;
+}
+
+SwTextMeta::SwTextMeta( SwFormatMeta & i_rAttr,
+ const sal_Int32 i_nStart, const sal_Int32 i_nEnd )
+ : SwTextAttr( i_rAttr, i_nStart )
+ , SwTextAttrNesting( i_rAttr, i_nStart, i_nEnd )
+{
+ i_rAttr.SetTextAttr( this );
+ SetHasDummyChar(true);
+}
+
+SwTextMeta::~SwTextMeta()
+{
+ SwFormatMeta & rFormatMeta( static_cast<SwFormatMeta &>(GetAttr()) );
+ if (rFormatMeta.GetTextAttr() == this)
+ {
+ rFormatMeta.SetTextAttr(nullptr);
+ }
+}
+
+void SwTextMeta::ChgTextNode(SwTextNode * const pNode)
+{
+ SwFormatMeta & rFormatMeta( static_cast<SwFormatMeta &>(GetAttr()) );
+ if (rFormatMeta.GetTextAttr() == this)
+ {
+ rFormatMeta.NotifyChangeTextNode(pNode);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/txtnode/txtedt.cxx b/sw/source/core/txtnode/txtedt.cxx
new file mode 100644
index 0000000000..2f3e7aa6db
--- /dev/null
+++ b/sw/source/core/txtnode/txtedt.cxx
@@ -0,0 +1,2404 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_wasm_strip.h>
+
+#include <hintids.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <svl/itemiter.hxx>
+#include <svl/languageoptions.hxx>
+#include <editeng/splwrap.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/hangulhanja.hxx>
+#include <i18nutil/transliteration.hxx>
+#include <linguistic/misc.hxx>
+#include <SwSmartTagMgr.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <officecfg/Office/Writer.hxx>
+#include <unotools/transliterationwrapper.hxx>
+#include <unotools/charclass.hxx>
+#include <sal/log.hxx>
+#include <swmodule.hxx>
+#include <splargs.hxx>
+#include <viewopt.hxx>
+#include <acmplwrd.hxx>
+#include <doc.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <IDocumentLayoutAccess.hxx>
+#include <docsh.hxx>
+#include <txtfld.hxx>
+#include <txatbase.hxx>
+#include <charatr.hxx>
+#include <pam.hxx>
+#include <hints.hxx>
+#include <ndtxt.hxx>
+#include <txtfrm.hxx>
+#include <SwGrammarMarkUp.hxx>
+#include <rootfrm.hxx>
+#include <swscanner.hxx>
+
+#include <breakit.hxx>
+#include <UndoOverwrite.hxx>
+#include <txatritr.hxx>
+#include <redline.hxx>
+#include <docary.hxx>
+#include <scriptinfo.hxx>
+#include <docstat.hxx>
+#include <editsh.hxx>
+#include <unotextmarkup.hxx>
+#include <txtatr.hxx>
+#include <fmtautofmt.hxx>
+#include <istyleaccess.hxx>
+#include <unicode/uchar.h>
+#include <DocumentSettingManager.hxx>
+
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+
+#include <vector>
+
+#include <unotextrange.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::frame;
+using namespace ::com::sun::star::i18n;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::linguistic2;
+using namespace ::com::sun::star::smarttags;
+
+namespace
+{
+ void DetectAndMarkMissingDictionaries( SwDoc& rDoc,
+ const uno::Reference< XSpellChecker1 >& xSpell,
+ const LanguageType eActLang )
+ {
+ if( xSpell.is() && !xSpell->hasLanguage( eActLang.get() ) )
+ rDoc.SetMissingDictionaries( true );
+ else
+ rDoc.SetMissingDictionaries( false );
+ }
+}
+
+static bool lcl_HasComments(const SwTextNode& rNode)
+{
+ sal_Int32 nPosition = rNode.GetText().indexOf(CH_TXTATR_INWORD);
+ while (nPosition != -1)
+ {
+ const SwTextAttr* pAttr = rNode.GetTextAttrForCharAt(nPosition);
+ if (pAttr && pAttr->Which() == RES_TXTATR_ANNOTATION)
+ return true;
+ nPosition = rNode.GetText().indexOf(CH_TXTATR_INWORD, nPosition + 1);
+ }
+ return false;
+}
+
+// possible delimiter characters within URLs for word breaking
+static bool lcl_IsDelim( const sal_Unicode c )
+{
+ return '#' == c || '$' == c || '%' == c || '&' == c || '+' == c ||
+ ',' == c || '-' == c || '.' == c || '/' == c || ':' == c ||
+ ';' == c || '=' == c || '?' == c || '@' == c || '_' == c;
+}
+
+// allow to check normal text with hyperlink by recognizing (parts of) URLs
+static bool lcl_IsURL(std::u16string_view rWord,
+ SwTextNode &rNode, sal_Int32 nBegin, sal_Int32 nLen)
+{
+ // not a text with hyperlink
+ if ( !rNode.GetTextAttrAt(nBegin, RES_TXTATR_INETFMT) )
+ return false;
+
+ // there is a dot in the word, which is not a period ("example.org")
+ const size_t nPosAt = rWord.find('.');
+ if (nPosAt != std::u16string_view::npos && nPosAt < rWord.length() - 1)
+ return true;
+
+ // an e-mail address ("user@example")
+ if ( rWord.find('@') != std::u16string_view::npos )
+ return true;
+
+ const OUString& rText = rNode.GetText();
+
+ // scheme (e.g. "http" in "http://" or "mailto" in "mailto:address"):
+ // word is followed by 1) ':' + an alphanumeric character; 2) or ':' + a delimiter
+ if ( nBegin + nLen + 2 <= rText.getLength() && ':' == rText[nBegin + nLen] )
+ {
+ sal_Unicode c = rText[nBegin + nLen + 1];
+ if ( u_isalnum(c) || lcl_IsDelim(c) )
+ return true;
+ }
+
+ // path, query, fragment (e.g. "path" in "example.org/path"):
+ // word is preceded by 1) an alphanumeric character + a delimiter; 2) or two delimiters
+ if ( 2 <= nBegin && lcl_IsDelim(rText[nBegin - 1]) )
+ {
+ sal_Unicode c = rText[nBegin - 2];
+ if ( u_isalnum(c) || lcl_IsDelim(c) )
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * This has basically the same function as SwScriptInfo::MaskHiddenRanges,
+ * only for deleted redlines
+ */
+
+static sal_Int32
+lcl_MaskRedlines( const SwTextNode& rNode, OUStringBuffer& rText,
+ sal_Int32 nStt, sal_Int32 nEnd,
+ const sal_Unicode cChar )
+{
+ sal_Int32 nNumOfMaskedRedlines = 0;
+
+ const SwDoc& rDoc = rNode.GetDoc();
+
+ for ( SwRedlineTable::size_type nAct = rDoc.getIDocumentRedlineAccess().GetRedlinePos( rNode, RedlineType::Any ); nAct < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); ++nAct )
+ {
+ const SwRangeRedline* pRed = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nAct ];
+
+ if ( pRed->Start()->GetNode() > rNode )
+ break;
+
+ if( RedlineType::Delete == pRed->GetType() )
+ {
+ sal_Int32 nRedlineEnd;
+ sal_Int32 nRedlineStart;
+
+ pRed->CalcStartEnd( rNode.GetIndex(), nRedlineStart, nRedlineEnd );
+
+ if ( nRedlineEnd < nStt || nRedlineStart > nEnd )
+ continue;
+
+ while ( nRedlineStart < nRedlineEnd && nRedlineStart < nEnd )
+ {
+ if (nRedlineStart >= nStt)
+ {
+ rText[nRedlineStart] = cChar;
+ ++nNumOfMaskedRedlines;
+ }
+ ++nRedlineStart;
+ }
+ }
+ }
+
+ return nNumOfMaskedRedlines;
+}
+
+/**
+ * Used for spell checking. Deleted redlines and hidden characters are masked
+ */
+static bool
+lcl_MaskRedlinesAndHiddenText( const SwTextNode& rNode, OUStringBuffer& rText,
+ sal_Int32 nStt, sal_Int32 nEnd,
+ const sal_Unicode cChar = CH_TXTATR_INWORD )
+{
+ sal_Int32 nRedlinesMasked = 0;
+ sal_Int32 nHiddenCharsMasked = 0;
+
+ const SwDoc& rDoc = rNode.GetDoc();
+ const bool bShowChg = IDocumentRedlineAccess::IsShowChanges( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() );
+
+ // If called from word count or from spell checking, deleted redlines
+ // should be masked:
+ if ( bShowChg )
+ {
+ nRedlinesMasked = lcl_MaskRedlines( rNode, rText, nStt, nEnd, cChar );
+ }
+
+ const bool bHideHidden = !SW_MOD()->GetViewOption(rDoc.GetDocumentSettingManager().get(DocumentSettingId::HTML_MODE))->IsShowHiddenChar();
+
+ // If called from word count, we want to mask the hidden ranges even
+ // if they are visible:
+ if ( bHideHidden )
+ {
+ nHiddenCharsMasked =
+ SwScriptInfo::MaskHiddenRanges( rNode, rText, nStt, nEnd, cChar );
+ }
+
+ return (nRedlinesMasked > 0) || (nHiddenCharsMasked > 0);
+}
+
+/**
+ * Used for spell checking. Calculates a rectangle for repaint.
+ */
+static SwRect lcl_CalculateRepaintRect(
+ const SwTextFrame & rTextFrame, const SwTextNode & rNode,
+ sal_Int32 const nChgStart, sal_Int32 const nChgEnd)
+{
+ TextFrameIndex const iChgStart(rTextFrame.MapModelToView(&rNode, nChgStart));
+ TextFrameIndex const iChgEnd(rTextFrame.MapModelToView(&rNode, nChgEnd));
+
+ SwRect aRect = rTextFrame.GetPaintArea();
+ SwRect aTmp = rTextFrame.GetPaintArea();
+
+ const SwTextFrame* pStartFrame = &rTextFrame;
+ while( pStartFrame->HasFollow() &&
+ iChgStart >= pStartFrame->GetFollow()->GetOffset())
+ pStartFrame = pStartFrame->GetFollow();
+ const SwTextFrame* pEndFrame = pStartFrame;
+ while( pEndFrame->HasFollow() &&
+ iChgEnd >= pEndFrame->GetFollow()->GetOffset())
+ pEndFrame = pEndFrame->GetFollow();
+
+ bool bSameFrame = true;
+
+ if( rTextFrame.HasFollow() )
+ {
+ if( pEndFrame != pStartFrame )
+ {
+ bSameFrame = false;
+ SwRect aStFrame( pStartFrame->GetPaintArea() );
+ {
+ SwRectFnSet aRectFnSet(pStartFrame);
+ aRectFnSet.SetLeft( aTmp, aRectFnSet.GetLeft(aStFrame) );
+ aRectFnSet.SetRight( aTmp, aRectFnSet.GetRight(aStFrame) );
+ aRectFnSet.SetBottom( aTmp, aRectFnSet.GetBottom(aStFrame) );
+ }
+ aStFrame = pEndFrame->GetPaintArea();
+ {
+ SwRectFnSet aRectFnSet(pEndFrame);
+ aRectFnSet.SetTop( aRect, aRectFnSet.GetTop(aStFrame) );
+ aRectFnSet.SetLeft( aRect, aRectFnSet.GetLeft(aStFrame) );
+ aRectFnSet.SetRight( aRect, aRectFnSet.GetRight(aStFrame) );
+ }
+ aRect.Union( aTmp );
+ while( true )
+ {
+ pStartFrame = pStartFrame->GetFollow();
+ if( pStartFrame == pEndFrame )
+ break;
+ aRect.Union( pStartFrame->GetPaintArea() );
+ }
+ }
+ }
+ if( bSameFrame )
+ {
+ SwRectFnSet aRectFnSet(pStartFrame);
+ if( aRectFnSet.GetTop(aTmp) == aRectFnSet.GetTop(aRect) )
+ aRectFnSet.SetLeft( aRect, aRectFnSet.GetLeft(aTmp) );
+ else
+ {
+ SwRect aStFrame( pStartFrame->GetPaintArea() );
+ aRectFnSet.SetLeft( aRect, aRectFnSet.GetLeft(aStFrame) );
+ aRectFnSet.SetRight( aRect, aRectFnSet.GetRight(aStFrame) );
+ aRectFnSet.SetTop( aRect, aRectFnSet.GetTop(aTmp) );
+ }
+
+ if( aTmp.Height() > aRect.Height() )
+ aRect.Height( aTmp.Height() );
+ }
+
+ return aRect;
+}
+
+/**
+ * Used for automatic styles. Used during RstAttr.
+ */
+static bool lcl_HaveCommonAttributes( IStyleAccess& rStyleAccess,
+ const SfxItemSet* pSet1,
+ sal_uInt16 nWhichId,
+ const SfxItemSet& rSet2,
+ std::shared_ptr<SfxItemSet>& pStyleHandle )
+{
+ bool bRet = false;
+
+ std::unique_ptr<SfxItemSet> pNewSet;
+
+ if ( !pSet1 )
+ {
+ OSL_ENSURE( nWhichId, "lcl_HaveCommonAttributes not used correctly" );
+ if ( SfxItemState::SET == rSet2.GetItemState( nWhichId, false ) )
+ {
+ pNewSet = rSet2.Clone();
+ pNewSet->ClearItem( nWhichId );
+ }
+ }
+ else if ( pSet1->Count() )
+ {
+ SfxItemIter aIter( *pSet1 );
+ const SfxPoolItem* pItem = aIter.GetCurItem();
+ do
+ {
+ if ( SfxItemState::SET == rSet2.GetItemState( pItem->Which(), false ) )
+ {
+ if ( !pNewSet )
+ pNewSet = rSet2.Clone();
+ pNewSet->ClearItem( pItem->Which() );
+ }
+
+ pItem = aIter.NextItem();
+ } while (pItem);
+ }
+
+ if ( pNewSet )
+ {
+ if ( pNewSet->Count() )
+ pStyleHandle = rStyleAccess.getAutomaticStyle( *pNewSet, IStyleAccess::AUTO_STYLE_CHAR );
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+/** Delete all attributes
+ *
+ * 5 cases:
+ * 1) The attribute is completely in the deletion range:
+ * -> delete it
+ * 2) The end of the attribute is in the deletion range:
+ * -> delete it, then re-insert it with new end
+ * 3) The start of the attribute is in the deletion range:
+ * -> delete it, then re-insert it with new start
+ * 4) The attribute contains the deletion range:
+ * Split, i.e.,
+ * -> Delete, re-insert from old start to start of deletion range
+ * -> insert new attribute from end of deletion range to old end
+ * 5) The attribute is outside the deletion range
+ * -> nothing to do
+ *
+ * @param nStt starting position
+ * @param nLen length of the deletion
+ * @param nthat ???
+ * @param pSet ???
+ * @param bInclRefToxMark ???
+ */
+
+void SwTextNode::RstTextAttr(
+ sal_Int32 nStt,
+ const sal_Int32 nLen,
+ const sal_uInt16 nWhich,
+ const SfxItemSet* pSet,
+ const bool bInclRefToxMark,
+ const bool bExactRange )
+{
+ if ( !GetpSwpHints() )
+ return;
+
+ sal_Int32 nEnd = nStt + nLen;
+ {
+ // enlarge range for the reset of text attributes in case of an overlapping input field
+ const SwTextInputField* pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt(nStt, RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent));
+ if ( pTextInputField == nullptr )
+ {
+ pTextInputField = dynamic_cast<const SwTextInputField*>(GetTextAttrAt(nEnd, RES_TXTATR_INPUTFIELD, ::sw::GetTextAttrMode::Parent));
+ }
+ if ( pTextInputField != nullptr )
+ {
+ if ( nStt > pTextInputField->GetStart() )
+ {
+ nStt = pTextInputField->GetStart();
+ }
+ if ( nEnd < *(pTextInputField->End()) )
+ {
+ nEnd = *(pTextInputField->End());
+ }
+ }
+ }
+
+ bool bChanged = false;
+
+ // nMin and nMax initialized to maximum / minimum (inverse)
+ sal_Int32 nMin = m_Text.getLength();
+ sal_Int32 nMax = nStt;
+ const bool bNoLen = nMin == 0;
+
+ // We have to remember the "new" attributes that have
+ // been introduced by splitting surrounding attributes (case 2,3,4).
+ std::vector<SwTextAttr *> newAttributes;
+ std::vector<SwTextAttr *> delAttributes;
+
+ // iterate over attribute array until start of attribute is behind deletion range
+ m_pSwpHints->SortIfNeedBe(); // trigger sorting now, we don't want it during iteration
+ size_t i = 0;
+ sal_Int32 nAttrStart = sal_Int32();
+ SwTextAttr *pHt = nullptr;
+ while ( (i < m_pSwpHints->Count())
+ && ( ( ( nAttrStart = m_pSwpHints->GetWithoutResorting(i)->GetStart()) < nEnd )
+ || nLen==0 || (nEnd == nAttrStart && nAttrStart == m_Text.getLength()))
+ && !bExactRange)
+ {
+ pHt = m_pSwpHints->GetWithoutResorting(i);
+
+ // attributes without end stay in!
+ // but consider <bInclRefToxMark> used by Undo
+ const sal_Int32* const pAttrEnd = pHt->GetEnd();
+ const bool bKeepAttrWithoutEnd =
+ pAttrEnd == nullptr
+ && ( !bInclRefToxMark
+ || ( RES_TXTATR_REFMARK != pHt->Which()
+ && RES_TXTATR_TOXMARK != pHt->Which()
+ && RES_TXTATR_META != pHt->Which()
+ && RES_TXTATR_METAFIELD != pHt->Which() ) );
+ if ( bKeepAttrWithoutEnd )
+ {
+
+ i++;
+ continue;
+ }
+ // attributes with content stay in
+ if ( pHt->HasContent() )
+ {
+ ++i;
+ continue;
+ }
+
+ // Default behavior is to process all attributes:
+ bool bSkipAttr = false;
+ std::shared_ptr<SfxItemSet> pStyleHandle;
+
+ // 1. case: We want to reset only the attributes listed in pSet:
+ if ( pSet )
+ {
+ bSkipAttr = SfxItemState::SET != pSet->GetItemState( pHt->Which(), false );
+ if ( bSkipAttr && RES_TXTATR_AUTOFMT == pHt->Which() )
+ {
+ // if the current attribute is an autostyle, we have to check if the autostyle
+ // and pSet have any attributes in common. If so, pStyleHandle will contain
+ // a handle to AutoStyle / pSet:
+ bSkipAttr = !lcl_HaveCommonAttributes( getIDocumentStyleAccess(), pSet, 0, *static_cast<const SwFormatAutoFormat&>(pHt->GetAttr()).GetStyleHandle(), pStyleHandle );
+ }
+ }
+ else if ( nWhich )
+ {
+ // 2. case: We want to reset only the attributes with WhichId nWhich:
+ bSkipAttr = nWhich != pHt->Which();
+ if ( bSkipAttr && RES_TXTATR_AUTOFMT == pHt->Which() )
+ {
+ bSkipAttr = !lcl_HaveCommonAttributes( getIDocumentStyleAccess(), nullptr, nWhich, *static_cast<const SwFormatAutoFormat&>(pHt->GetAttr()).GetStyleHandle(), pStyleHandle );
+ }
+ }
+ else if ( !bInclRefToxMark )
+ {
+ // 3. case: Reset all attributes except from ref/toxmarks:
+ // skip hints with CH_TXTATR here
+ // (deleting those is ONLY allowed for UNDO!)
+ bSkipAttr = RES_TXTATR_REFMARK == pHt->Which()
+ || RES_TXTATR_TOXMARK == pHt->Which()
+ || RES_TXTATR_META == pHt->Which()
+ || RES_TXTATR_METAFIELD == pHt->Which();
+ }
+
+ if ( bSkipAttr )
+ {
+ i++;
+ continue;
+ }
+
+ if (nStt <= nAttrStart) // Case: 1,3,5
+ {
+ const sal_Int32 nAttrEnd = pAttrEnd != nullptr
+ ? *pAttrEnd
+ : nAttrStart;
+ if (nEnd > nAttrStart
+ || (nEnd == nAttrEnd && nEnd == nAttrStart)) // Case: 1,3
+ {
+ if ( nMin > nAttrStart )
+ nMin = nAttrStart;
+ if ( nMax < nAttrEnd )
+ nMax = nAttrEnd;
+ // If only a no-extent hint is deleted, no resorting is needed
+ bChanged = bChanged || nEnd > nAttrStart || bNoLen;
+ if (nAttrEnd <= nEnd) // Case: 1
+ {
+ delAttributes.push_back(pHt);
+
+ if ( pStyleHandle )
+ {
+ SwTextAttr* pNew = MakeTextAttr( GetDoc(),
+ *pStyleHandle, nAttrStart, nAttrEnd );
+ newAttributes.push_back(pNew);
+ }
+ }
+ else // Case: 3
+ {
+ bChanged = true;
+ m_pSwpHints->NoteInHistory( pHt );
+ // UGLY: this may temporarily destroy the sorting!
+ pHt->SetStart(nEnd);
+ m_pSwpHints->NoteInHistory( pHt, true );
+
+ if ( pStyleHandle && nAttrStart < nEnd )
+ {
+ SwTextAttr* pNew = MakeTextAttr( GetDoc(),
+ *pStyleHandle, nAttrStart, nEnd );
+ newAttributes.push_back(pNew);
+ }
+ }
+ }
+ }
+ else if (pAttrEnd != nullptr) // Case: 2,4,5
+ {
+ if (*pAttrEnd > nStt) // Case: 2,4
+ {
+ if (*pAttrEnd < nEnd) // Case: 2
+ {
+ if ( nMin > nAttrStart )
+ nMin = nAttrStart;
+ if ( nMax < *pAttrEnd )
+ nMax = *pAttrEnd;
+ bChanged = true;
+
+ const sal_Int32 nAttrEnd = *pAttrEnd;
+
+ m_pSwpHints->NoteInHistory( pHt );
+ // UGLY: this may temporarily destroy the sorting!
+ pHt->SetEnd(nStt);
+ m_pSwpHints->NoteInHistory( pHt, true );
+
+ if ( pStyleHandle )
+ {
+ SwTextAttr* pNew = MakeTextAttr( GetDoc(),
+ *pStyleHandle, nStt, nAttrEnd );
+ newAttributes.push_back(pNew);
+ }
+ }
+ else if (nLen) // Case: 4
+ {
+ // for Length 0 both hints would be merged again by
+ // InsertHint, so leave them alone!
+ if ( nMin > nAttrStart )
+ nMin = nAttrStart;
+ if ( nMax < *pAttrEnd )
+ nMax = *pAttrEnd;
+ bChanged = true;
+ const sal_Int32 nTmpEnd = *pAttrEnd;
+ m_pSwpHints->NoteInHistory( pHt );
+ // UGLY: this may temporarily destroy the sorting!
+ pHt->SetEnd(nStt);
+ m_pSwpHints->NoteInHistory( pHt, true );
+
+ if ( pStyleHandle && nStt < nEnd )
+ {
+ SwTextAttr* pNew = MakeTextAttr( GetDoc(),
+ *pStyleHandle, nStt, nEnd );
+ newAttributes.push_back(pNew);
+ }
+
+ if( nEnd < nTmpEnd )
+ {
+ SwTextAttr* pNew = MakeTextAttr( GetDoc(),
+ pHt->GetAttr(), nEnd, nTmpEnd );
+ if ( pNew )
+ {
+ SwTextCharFormat* pCharFormat = dynamic_cast<SwTextCharFormat*>(pHt);
+ if ( pCharFormat )
+ static_txtattr_cast<SwTextCharFormat*>(pNew)->SetSortNumber(pCharFormat->GetSortNumber());
+
+ newAttributes.push_back(pNew);
+ }
+ }
+ }
+ }
+ }
+ ++i;
+ }
+
+ if (bExactRange)
+ {
+ // Only delete the hints which start at nStt and end at nEnd.
+ for (i = 0; i < m_pSwpHints->Count(); ++i)
+ {
+ SwTextAttr* pHint = m_pSwpHints->Get(i);
+ if ( (isTXTATR_WITHEND(pHint->Which()) && RES_TXTATR_AUTOFMT != pHint->Which())
+ || pHint->GetStart() != nStt)
+ continue;
+
+ const sal_Int32* pHintEnd = pHint->GetEnd();
+ if (!pHintEnd || *pHintEnd != nEnd)
+ continue;
+
+ delAttributes.push_back(pHint);
+ }
+ }
+
+ if (bChanged && !delAttributes.empty())
+ { // Delete() calls GetStartOf() - requires sorted hints!
+ m_pSwpHints->Resort();
+ }
+
+ // delay deleting the hints because it re-sorts the hints array
+ for (SwTextAttr *const pDel : delAttributes)
+ {
+ m_pSwpHints->Delete(pDel);
+ DestroyAttr(pDel);
+ }
+
+ // delay inserting the hints because it re-sorts the hints array
+ for (SwTextAttr *const pNew : newAttributes)
+ {
+ InsertHint(pNew, SetAttrMode::NOHINTADJUST);
+ }
+
+ TryDeleteSwpHints();
+
+ if (!bChanged)
+ return;
+
+ if ( HasHints() )
+ { // possibly sometimes Resort would be sufficient, but...
+ m_pSwpHints->MergePortions(*this);
+ }
+
+ // TextFrame's respond to aHint, others to aNew
+ SwUpdateAttr aHint(
+ nMin,
+ nMax,
+ 0);
+
+ CallSwClientNotify(sw::LegacyModifyHint(nullptr, &aHint));
+ SwFormatChg aNew( GetFormatColl() );
+ CallSwClientNotify(sw::LegacyModifyHint(nullptr, &aNew));
+}
+
+static sal_Int32 clipIndexBounds(std::u16string_view aStr, sal_Int32 nPos)
+{
+ if (nPos < 0)
+ return 0;
+ if (nPos > sal_Int32(aStr.size()))
+ return aStr.size();
+ return nPos;
+}
+
+// Return current word:
+// Search from left to right, so find the word before nPos.
+// Except if at the start of the paragraph, then return the first word.
+// If the first word consists only of whitespace, return an empty string.
+OUString SwTextFrame::GetCurWord(SwPosition const& rPos) const
+{
+ TextFrameIndex const nPos(MapModelToViewPos(rPos));
+ SwTextNode *const pTextNode(rPos.GetNode().GetTextNode());
+ assert(pTextNode);
+ OUString const& rText(GetText());
+ assert(sal_Int32(nPos) <= rText.getLength()); // invalid index
+
+ if (rText.isEmpty() || IsHiddenNow())
+ return OUString();
+
+ assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
+ const uno::Reference< XBreakIterator > &rxBreak = g_pBreakIt->GetBreakIter();
+ sal_Int16 nWordType = WordType::DICTIONARY_WORD;
+ lang::Locale aLocale( g_pBreakIt->GetLocale(pTextNode->GetLang(rPos.GetContentIndex())) );
+ Boundary aBndry =
+ rxBreak->getWordBoundary(rText, sal_Int32(nPos), aLocale, nWordType, true);
+
+ // if no word was found use previous word (if any)
+ if (aBndry.startPos == aBndry.endPos)
+ {
+ aBndry = rxBreak->previousWord(rText, sal_Int32(nPos), aLocale, nWordType);
+ }
+
+ // check if word was found and if it uses a symbol font, if so
+ // enforce returning an empty string
+ if (aBndry.endPos != aBndry.startPos
+ && IsSymbolAt(TextFrameIndex(aBndry.startPos)))
+ {
+ aBndry.endPos = aBndry.startPos;
+ }
+
+ // can have -1 as start/end of bounds not found
+ aBndry.startPos = clipIndexBounds(rText, aBndry.startPos);
+ aBndry.endPos = clipIndexBounds(rText, aBndry.endPos);
+
+ return rText.copy(aBndry.startPos,
+ aBndry.endPos - aBndry.startPos);
+}
+
+SwScanner::SwScanner( const SwTextNode& rNd, const OUString& rText,
+ const LanguageType* pLang, const ModelToViewHelper& rConvMap,
+ sal_uInt16 nType, sal_Int32 nStart, sal_Int32 nEnd, bool bClp )
+ : SwScanner(
+ [&rNd](sal_Int32 const nBegin, sal_uInt16 const nScript, bool const bNoChar)
+ { return rNd.GetLang(nBegin, bNoChar ? 0 : 1, nScript); }
+ , rText, pLang, rConvMap, nType, nStart, nEnd, bClp)
+{
+}
+
+SwScanner::SwScanner(std::function<LanguageType(sal_Int32, sal_Int32, bool)> aGetLangOfChar,
+ OUString aText, const LanguageType* pLang,
+ ModelToViewHelper aConvMap, sal_uInt16 nType, sal_Int32 nStart,
+ sal_Int32 nEnd, bool bClp)
+ : m_pGetLangOfChar(std::move(aGetLangOfChar))
+ , m_aPreDashReplacementText(std::move(aText))
+ , m_pLanguage(pLang)
+ , m_ModelToView(std::move(aConvMap))
+ , m_nLength(0)
+ , m_nOverriddenDashCount(0)
+ , m_nWordType(nType)
+ , m_bClip(bClp)
+{
+ m_nStartPos = m_nBegin = nStart;
+ m_nEndPos = nEnd;
+
+ //MSWord f.e has special emdash and endash behaviour in that they break
+ //words for the purposes of word counting, while a hyphen etc. doesn't.
+
+ //The default configuration treats emdash/endash as a word break, but
+ //additional ones can be added in under tools->options
+ if (m_nWordType == i18n::WordType::WORD_COUNT)
+ {
+ OUString sDashes = officecfg::Office::Writer::WordCount::AdditionalSeparators::get();
+ OUStringBuffer aBuf(m_aPreDashReplacementText);
+ for (sal_Int32 i = m_nStartPos; i < m_nEndPos; ++i)
+ {
+ if (i < 0)
+ continue;
+ sal_Unicode cChar = aBuf[i];
+ if (sDashes.indexOf(cChar) != -1)
+ {
+ aBuf[i] = ' ';
+ ++m_nOverriddenDashCount;
+ }
+ }
+ m_aText = aBuf.makeStringAndClear();
+ }
+ else
+ m_aText = m_aPreDashReplacementText;
+
+ assert(m_aPreDashReplacementText.getLength() == m_aText.getLength());
+
+ if ( m_pLanguage )
+ {
+ m_aCurrentLang = *m_pLanguage;
+ }
+ else
+ {
+ ModelToViewHelper::ModelPosition aModelBeginPos =
+ m_ModelToView.ConvertToModelPosition( m_nBegin );
+ m_aCurrentLang = m_pGetLangOfChar(aModelBeginPos.mnPos, 0, true);
+ }
+}
+
+namespace
+{
+ //fdo#45271 for Asian words count characters instead of words
+ sal_Int32 forceEachAsianCodePointToWord(const OUString &rText, sal_Int32 nBegin, sal_Int32 nLen)
+ {
+ if (nLen > 1)
+ {
+ const uno::Reference< XBreakIterator > &rxBreak = g_pBreakIt->GetBreakIter();
+
+ sal_uInt16 nCurrScript = rxBreak->getScriptType( rText, nBegin );
+
+ sal_Int32 indexUtf16 = nBegin;
+ rText.iterateCodePoints(&indexUtf16);
+
+ //First character is Asian, consider it a word :-(
+ if (nCurrScript == i18n::ScriptType::ASIAN)
+ {
+ nLen = indexUtf16 - nBegin;
+ return nLen;
+ }
+
+ //First character was not Asian, consider appearance of any Asian character
+ //to be the end of the word
+ while (indexUtf16 < nBegin + nLen)
+ {
+ nCurrScript = rxBreak->getScriptType( rText, indexUtf16 );
+ if (nCurrScript == i18n::ScriptType::ASIAN)
+ {
+ nLen = indexUtf16 - nBegin;
+ return nLen;
+ }
+ rText.iterateCodePoints(&indexUtf16);
+ }
+ }
+ return nLen;
+ }
+}
+
+bool SwScanner::NextWord()
+{
+ m_nBegin = m_nBegin + m_nLength;
+ Boundary aBound;
+
+ std::optional<CharClass> xLocalCharClass;
+
+ while ( true )
+ {
+ // skip non-letter characters:
+ while (m_nBegin < m_aText.getLength())
+ {
+ if (m_nBegin >= 0 && !u_isspace(m_aText[m_nBegin]))
+ {
+ if ( !m_pLanguage )
+ {
+ const sal_uInt16 nNextScriptType = g_pBreakIt->GetBreakIter()->getScriptType( m_aText, m_nBegin );
+ ModelToViewHelper::ModelPosition aModelBeginPos =
+ m_ModelToView.ConvertToModelPosition( m_nBegin );
+ m_aCurrentLang = m_pGetLangOfChar(aModelBeginPos.mnPos, nNextScriptType, false);
+ }
+
+ if ( m_nWordType != i18n::WordType::WORD_COUNT )
+ {
+ xLocalCharClass.emplace(LanguageTag( g_pBreakIt->GetLocale( m_aCurrentLang ) ));
+ if ( xLocalCharClass->isLetterNumeric(OUString(m_aText[m_nBegin])) )
+ break;
+ }
+ else
+ break;
+ }
+ ++m_nBegin;
+ }
+
+ if ( m_nBegin >= m_aText.getLength() || m_nBegin >= m_nEndPos )
+ return false;
+
+ // get the word boundaries
+ aBound = g_pBreakIt->GetBreakIter()->getWordBoundary( m_aText, m_nBegin,
+ g_pBreakIt->GetLocale( m_aCurrentLang ), m_nWordType, true );
+ OSL_ENSURE( aBound.endPos >= aBound.startPos, "broken aBound result" );
+
+ // we don't want to include preceding text
+ // to count words in text with mixed script punctuation correctly,
+ // but we want to include preceding symbols (eg. percent sign, section sign,
+ // degree sign defined by dict_word_hu to spell check their affixed forms).
+ if (m_nWordType == i18n::WordType::WORD_COUNT && aBound.startPos < m_nBegin)
+ aBound.startPos = m_nBegin;
+
+ //no word boundaries could be found
+ if(aBound.endPos == aBound.startPos)
+ return false;
+
+ //if a word before is found it has to be searched for the next
+ if(aBound.endPos == m_nBegin)
+ ++m_nBegin;
+ else
+ break;
+ } // end while( true )
+
+ // #i89042, as discussed with HDU: don't evaluate script changes for word count. Use whole word.
+ if ( m_nWordType == i18n::WordType::WORD_COUNT )
+ {
+ m_nBegin = std::max(aBound.startPos, m_nBegin);
+ m_nLength = 0;
+ if (aBound.endPos > m_nBegin)
+ m_nLength = aBound.endPos - m_nBegin;
+ }
+ else
+ {
+ // we have to differentiate between these cases:
+ if ( aBound.startPos <= m_nBegin )
+ {
+ OSL_ENSURE( aBound.endPos >= m_nBegin, "Unexpected aBound result" );
+
+ // restrict boundaries to script boundaries and nEndPos
+ const sal_uInt16 nCurrScript = g_pBreakIt->GetBreakIter()->getScriptType( m_aText, m_nBegin );
+ OUString aTmpWord = m_aText.copy( m_nBegin, aBound.endPos - m_nBegin );
+ const sal_Int32 nScriptEnd = m_nBegin +
+ g_pBreakIt->GetBreakIter()->endOfScript( aTmpWord, 0, nCurrScript );
+ const sal_Int32 nEnd = std::min( aBound.endPos, nScriptEnd );
+
+ // restrict word start to last script change position
+ sal_Int32 nScriptBegin = 0;
+ if ( aBound.startPos < m_nBegin )
+ {
+ // search from nBegin backwards until the next script change
+ aTmpWord = m_aText.copy( aBound.startPos,
+ m_nBegin - aBound.startPos + 1 );
+ nScriptBegin = aBound.startPos +
+ g_pBreakIt->GetBreakIter()->beginOfScript( aTmpWord, m_nBegin - aBound.startPos,
+ nCurrScript );
+ }
+
+ m_nBegin = std::max( aBound.startPos, nScriptBegin );
+ m_nLength = nEnd - m_nBegin;
+ }
+ else
+ {
+ const sal_uInt16 nCurrScript = g_pBreakIt->GetBreakIter()->getScriptType( m_aText, aBound.startPos );
+ OUString aTmpWord = m_aText.copy( aBound.startPos,
+ aBound.endPos - aBound.startPos );
+ const sal_Int32 nScriptEnd = aBound.startPos +
+ g_pBreakIt->GetBreakIter()->endOfScript( aTmpWord, 0, nCurrScript );
+ const sal_Int32 nEnd = std::min( aBound.endPos, nScriptEnd );
+ m_nBegin = aBound.startPos;
+ m_nLength = nEnd - m_nBegin;
+ }
+ }
+
+ // optionally clip the result of getWordBoundaries:
+ if ( m_bClip )
+ {
+ aBound.startPos = std::max( aBound.startPos, m_nStartPos );
+ aBound.endPos = std::min( aBound.endPos, m_nEndPos );
+ if (aBound.endPos < aBound.startPos)
+ {
+ m_nBegin = m_nEndPos;
+ m_nLength = 0; // found word is outside of search interval
+ }
+ else
+ {
+ m_nBegin = aBound.startPos;
+ m_nLength = aBound.endPos - m_nBegin;
+ }
+ }
+
+ if( ! m_nLength )
+ return false;
+
+ if ( m_nWordType == i18n::WordType::WORD_COUNT )
+ m_nLength = forceEachAsianCodePointToWord(m_aText, m_nBegin, m_nLength);
+
+ m_aPrevWord = m_aWord;
+ m_aWord = m_aPreDashReplacementText.copy( m_nBegin, m_nLength );
+
+ return true;
+}
+
+// Note: this is a clone of SwTextFrame::AutoSpell_, so keep them in sync when fixing things!
+bool SwTextNode::Spell(SwSpellArgs* pArgs)
+{
+ // modify string according to redline information and hidden text
+ const OUString aOldText( m_Text );
+ OUStringBuffer buf(m_Text);
+ const bool bContainsComments = lcl_HasComments(*this);
+ const bool bRestoreString =
+ lcl_MaskRedlinesAndHiddenText(*this, buf, 0, m_Text.getLength());
+ if (bRestoreString)
+ { // ??? UGLY: is it really necessary to modify m_Text here?
+ m_Text = buf.makeStringAndClear();
+ }
+
+ sal_Int32 nBegin = ( &pArgs->pStartPos->GetNode() != this )
+ ? 0
+ : pArgs->pStartPos->GetContentIndex();
+
+ sal_Int32 nEnd = ( &pArgs->pEndPos->GetNode() != this )
+ ? m_Text.getLength()
+ : pArgs->pEndPos->GetContentIndex();
+
+ pArgs->xSpellAlt = nullptr;
+
+ // 4 cases:
+
+ // 1. IsWrongDirty = 0 and GetWrong = 0
+ // Everything is checked and correct
+ // 2. IsWrongDirty = 0 and GetWrong = 1
+ // Everything is checked and errors are identified in the wrong list
+ // 3. IsWrongDirty = 1 and GetWrong = 0
+ // Nothing has been checked
+ // 4. IsWrongDirty = 1 and GetWrong = 1
+ // Text has been checked but there is an invalid range in the wrong list
+
+ // Nothing has to be done for case 1.
+ if ( ( IsWrongDirty() || GetWrong() ) && m_Text.getLength() )
+ {
+ if (nBegin > m_Text.getLength())
+ {
+ nBegin = m_Text.getLength();
+ }
+ if (nEnd > m_Text.getLength())
+ {
+ nEnd = m_Text.getLength();
+ }
+
+ if(!IsWrongDirty())
+ {
+ const sal_Int32 nTemp = GetWrong()->NextWrong( nBegin );
+ if(nTemp > nEnd)
+ {
+ // reset original text
+ if ( bRestoreString )
+ {
+ m_Text = aOldText;
+ }
+ return false;
+ }
+ if(nTemp > nBegin)
+ nBegin = nTemp;
+
+ }
+
+ // In case 2. we pass the wrong list to the scanned, because only
+ // the words in the wrong list have to be checked
+ SwScanner aScanner( *this, m_Text, nullptr, ModelToViewHelper(),
+ WordType::DICTIONARY_WORD,
+ nBegin, nEnd );
+ bool bNextWord = aScanner.NextWord();
+ while( !pArgs->xSpellAlt.is() && bNextWord )
+ {
+ bool bCalledNextWord = false;
+
+ const OUString& rWord = aScanner.GetWord();
+
+ // get next language for next word, consider language attributes
+ // within the word
+ LanguageType eActLang = aScanner.GetCurrentLanguage();
+ DetectAndMarkMissingDictionaries( GetTextNode()->GetDoc(), pArgs->xSpeller, eActLang );
+
+ if( rWord.getLength() > 0 && LANGUAGE_NONE != eActLang &&
+ !lcl_IsURL(rWord, *this, aScanner.GetBegin(), aScanner.GetLen() ) )
+ {
+ if (pArgs->xSpeller.is())
+ {
+ SvxSpellWrapper::CheckSpellLang( pArgs->xSpeller, eActLang );
+ pArgs->xSpellAlt = pArgs->xSpeller->spell( rWord, static_cast<sal_uInt16>(eActLang),
+ Sequence< PropertyValue >() );
+ }
+ if( pArgs->xSpellAlt.is() )
+ {
+ if ( IsSymbolAt(aScanner.GetBegin()) ||
+ // redlines can leave "in word" character within word,
+ // we must remove them before spell checking
+ // to avoid false alarm
+ ( (bRestoreString || bContainsComments) && pArgs->xSpeller->isValid( rWord.replaceAll(OUStringChar(CH_TXTATR_INWORD), ""),
+ static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ) ) )
+ {
+ pArgs->xSpellAlt = nullptr;
+ }
+ else
+ {
+ OUString sPrevWord = aScanner.GetPrevWord();
+ auto nWordBegin = aScanner.GetBegin();
+ auto nWordEnd = aScanner.GetEnd();
+ bNextWord = aScanner.NextWord();
+ const OUString& rActualWord = aScanner.GetPrevWord();
+ bCalledNextWord = true;
+ // check space separated word pairs in the dictionary, e.g. "vice versa"
+ if ( !((bNextWord && !linguistic::HasDigits(aScanner.GetWord()) &&
+ pArgs->xSpeller->isValid( rActualWord + " " + aScanner.GetWord(),
+ static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() )) ||
+ ( !sPrevWord.isEmpty() && !linguistic::HasDigits(sPrevWord) &&
+ pArgs->xSpeller->isValid( sPrevWord + " " + rActualWord,
+ static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ))) )
+ {
+ // make sure the selection build later from the data
+ // below does not include "in word" character to the
+ // left and right in order to preserve those. Therefore
+ // count those "in words" in order to modify the
+ // selection accordingly.
+ const sal_Unicode* pChar = aScanner.GetPrevWord().getStr();
+ sal_Int32 nLeft = 0;
+ while (*pChar++ == CH_TXTATR_INWORD)
+ ++nLeft;
+ pChar = rActualWord.getLength() ? rActualWord.getStr() + rActualWord.getLength() - 1 : nullptr;
+ sal_Int32 nRight = 0;
+ while (pChar && *pChar-- == CH_TXTATR_INWORD)
+ ++nRight;
+
+ pArgs->pStartPos->Assign(*this, nWordEnd - nRight );
+ pArgs->pEndPos->Assign(*this, nWordBegin + nLeft );
+ }
+ else
+ {
+ pArgs->xSpellAlt = nullptr;
+ }
+ }
+ }
+ }
+
+ if ( !bCalledNextWord )
+ bNextWord = aScanner.NextWord();
+ }
+ }
+
+ // reset original text
+ if ( bRestoreString )
+ {
+ m_Text = aOldText;
+ }
+
+ return pArgs->xSpellAlt.is();
+}
+
+void SwTextNode::SetLanguageAndFont( const SwPaM &rPaM,
+ LanguageType nLang, sal_uInt16 nLangWhichId,
+ const vcl::Font *pFont, sal_uInt16 nFontWhichId )
+{
+ SwEditShell *pEditShell = GetDoc().GetEditShell();
+ if (!pEditShell)
+ return;
+ SfxItemSet aSet(pEditShell->GetAttrPool(), nLangWhichId, nLangWhichId );
+ if (pFont)
+ aSet.MergeRange(nFontWhichId, nFontWhichId); // Keep it sorted
+ aSet.Put( SvxLanguageItem( nLang, nLangWhichId ) );
+
+ OSL_ENSURE( pFont, "target font missing?" );
+ if (pFont)
+ {
+ SvxFontItem aFontItem = static_cast<const SvxFontItem&>( aSet.Get( nFontWhichId ) );
+ aFontItem.SetFamilyName( pFont->GetFamilyName());
+ aFontItem.SetFamily( pFont->GetFamilyType());
+ aFontItem.SetStyleName( pFont->GetStyleName());
+ aFontItem.SetPitch( pFont->GetPitch());
+ aFontItem.SetCharSet( pFont->GetCharSet() );
+ aSet.Put( aFontItem );
+ }
+
+ GetDoc().getIDocumentContentOperations().InsertItemSet( rPaM, aSet );
+ // SetAttr( aSet ); <- Does not set language attribute of empty paragraphs correctly,
+ // <- because since there is no selection the flag to garbage
+ // <- collect all attributes is set, and therefore attributes spanned
+ // <- over empty selection are removed.
+
+}
+
+bool SwTextNode::Convert( SwConversionArgs &rArgs )
+{
+ // get range of text within node to be converted
+ // (either all the text or the text within the selection
+ // when the conversion was started)
+ const sal_Int32 nTextBegin = ( &rArgs.pStartPos->GetNode() == this )
+ ? std::min(rArgs.pStartPos->GetContentIndex(), m_Text.getLength())
+ : 0;
+
+ const sal_Int32 nTextEnd = ( &rArgs.pEndPos->GetNode() == this )
+ ? std::min(rArgs.pEndPos->GetContentIndex(), m_Text.getLength())
+ : m_Text.getLength();
+
+ rArgs.aConvText.clear();
+
+ // modify string according to redline information and hidden text
+ const OUString aOldText( m_Text );
+ OUStringBuffer buf(m_Text);
+ const bool bRestoreString =
+ lcl_MaskRedlinesAndHiddenText(*this, buf, 0, m_Text.getLength());
+ if (bRestoreString)
+ { // ??? UGLY: is it really necessary to modify m_Text here?
+ m_Text = buf.makeStringAndClear();
+ }
+
+ bool bFound = false;
+ sal_Int32 nBegin = nTextBegin;
+ sal_Int32 nLen = 0;
+ LanguageType nLangFound = LANGUAGE_NONE;
+ if (m_Text.isEmpty())
+ {
+ if (rArgs.bAllowImplicitChangesForNotConvertibleText)
+ {
+ // create SwPaM with mark & point spanning empty paragraph
+ //SwPaM aCurPaM( *this, *this, nBegin, nBegin + nLen ); <-- wrong c-tor, does sth different
+ SwPaM aCurPaM( *this, 0 );
+
+ SetLanguageAndFont( aCurPaM,
+ rArgs.nConvTargetLang, RES_CHRATR_CJK_LANGUAGE,
+ rArgs.pTargetFont, RES_CHRATR_CJK_FONT );
+ }
+ }
+ else
+ {
+ SwLanguageIterator aIter( *this, nBegin );
+
+ // Implicit changes require setting new attributes, which in turn destroys
+ // the attribute sequence on that aIter iterates. We store the necessary
+ // coordinates and apply those changes after iterating through the text.
+ typedef std::pair<sal_Int32, sal_Int32> ImplicitChangesRange;
+ std::vector<ImplicitChangesRange> aImplicitChanges;
+
+ // find non zero length text portion of appropriate language
+ do {
+ nLangFound = aIter.GetLanguage();
+ bool bLangOk = (nLangFound == rArgs.nConvSrcLang) ||
+ (editeng::HangulHanjaConversion::IsChinese( nLangFound ) &&
+ editeng::HangulHanjaConversion::IsChinese( rArgs.nConvSrcLang ));
+
+ sal_Int32 nChPos = aIter.GetChgPos();
+ // the position at the end of the paragraph is COMPLETE_STRING and
+ // thus must be cut to the end of the actual string.
+ assert(nChPos != -1);
+ if (nChPos == -1 || nChPos == COMPLETE_STRING)
+ {
+ nChPos = m_Text.getLength();
+ }
+
+ nLen = nChPos - nBegin;
+ bFound = bLangOk && nLen > 0;
+ if (!bFound)
+ {
+ // create SwPaM with mark & point spanning the attributed text
+ //SwPaM aCurPaM( *this, *this, nBegin, nBegin + nLen ); <-- wrong c-tor, does sth different
+ SwPaM aCurPaM( *this, nBegin );
+ aCurPaM.SetMark();
+ aCurPaM.GetPoint()->SetContent(nBegin + nLen);
+
+ // check script type of selected text
+ if (SwEditShell *pEditShell = GetDoc().GetEditShell())
+ {
+ pEditShell->Push(); // save current cursor on stack
+ pEditShell->SetSelection( aCurPaM );
+ bool bIsAsianScript = (SvtScriptType::ASIAN == pEditShell->GetScriptType());
+ pEditShell->Pop(SwCursorShell::PopMode::DeleteCurrent); // restore cursor from stack
+
+ if (!bIsAsianScript && rArgs.bAllowImplicitChangesForNotConvertibleText)
+ {
+ // Store for later use
+ aImplicitChanges.emplace_back(nBegin, nBegin+nLen);
+ }
+ }
+ nBegin = nChPos; // start of next language portion
+ }
+ } while (!bFound && aIter.Next()); /* loop while nothing was found and still sth is left to be searched */
+
+ // Apply implicit changes, if any, now that aIter is no longer used
+ for (const auto& rImplicitChange : aImplicitChanges)
+ {
+ SwPaM aPaM( *this, rImplicitChange.first );
+ aPaM.SetMark();
+ aPaM.GetPoint()->SetContent( rImplicitChange.second );
+ SetLanguageAndFont( aPaM, rArgs.nConvTargetLang, RES_CHRATR_CJK_LANGUAGE, rArgs.pTargetFont, RES_CHRATR_CJK_FONT );
+ }
+
+ }
+
+ // keep resulting text within selection / range of text to be converted
+ if (nBegin < nTextBegin)
+ nBegin = nTextBegin;
+ if (nBegin + nLen > nTextEnd)
+ nLen = nTextEnd - nBegin;
+ bool bInSelection = nBegin < nTextEnd;
+
+ if (bFound && bInSelection) // convertible text found within selection/range?
+ {
+ OSL_ENSURE( !m_Text.isEmpty(), "convertible text portion missing!" );
+ rArgs.aConvText = m_Text.copy(nBegin, nLen);
+ rArgs.nConvTextLang = nLangFound;
+
+ // position where to start looking in next iteration (after current ends)
+ rArgs.pStartPos->Assign(*this, nBegin + nLen );
+ // end position (when we have travelled over the whole document)
+ rArgs.pEndPos->Assign(*this, nBegin );
+ }
+
+ // restore original text
+ if ( bRestoreString )
+ {
+ m_Text = aOldText;
+ }
+
+ return !rArgs.aConvText.isEmpty();
+}
+
+// Note: this is a clone of SwTextNode::Spell, so keep them in sync when fixing things!
+SwRect SwTextFrame::AutoSpell_(SwTextNode & rNode, sal_Int32 nActPos)
+{
+ SwRect aRect;
+ assert(sw::FrameContainsNode(*this, rNode.GetIndex()));
+ SwTextNode *const pNode(&rNode);
+ if (!nActPos)
+ nActPos = COMPLETE_STRING;
+
+ SwAutoCompleteWord& rACW = SwDoc::GetAutoCompleteWords();
+
+ // modify string according to redline information and hidden text
+ const OUString aOldText( pNode->GetText() );
+ OUStringBuffer buf(pNode->m_Text);
+ const bool bContainsComments = lcl_HasComments(rNode);
+ const bool bRestoreString =
+ lcl_MaskRedlinesAndHiddenText(*pNode, buf, 0, pNode->GetText().getLength());
+ if (bRestoreString)
+ { // ??? UGLY: is it really necessary to modify m_Text here? just for GetLang()?
+ pNode->m_Text = buf.makeStringAndClear();
+ }
+
+ // a change of data indicates that at least one word has been modified
+
+ sal_Int32 nBegin = 0;
+ sal_Int32 nEnd = pNode->GetText().getLength();
+ sal_Int32 nInsertPos = 0;
+ sal_Int32 nChgStart = COMPLETE_STRING;
+ sal_Int32 nChgEnd = 0;
+ sal_Int32 nInvStart = COMPLETE_STRING;
+ sal_Int32 nInvEnd = 0;
+
+ const bool bAddAutoCmpl = pNode->IsAutoCompleteWordDirty() &&
+ SwViewOption::IsAutoCompleteWords();
+
+ if( pNode->GetWrong() )
+ {
+ nBegin = pNode->GetWrong()->GetBeginInv();
+ if( COMPLETE_STRING != nBegin )
+ {
+ nEnd = std::max(pNode->GetWrong()->GetEndInv(), pNode->GetText().getLength());
+ }
+
+ // get word around nBegin, we start at nBegin - 1
+ if ( COMPLETE_STRING != nBegin )
+ {
+ if ( nBegin )
+ --nBegin;
+
+ LanguageType eActLang = pNode->GetLang( nBegin );
+ Boundary aBound =
+ g_pBreakIt->GetBreakIter()->getWordBoundary( pNode->GetText(), nBegin,
+ g_pBreakIt->GetLocale( eActLang ),
+ WordType::DICTIONARY_WORD, true );
+ nBegin = aBound.startPos;
+ }
+
+ // get the position in the wrong list
+ nInsertPos = pNode->GetWrong()->GetWrongPos( nBegin );
+
+ // sometimes we have to skip one entry
+ if( nInsertPos < pNode->GetWrong()->Count() &&
+ nBegin == pNode->GetWrong()->Pos( nInsertPos ) +
+ pNode->GetWrong()->Len( nInsertPos ) )
+ nInsertPos++;
+ }
+
+ bool bFresh = nBegin < nEnd;
+ bool bPending(false);
+
+ if( bFresh )
+ {
+ uno::Reference< XSpellChecker1 > xSpell( ::GetSpellChecker() );
+ SwDoc& rDoc = pNode->GetDoc();
+
+ SwScanner aScanner( *pNode, pNode->GetText(), nullptr, ModelToViewHelper(),
+ WordType::DICTIONARY_WORD, nBegin, nEnd);
+
+ bool bNextWord = aScanner.NextWord();
+ while( bNextWord )
+ {
+ const OUString& rWord = aScanner.GetWord();
+ nBegin = aScanner.GetBegin();
+ sal_Int32 nLen = aScanner.GetLen();
+ bool bCalledNextWord = false;
+
+ // get next language for next word, consider language attributes
+ // within the word
+ LanguageType eActLang = aScanner.GetCurrentLanguage();
+ DetectAndMarkMissingDictionaries( rDoc, xSpell, eActLang );
+
+ bool bSpell = xSpell.is() && xSpell->hasLanguage( static_cast<sal_uInt16>(eActLang) );
+ if( bSpell && !rWord.isEmpty() && !lcl_IsURL(rWord, *pNode, nBegin, nLen) )
+ {
+ // check for: bAlter => xHyphWord.is()
+ OSL_ENSURE(!bSpell || xSpell.is(), "NULL pointer");
+ if( !xSpell->isValid( rWord, static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ) &&
+ // redlines can leave "in word" character within word,
+ // we must remove them before spell checking
+ // to avoid false alarm
+ ((!bRestoreString && !bContainsComments) || !xSpell->isValid( rWord.replaceAll(OUStringChar(CH_TXTATR_INWORD), ""),
+ static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ) ) )
+ {
+ OUString sPrevWord = aScanner.GetPrevWord();
+ bNextWord = aScanner.NextWord();
+ bCalledNextWord = true;
+ // check space separated word pairs in the dictionary, e.g. "vice versa"
+ if ( !((bNextWord && !linguistic::HasDigits(aScanner.GetWord()) &&
+ xSpell->isValid( aScanner.GetPrevWord() + " " + aScanner.GetWord(),
+ static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() )) ||
+ (!sPrevWord.isEmpty() && !linguistic::HasDigits(sPrevWord) &&
+ xSpell->isValid( sPrevWord + " " + aScanner.GetPrevWord(),
+ static_cast<sal_uInt16>(eActLang), Sequence< PropertyValue >() ))) )
+ {
+ sal_Int32 nSmartTagStt = nBegin;
+ sal_Int32 nDummy = 1;
+ if ( !pNode->GetSmartTags() || !pNode->GetSmartTags()->InWrongWord( nSmartTagStt, nDummy ) )
+ {
+ if( !pNode->GetWrong() )
+ {
+ pNode->SetWrong( std::make_unique<SwWrongList>( WRONGLIST_SPELL ) );
+ pNode->GetWrong()->SetInvalid( 0, nEnd );
+ }
+ SwWrongList::FreshState const eState(pNode->GetWrong()->Fresh(
+ nChgStart, nChgEnd, nBegin, nLen, nInsertPos, nActPos));
+ switch (eState)
+ {
+ case SwWrongList::FreshState::FRESH:
+ pNode->GetWrong()->Insert(OUString(), nullptr, nBegin, nLen, nInsertPos++);
+ break;
+ case SwWrongList::FreshState::CURSOR:
+ bPending = true;
+ [[fallthrough]]; // to mark as invalid
+ case SwWrongList::FreshState::NOTHING:
+ nInvStart = nBegin;
+ nInvEnd = nBegin + nLen;
+ break;
+ }
+ }
+ }
+ else if( bAddAutoCmpl && rACW.GetMinWordLen() <= aScanner.GetPrevWord().getLength() )
+ {
+ // tdf#119695 only add the word if the cursor position is outside the word
+ // so that the incomplete words are not added as autocomplete candidates
+ bool bCursorOutsideWord = nActPos > nBegin + nLen || nActPos < nBegin;
+ if (bCursorOutsideWord)
+ rACW.InsertWord(aScanner.GetPrevWord(), rDoc);
+ }
+ }
+ else if( bAddAutoCmpl && rACW.GetMinWordLen() <= rWord.getLength() )
+ {
+ // tdf#119695 only add the word if the cursor position is outside the word
+ // so that the incomplete words are not added as autocomplete candidates
+ bool bCursorOutsideWord = nActPos > nBegin + nLen || nActPos < nBegin;
+ if (bCursorOutsideWord)
+ rACW.InsertWord(rWord, rDoc);
+ }
+ }
+
+ if ( !bCalledNextWord )
+ bNextWord = aScanner.NextWord();
+ }
+ }
+
+ // reset original text
+ // i63141 before calling GetCharRect(..) with formatting!
+ if ( bRestoreString )
+ {
+ pNode->m_Text = aOldText;
+ }
+ if( pNode->GetWrong() )
+ {
+ if( bFresh )
+ pNode->GetWrong()->Fresh( nChgStart, nChgEnd,
+ nEnd, 0, nInsertPos, nActPos );
+
+ // Calculate repaint area:
+
+ if( nChgStart < nChgEnd )
+ {
+ aRect = lcl_CalculateRepaintRect(*this, rNode, nChgStart, nChgEnd);
+
+ // fdo#71558 notify misspelled word to accessibility
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+ SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr;
+ if( pViewSh )
+ pViewSh->InvalidateAccessibleParaAttrs( *this );
+#endif
+ }
+
+ pNode->GetWrong()->SetInvalid( nInvStart, nInvEnd );
+ pNode->SetWrongDirty(
+ (COMPLETE_STRING != pNode->GetWrong()->GetBeginInv())
+ ? (bPending
+ ? sw::WrongState::PENDING
+ : sw::WrongState::TODO)
+ : sw::WrongState::DONE);
+ if( !pNode->GetWrong()->Count() && ! pNode->IsWrongDirty() )
+ pNode->ClearWrong();
+ }
+ else
+ pNode->SetWrongDirty(sw::WrongState::DONE);
+
+ if( bAddAutoCmpl )
+ pNode->SetAutoCompleteWordDirty( false );
+
+ return aRect;
+}
+
+/** Function: SmartTagScan
+
+ Function scans words in current text and checks them in the
+ smarttag libraries. If the check returns true to bounds of the
+ recognized words are stored into a list that is used later for drawing
+ the underline.
+
+ @return SwRect Repaint area
+*/
+SwRect SwTextFrame::SmartTagScan(SwTextNode & rNode)
+{
+ SwRect aRet;
+
+ assert(sw::FrameContainsNode(*this, rNode.GetIndex()));
+ SwTextNode *const pNode = &rNode;
+ const OUString& rText = pNode->GetText();
+
+ // Iterate over language portions
+ SmartTagMgr& rSmartTagMgr = SwSmartTagMgr::Get();
+
+ SwWrongList* pSmartTagList = pNode->GetSmartTags();
+
+ sal_Int32 nBegin = 0;
+ sal_Int32 nEnd = rText.getLength();
+
+ if ( pSmartTagList )
+ {
+ if ( pSmartTagList->GetBeginInv() != COMPLETE_STRING )
+ {
+ nBegin = pSmartTagList->GetBeginInv();
+ nEnd = std::min( pSmartTagList->GetEndInv(), rText.getLength() );
+
+ if ( nBegin < nEnd )
+ {
+ const LanguageType aCurrLang = pNode->GetLang( nBegin );
+ const css::lang::Locale aCurrLocale = g_pBreakIt->GetLocale( aCurrLang );
+ nBegin = g_pBreakIt->GetBreakIter()->beginOfSentence( rText, nBegin, aCurrLocale );
+ nEnd = g_pBreakIt->GetBreakIter()->endOfSentence(rText, nEnd, aCurrLocale);
+ if (nEnd > rText.getLength() || nEnd < 0)
+ nEnd = rText.getLength();
+ }
+ }
+ }
+
+ const sal_uInt16 nNumberOfEntries = pSmartTagList ? pSmartTagList->Count() : 0;
+ sal_uInt16 nNumberOfRemovedEntries = 0;
+ sal_uInt16 nNumberOfInsertedEntries = 0;
+
+ // clear smart tag list between nBegin and nEnd:
+ if ( 0 != nNumberOfEntries )
+ {
+ sal_Int32 nChgStart = COMPLETE_STRING;
+ sal_Int32 nChgEnd = 0;
+ const sal_uInt16 nCurrentIndex = pSmartTagList->GetWrongPos( nBegin );
+ pSmartTagList->Fresh( nChgStart, nChgEnd, nBegin, nEnd - nBegin, nCurrentIndex, COMPLETE_STRING );
+ nNumberOfRemovedEntries = nNumberOfEntries - pSmartTagList->Count();
+ }
+
+ if ( nBegin < nEnd )
+ {
+ // Expand the string:
+ const ModelToViewHelper aConversionMap(*pNode, getRootFrame() /*TODO - replace or expand fields for smart tags?*/);
+ const OUString& aExpandText = aConversionMap.getViewText();
+
+ // Ownership ov ConversionMap is passed to SwXTextMarkup object!
+ uno::Reference<text::XTextMarkup> const xTextMarkup =
+ new SwXTextMarkup(pNode, aConversionMap);
+
+ css::uno::Reference< css::frame::XController > xController = pNode->GetDoc().GetDocShell()->GetController();
+
+ SwPosition start(*pNode, nBegin);
+ SwPosition end (*pNode, nEnd);
+ rtl::Reference<SwXTextRange> xRange = SwXTextRange::CreateXTextRange(pNode->GetDoc(), start, &end);
+
+ rSmartTagMgr.RecognizeTextRange(xRange, xTextMarkup, xController);
+
+ sal_Int32 nLangBegin = nBegin;
+ sal_Int32 nLangEnd;
+
+ // smart tag recognition has to be done for each language portion:
+ SwLanguageIterator aIter( *pNode, nLangBegin );
+
+ do
+ {
+ const LanguageType nLang = aIter.GetLanguage();
+ const css::lang::Locale aLocale = g_pBreakIt->GetLocale( nLang );
+ nLangEnd = std::min<sal_Int32>( nEnd, aIter.GetChgPos() );
+
+ const sal_Int32 nExpandBegin = aConversionMap.ConvertToViewPosition( nLangBegin );
+ const sal_Int32 nExpandEnd = aConversionMap.ConvertToViewPosition( nLangEnd );
+
+ rSmartTagMgr.RecognizeString(aExpandText, xTextMarkup, xController, aLocale, nExpandBegin, nExpandEnd - nExpandBegin );
+
+ nLangBegin = nLangEnd;
+ }
+ while ( aIter.Next() && nLangEnd < nEnd );
+
+ pSmartTagList = pNode->GetSmartTags();
+
+ const sal_uInt16 nNumberOfEntriesAfterRecognize = pSmartTagList ? pSmartTagList->Count() : 0;
+ nNumberOfInsertedEntries = nNumberOfEntriesAfterRecognize - ( nNumberOfEntries - nNumberOfRemovedEntries );
+ }
+
+ if( pSmartTagList )
+ {
+ // Update WrongList stuff
+ pSmartTagList->SetInvalid( COMPLETE_STRING, 0 );
+ pNode->SetSmartTagDirty( COMPLETE_STRING != pSmartTagList->GetBeginInv() );
+
+ if( !pSmartTagList->Count() && !pNode->IsSmartTagDirty() )
+ pNode->ClearSmartTags();
+
+ // Calculate repaint area:
+ if ( nBegin < nEnd && ( 0 != nNumberOfRemovedEntries ||
+ 0 != nNumberOfInsertedEntries ) )
+ {
+ aRet = lcl_CalculateRepaintRect(*this, rNode, nBegin, nEnd);
+ }
+ }
+ else
+ pNode->SetSmartTagDirty( false );
+
+ return aRet;
+}
+
+void SwTextFrame::CollectAutoCmplWrds(SwTextNode & rNode, sal_Int32 nActPos)
+{
+ assert(sw::FrameContainsNode(*this, rNode.GetIndex())); (void) this;
+ SwTextNode *const pNode(&rNode);
+ if (!nActPos)
+ nActPos = COMPLETE_STRING;
+
+ SwDoc& rDoc = pNode->GetDoc();
+ SwAutoCompleteWord& rACW = SwDoc::GetAutoCompleteWords();
+
+ sal_Int32 nBegin = 0;
+ sal_Int32 nEnd = pNode->GetText().getLength();
+ sal_Int32 nLen;
+ bool bACWDirty = false;
+
+ if( nBegin < nEnd )
+ {
+ int nCnt = 200;
+ SwScanner aScanner( *pNode, pNode->GetText(), nullptr, ModelToViewHelper(),
+ WordType::DICTIONARY_WORD, nBegin, nEnd );
+ while( aScanner.NextWord() )
+ {
+ nBegin = aScanner.GetBegin();
+ nLen = aScanner.GetLen();
+ if( rACW.GetMinWordLen() <= nLen )
+ {
+ const OUString& rWord = aScanner.GetWord();
+
+ if( nActPos < nBegin || ( nBegin + nLen ) < nActPos )
+ {
+ if( rACW.GetMinWordLen() <= rWord.getLength() )
+ rACW.InsertWord( rWord, rDoc );
+ }
+ else
+ bACWDirty = true;
+ }
+ if( !--nCnt )
+ {
+ // don't wait for TIMER here, so we can finish big paragraphs
+ if (Application::AnyInput(VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER)))
+ return;
+ nCnt = 100;
+ }
+ }
+ }
+
+ if (!bACWDirty)
+ pNode->SetAutoCompleteWordDirty( false );
+}
+
+SwInterHyphInfoTextFrame::SwInterHyphInfoTextFrame(
+ SwTextFrame const& rFrame, SwTextNode const& rNode,
+ SwInterHyphInfo const& rHyphInfo)
+ : m_nStart(rFrame.MapModelToView(&rNode, rHyphInfo.m_nStart))
+ , m_nEnd(rFrame.MapModelToView(&rNode, rHyphInfo.m_nEnd))
+ , m_nWordStart(0)
+ , m_nWordLen(0)
+{
+}
+
+void SwInterHyphInfoTextFrame::UpdateTextNodeHyphInfo(SwTextFrame const& rFrame,
+ SwTextNode const& rNode, SwInterHyphInfo & o_rHyphInfo)
+{
+ std::pair<SwTextNode const*, sal_Int32> const wordStart(rFrame.MapViewToModel(m_nWordStart));
+ std::pair<SwTextNode const*, sal_Int32> const wordEnd(rFrame.MapViewToModel(m_nWordStart+m_nWordLen));
+ if (wordStart.first != &rNode || wordEnd.first != &rNode)
+ { // not sure if this can happen since nStart/nEnd are in rNode
+ SAL_WARN("sw.core", "UpdateTextNodeHyphInfo: outside of node");
+ return;
+ }
+ o_rHyphInfo.m_nWordStart = wordStart.second;
+ o_rHyphInfo.m_nWordLen = wordEnd.second - wordStart.second;
+ o_rHyphInfo.SetHyphWord(m_xHyphWord);
+}
+
+/// Find the SwTextFrame and call its Hyphenate
+bool SwTextNode::Hyphenate( SwInterHyphInfo &rHyphInf )
+{
+ // shortcut: paragraph doesn't have a language set:
+ if ( LANGUAGE_NONE == GetSwAttrSet().GetLanguage().GetLanguage()
+ && LanguageType(USHRT_MAX) == GetLang(0, m_Text.getLength()))
+ {
+ return false;
+ }
+
+ SwTextFrame *pFrame = ::sw::SwHyphIterCacheLastTextFrame(this,
+ [&rHyphInf, this]() {
+ std::pair<Point, bool> tmp;
+ Point const*const pPoint = rHyphInf.GetCursorPos();
+ if (pPoint)
+ {
+ tmp.first = *pPoint;
+ tmp.second = true;
+ }
+ return static_cast<SwTextFrame*>(this->getLayoutFrame(
+ this->GetDoc().getIDocumentLayoutAccess().GetCurrentLayout(),
+ nullptr, pPoint ? &tmp : nullptr));
+ });
+ if (!pFrame)
+ {
+ // There was a comment here that claimed that the following assertion
+ // shouldn't exist as it's triggered by "Trennung ueber Sonderbereiche",
+ // (hyphenation across special sections?), whatever that means.
+ OSL_ENSURE( pFrame, "!SwTextNode::Hyphenate: can't find any frame" );
+ return false;
+ }
+ SwInterHyphInfoTextFrame aHyphInfo(*pFrame, *this, rHyphInf);
+
+ pFrame = &(pFrame->GetFrameAtOfst( aHyphInfo.m_nStart ));
+
+ while( pFrame )
+ {
+ if (pFrame->Hyphenate(aHyphInfo))
+ {
+ // The layout is not robust wrt. "direct formatting"
+ // cf. layact.cxx, SwLayAction::TurboAction_(), if( !pCnt->IsValid() ...
+ pFrame->SetCompletePaint();
+ aHyphInfo.UpdateTextNodeHyphInfo(*pFrame, *this, rHyphInf);
+ return true;
+ }
+ pFrame = pFrame->GetFollow();
+ if( pFrame )
+ {
+ aHyphInfo.m_nEnd = aHyphInfo.m_nEnd - (pFrame->GetOffset() - aHyphInfo.m_nStart);
+ aHyphInfo.m_nStart = pFrame->GetOffset();
+ }
+ }
+ return false;
+}
+
+namespace
+{
+ struct swTransliterationChgData
+ {
+ sal_Int32 nStart;
+ sal_Int32 nLen;
+ OUString sChanged;
+ Sequence< sal_Int32 > aOffsets;
+ };
+}
+
+// change text to Upper/Lower/Hiragana/Katakana/...
+void SwTextNode::TransliterateText(
+ utl::TransliterationWrapper& rTrans,
+ sal_Int32 nStt, sal_Int32 nEnd,
+ SwUndoTransliterate* pUndo, bool bUseRedlining )
+{
+ if (nStt >= nEnd)
+ return;
+
+ const sal_Int32 selStart = nStt;
+ const sal_Int32 selEnd = nEnd;
+
+ // since we don't use Hiragana/Katakana or half-width/full-width transliterations here
+ // it is fine to use ANYWORD_IGNOREWHITESPACES. (ANY_WORD btw is broken and will
+ // occasionally miss words in consecutive sentences). Also with ANYWORD_IGNOREWHITESPACES
+ // text like 'just-in-time' will be converted to 'Just-In-Time' which seems to be the
+ // proper thing to do.
+ const sal_Int16 nWordType = WordType::ANYWORD_IGNOREWHITESPACES;
+
+ // In order to have less trouble with changing text size, e.g. because
+ // of ligatures or German small sz being resolved, we need to process
+ // the text replacements from end to start.
+ // This way the offsets for the yet to be changed words will be
+ // left unchanged by the already replaced text.
+ // For this we temporarily save the changes to be done in this vector
+ std::vector< swTransliterationChgData > aChanges;
+ swTransliterationChgData aChgData;
+
+ if (rTrans.getType() == TransliterationFlags::TITLE_CASE)
+ {
+ // for 'capitalize every word' we need to iterate over each word
+
+ Boundary aSttBndry;
+ Boundary aEndBndry;
+ aSttBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
+ GetText(), nStt,
+ g_pBreakIt->GetLocale( GetLang( nStt ) ),
+ nWordType,
+ true /*prefer forward direction*/);
+ aEndBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
+ GetText(), nEnd,
+ g_pBreakIt->GetLocale( GetLang( nEnd ) ),
+ nWordType,
+ false /*prefer backward direction*/);
+
+ // prevent backtracking to the previous word if selection is at word boundary
+ if (aSttBndry.endPos <= nStt)
+ {
+ aSttBndry = g_pBreakIt->GetBreakIter()->nextWord(
+ GetText(), aSttBndry.endPos,
+ g_pBreakIt->GetLocale( GetLang( aSttBndry.endPos ) ),
+ nWordType);
+ }
+ // prevent advancing to the next word if selection is at word boundary
+ if (aEndBndry.startPos >= nEnd)
+ {
+ aEndBndry = g_pBreakIt->GetBreakIter()->previousWord(
+ GetText(), aEndBndry.startPos,
+ g_pBreakIt->GetLocale( GetLang( aEndBndry.startPos ) ),
+ nWordType);
+ }
+
+ /* Nothing to do if user selection lies entirely outside of word start and end boundary computed above.
+ * Skip this node, because otherwise the below logic for constraining to the selection will fail */
+ if (aSttBndry.startPos >= selEnd || aEndBndry.endPos <= selStart) {
+ return;
+ }
+
+ // prevent going outside of the user's selection, which may
+ // start in the middle of a word
+ aSttBndry.startPos = std::max(aSttBndry.startPos, selStart);
+ aEndBndry.startPos = std::max(aSttBndry.startPos, aEndBndry.startPos);
+
+ Boundary aCurWordBndry( aSttBndry );
+ while (aCurWordBndry.startPos <= aEndBndry.startPos)
+ {
+ nStt = aCurWordBndry.startPos;
+ nEnd = aCurWordBndry.endPos;
+ const sal_Int32 nLen = nEnd - nStt;
+ OSL_ENSURE( nLen > 0, "invalid word length of 0" );
+
+ Sequence <sal_Int32> aOffsets;
+ OUString const sChgd( rTrans.transliterate(
+ GetText(), GetLang(nStt), nStt, nLen, &aOffsets) );
+
+ assert(nStt < m_Text.getLength());
+ if (0 != rtl_ustr_shortenedCompare_WithLength(
+ m_Text.getStr() + nStt, m_Text.getLength() - nStt,
+ sChgd.getStr(), sChgd.getLength(), nLen))
+ {
+ aChgData.nStart = nStt;
+ aChgData.nLen = nLen;
+ aChgData.sChanged = sChgd;
+ aChgData.aOffsets = aOffsets;
+ aChanges.push_back( aChgData );
+ }
+
+ aCurWordBndry = g_pBreakIt->GetBreakIter()->nextWord(
+ GetText(), nStt,
+ g_pBreakIt->GetLocale(GetLang(nStt, 1)),
+ nWordType);
+ }
+ }
+ else if (rTrans.getType() == TransliterationFlags::SENTENCE_CASE)
+ {
+ // For 'sentence case' we need to iterate sentence by sentence.
+ // nLastStart and nLastEnd are the boundaries of the last sentence in
+ // the user's selection.
+ sal_Int32 nLastStart = g_pBreakIt->GetBreakIter()->beginOfSentence(
+ GetText(), nEnd,
+ g_pBreakIt->GetLocale( GetLang( nEnd ) ) );
+ sal_Int32 nLastEnd = g_pBreakIt->GetBreakIter()->endOfSentence(
+ GetText(), nLastStart,
+ g_pBreakIt->GetLocale( GetLang( nLastStart ) ) );
+
+ // Begin with the starting point of the user's selection (it may not be
+ // the beginning of a sentence)...
+ sal_Int32 nCurrentStart = nStt;
+ // ...And extend to the end of the first sentence
+ sal_Int32 nCurrentEnd = g_pBreakIt->GetBreakIter()->endOfSentence(
+ GetText(), nCurrentStart,
+ g_pBreakIt->GetLocale( GetLang( nCurrentStart ) ) );
+
+ // prevent backtracking to the previous sentence if selection starts at end of a sentence
+ if (nCurrentEnd <= nStt)
+ {
+ // now nCurrentStart is probably located on a non-letter word. (unless we
+ // are in Asian text with no spaces...)
+ // Thus to get the real sentence start we should locate the next real word,
+ // that is one found by DICTIONARY_WORD
+ i18n::Boundary aBndry = g_pBreakIt->GetBreakIter()->nextWord(
+ GetText(), nCurrentEnd,
+ g_pBreakIt->GetLocale( GetLang( nCurrentEnd ) ),
+ i18n::WordType::DICTIONARY_WORD);
+
+ // now get new current sentence boundaries
+ nCurrentStart = g_pBreakIt->GetBreakIter()->beginOfSentence(
+ GetText(), aBndry.startPos,
+ g_pBreakIt->GetLocale( GetLang( aBndry.startPos) ) );
+ nCurrentEnd = g_pBreakIt->GetBreakIter()->endOfSentence(
+ GetText(), nCurrentStart,
+ g_pBreakIt->GetLocale( GetLang( nCurrentStart) ) );
+ }
+ // prevent advancing to the next sentence if selection ends at start of a sentence
+ if (nLastStart >= nEnd)
+ {
+ // now nCurrentStart is probably located on a non-letter word. (unless we
+ // are in Asian text with no spaces...)
+ // Thus to get the real sentence start we should locate the previous real word,
+ // that is one found by DICTIONARY_WORD
+ i18n::Boundary aBndry = g_pBreakIt->GetBreakIter()->previousWord(
+ GetText(), nLastStart,
+ g_pBreakIt->GetLocale( GetLang( nLastStart) ),
+ i18n::WordType::DICTIONARY_WORD);
+ nLastEnd = g_pBreakIt->GetBreakIter()->endOfSentence(
+ GetText(), aBndry.startPos,
+ g_pBreakIt->GetLocale( GetLang( aBndry.startPos) ) );
+ if (nCurrentEnd > nLastEnd)
+ nCurrentEnd = nLastEnd;
+ }
+
+ // Prevent going outside of the user's selection
+ nCurrentStart = std::max(selStart, nCurrentStart);
+ nCurrentEnd = std::min(selEnd, nCurrentEnd);
+ nLastEnd = std::min(selEnd, nLastEnd);
+
+ while (nCurrentStart < nLastEnd)
+ {
+ sal_Int32 nLen = nCurrentEnd - nCurrentStart;
+ OSL_ENSURE( nLen > 0, "invalid word length of 0" );
+
+ Sequence <sal_Int32> aOffsets;
+ OUString const sChgd( rTrans.transliterate(GetText(),
+ GetLang(nCurrentStart), nCurrentStart, nLen, &aOffsets) );
+
+ assert(nStt < m_Text.getLength());
+ if (0 != rtl_ustr_shortenedCompare_WithLength(
+ m_Text.getStr() + nStt, m_Text.getLength() - nStt,
+ sChgd.getStr(), sChgd.getLength(), nLen))
+ {
+ aChgData.nStart = nCurrentStart;
+ aChgData.nLen = nLen;
+ aChgData.sChanged = sChgd;
+ aChgData.aOffsets = aOffsets;
+ aChanges.push_back( aChgData );
+ }
+
+ Boundary aFirstWordBndry = g_pBreakIt->GetBreakIter()->nextWord(
+ GetText(), nCurrentEnd,
+ g_pBreakIt->GetLocale( GetLang( nCurrentEnd ) ),
+ nWordType);
+ nCurrentStart = aFirstWordBndry.startPos;
+ nCurrentEnd = g_pBreakIt->GetBreakIter()->endOfSentence(
+ GetText(), nCurrentStart,
+ g_pBreakIt->GetLocale( GetLang( nCurrentStart ) ) );
+ }
+ }
+ else
+ {
+ // here we may transliterate over complete language portions...
+
+ std::unique_ptr<SwLanguageIterator> pIter;
+ if( rTrans.needLanguageForTheMode() )
+ pIter.reset(new SwLanguageIterator( *this, nStt ));
+
+ sal_Int32 nEndPos = 0;
+ LanguageType nLang = LANGUAGE_NONE;
+ sal_Int32 nLoopControlRuns = 0;
+ do {
+ if( pIter )
+ {
+ nLang = pIter->GetLanguage();
+ nEndPos = pIter->GetChgPos();
+ if( nEndPos > nEnd )
+ nEndPos = nEnd;
+ }
+ else
+ {
+ nLang = LANGUAGE_SYSTEM;
+ nEndPos = nEnd;
+ }
+ const sal_Int32 nLen = nEndPos - nStt;
+
+ Sequence <sal_Int32> aOffsets;
+ OUString const sChgd( rTrans.transliterate(
+ m_Text, nLang, nStt, nLen, &aOffsets) );
+
+ assert(nStt < m_Text.getLength());
+ if (0 != rtl_ustr_shortenedCompare_WithLength(
+ m_Text.getStr() + nStt, m_Text.getLength() - nStt,
+ sChgd.getStr(), sChgd.getLength(), nLen))
+ {
+ aChgData.nStart = nStt;
+ aChgData.nLen = nLen;
+ aChgData.sChanged = sChgd;
+ aChgData.aOffsets = aOffsets;
+ aChanges.push_back( aChgData );
+ }
+
+ nStt = nEndPos;
+
+ // tdf#157937 selection containing tracked changes needs loop control:
+ // stop looping, if there are too much empty transliterations
+ if ( sChgd.isEmpty() )
+ ++nLoopControlRuns;
+
+ } while( nEndPos < nEnd && pIter && pIter->Next() && nLoopControlRuns < 100 );
+ }
+
+ if (aChanges.empty())
+ return;
+
+ // now apply the changes from end to start to leave the offsets of the
+ // yet unchanged text parts remain the same.
+ size_t nSum(0);
+
+ for (size_t i = 0; i < aChanges.size(); ++i)
+ { // check this here since AddChanges cannot be moved below
+ // call to ReplaceTextOnly
+ swTransliterationChgData & rData =
+ aChanges[ aChanges.size() - 1 - i ];
+
+ nSum += rData.sChanged.getLength() - rData.nLen;
+ if (nSum > o3tl::make_unsigned(GetSpaceLeft()))
+ {
+ SAL_WARN("sw.core", "SwTextNode::ReplaceTextOnly: "
+ "node text with insertion > node capacity.");
+ return;
+ }
+
+ if ( bUseRedlining )
+ {
+ // create SwPaM with mark & point spanning the attributed text
+ //SwPaM aCurPaM( *this, *this, nBegin, nBegin + nLen ); <-- wrong c-tor, does sth different
+ SwPaM aCurPaM( *this, rData.nStart );
+ aCurPaM.SetMark();
+ aCurPaM.GetPoint()->SetContent( rData.nStart + rData.nLen );
+ // replace the changed words
+ if ( aCurPaM.GetText() != rData.sChanged )
+ GetDoc().getIDocumentContentOperations().ReplaceRange( aCurPaM, rData.sChanged, false );
+ }
+ else
+ {
+ if (pUndo)
+ pUndo->AddChanges( *this, rData.nStart, rData.nLen, rData.aOffsets );
+ ReplaceTextOnly( rData.nStart, rData.nLen, rData.sChanged, rData.aOffsets );
+ }
+ }
+}
+
+void SwTextNode::ReplaceTextOnly( sal_Int32 nPos, sal_Int32 nLen,
+ std::u16string_view aText,
+ const Sequence<sal_Int32>& rOffsets )
+{
+ assert(sal_Int32(aText.size()) - nLen <= GetSpaceLeft());
+
+ m_Text = m_Text.replaceAt(nPos, nLen, aText);
+
+ sal_Int32 nTLen = aText.size();
+ const sal_Int32* pOffsets = rOffsets.getConstArray();
+ // now look for no 1-1 mapping -> move the indices!
+ sal_Int32 nMyOff = nPos;
+ for( sal_Int32 nI = 0; nI < nTLen; ++nI )
+ {
+ const sal_Int32 nOff = pOffsets[ nI ];
+ if( nOff < nMyOff )
+ {
+ // something is inserted
+ sal_Int32 nCnt = 1;
+ while( nI + nCnt < nTLen && nOff == pOffsets[ nI + nCnt ] )
+ ++nCnt;
+
+ Update(SwContentIndex(this, nMyOff), nCnt, UpdateMode::Default);
+ nMyOff = nOff;
+ //nMyOff -= nCnt;
+ nI += nCnt - 1;
+ }
+ else if( nOff > nMyOff )
+ {
+ // something is deleted
+ Update(SwContentIndex(this, nMyOff + 1), nOff - nMyOff, UpdateMode::Negative);
+ nMyOff = nOff;
+ }
+ ++nMyOff;
+ }
+ if( nMyOff < nLen )
+ // something is deleted at the end
+ Update(SwContentIndex(this, nMyOff), nLen - nMyOff, UpdateMode::Negative);
+
+ // notify the layout!
+ const auto aDelHint = sw::DeleteText(nPos, nTLen);
+ CallSwClientNotify(aDelHint);
+
+ const auto aInsHint = sw::MakeInsertText(*this, nPos, nTLen);
+ CallSwClientNotify(aInsHint);
+}
+
+// the return values allows us to see if we did the heavy-
+// lifting required to actually break and count the words.
+bool SwTextNode::CountWords( SwDocStat& rStat,
+ sal_Int32 nStt, sal_Int32 nEnd ) const
+{
+ if( nStt > nEnd )
+ { // bad call
+ return false;
+ }
+ if (IsInRedlines())
+ { //not counting txtnodes used to hold deleted redline content
+ return false;
+ }
+ bool bCountAll = ( (0 == nStt) && (GetText().getLength() == nEnd) );
+ ++rStat.nAllPara; // #i93174#: count _all_ paragraphs
+ if ( IsHidden() )
+ { // not counting hidden paras
+ return false;
+ }
+ // count words in numbering string if started at beginning of para:
+ bool bCountNumbering = nStt == 0;
+ bool bHasBullet = false, bHasNumbering = false;
+ OUString sNumString;
+ if (bCountNumbering)
+ {
+ sNumString = GetNumString();
+ bHasNumbering = !sNumString.isEmpty();
+ if (!bHasNumbering)
+ bHasBullet = HasBullet();
+ bCountNumbering = bHasNumbering || bHasBullet;
+ }
+
+ if( nStt == nEnd && !bCountNumbering)
+ { // unnumbered empty node or empty selection
+ if (bCountAll)
+ {
+ SetWordCountDirty( false ); // reset flag to speed up DoIdleJob
+ }
+ return false;
+ }
+
+ // count of non-empty paras
+ ++rStat.nPara;
+
+ // Shortcut when counting whole paragraph and current count is clean
+ if ( bCountAll && !IsWordCountDirty() )
+ {
+ // accumulate into DocStat record to return the values
+
+ rStat.nWord += m_aParagraphIdleData.nNumberOfWords;
+ rStat.nAsianWord += m_aParagraphIdleData.nNumberOfAsianWords;
+ rStat.nChar += m_aParagraphIdleData.nNumberOfChars;
+ rStat.nCharExcludingSpaces += m_aParagraphIdleData.nNumberOfCharsExcludingSpaces;
+ return false;
+ }
+
+ // ConversionMap to expand fields, remove invisible and redline deleted text for scanner
+ const ModelToViewHelper aConversionMap(*this,
+ getIDocumentLayoutAccess().GetCurrentLayout(),
+ ExpandMode::ExpandFields | ExpandMode::ExpandFootnote | ExpandMode::HideInvisible | ExpandMode::HideDeletions | ExpandMode::HideFieldmarkCommands);
+ const OUString& aExpandText = aConversionMap.getViewText();
+
+ if (aExpandText.isEmpty() && !bCountNumbering)
+ {
+ if (bCountAll)
+ {
+ SetWordCountDirty( false ); // reset flag to speed up DoIdleJob
+ }
+ return false;
+ }
+
+ // map start and end points onto the ConversionMap
+ const sal_Int32 nExpandBegin = aConversionMap.ConvertToViewPosition( nStt );
+ const sal_Int32 nExpandEnd = aConversionMap.ConvertToViewPosition( nEnd );
+
+ //do the count
+ // all counts exclude hidden paras and hidden+redlined within para
+ // definition of space/white chars in SwScanner (and BreakIter!)
+ // uses both u_isspace and BreakIter getWordBoundary in SwScanner
+ sal_uInt32 nTmpWords = 0; // count of all words
+ sal_uInt32 nTmpAsianWords = 0; //count of all Asian codepoints
+ sal_uInt32 nTmpChars = 0; // count of all chars
+ sal_uInt32 nTmpCharsExcludingSpaces = 0; // all non-white chars
+
+ // count words in masked and expanded text:
+ if (!aExpandText.isEmpty())
+ {
+ assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
+
+ // zero is NULL for pLanguage -----------v last param = true for clipping
+ SwScanner aScanner( *this, aExpandText, nullptr, aConversionMap, i18n::WordType::WORD_COUNT,
+ nExpandBegin, nExpandEnd, true );
+
+ // used to filter out scanner returning almost empty strings (len=1; unichar=0x0001)
+ const OUString aBreakWord( CH_TXTATR_BREAKWORD );
+
+ while ( aScanner.NextWord() )
+ {
+ if( !aExpandText.match(aBreakWord, aScanner.GetBegin() ))
+ {
+ ++nTmpWords;
+ const OUString &rWord = aScanner.GetWord();
+ if (g_pBreakIt->GetBreakIter()->getScriptType(rWord, 0) == i18n::ScriptType::ASIAN)
+ ++nTmpAsianWords;
+ nTmpCharsExcludingSpaces += g_pBreakIt->getGraphemeCount(rWord);
+ }
+ }
+
+ nTmpCharsExcludingSpaces += aScanner.getOverriddenDashCount();
+
+ nTmpChars = g_pBreakIt->getGraphemeCount(aExpandText, nExpandBegin, nExpandEnd);
+ }
+
+ // no nTmpCharsExcludingSpaces adjust needed neither for blanked out MaskedChars
+ // nor for mid-word selection - set scanner bClip = true at creation
+
+ // count outline number label - ? no expansion into map
+ // always counts all of number-ish label
+ if (bHasNumbering) // count words in numbering string
+ {
+ LanguageType aLanguage = GetLang( 0 );
+
+ SwScanner aScanner( *this, sNumString, &aLanguage, ModelToViewHelper(),
+ i18n::WordType::WORD_COUNT, 0, sNumString.getLength(), true );
+
+ while ( aScanner.NextWord() )
+ {
+ ++nTmpWords;
+ const OUString &rWord = aScanner.GetWord();
+ if (g_pBreakIt->GetBreakIter()->getScriptType(rWord, 0) == i18n::ScriptType::ASIAN)
+ ++nTmpAsianWords;
+ nTmpCharsExcludingSpaces += g_pBreakIt->getGraphemeCount(rWord);
+ }
+
+ nTmpCharsExcludingSpaces += aScanner.getOverriddenDashCount();
+ nTmpChars += g_pBreakIt->getGraphemeCount(sNumString);
+ }
+ else if ( bHasBullet )
+ {
+ ++nTmpWords;
+ ++nTmpChars;
+ ++nTmpCharsExcludingSpaces;
+ }
+
+ // If counting the whole para then update cached values and mark clean
+ if ( bCountAll )
+ {
+ m_aParagraphIdleData.nNumberOfWords = nTmpWords;
+ m_aParagraphIdleData.nNumberOfAsianWords = nTmpAsianWords;
+ m_aParagraphIdleData.nNumberOfChars = nTmpChars;
+ m_aParagraphIdleData.nNumberOfCharsExcludingSpaces = nTmpCharsExcludingSpaces;
+ SetWordCountDirty( false );
+ }
+ // accumulate into DocStat record to return the values
+ rStat.nWord += nTmpWords;
+ rStat.nAsianWord += nTmpAsianWords;
+ rStat.nChar += nTmpChars;
+ rStat.nCharExcludingSpaces += nTmpCharsExcludingSpaces;
+
+ return true;
+}
+
+void SwTextNode::SetWrong( std::unique_ptr<SwWrongList> pNew )
+{
+ m_aParagraphIdleData.pWrong = std::move(pNew);
+}
+
+void SwTextNode::ClearWrong()
+{
+ m_aParagraphIdleData.pWrong.reset();
+}
+
+std::unique_ptr<SwWrongList> SwTextNode::ReleaseWrong()
+{
+ return std::move(m_aParagraphIdleData.pWrong);
+}
+
+SwWrongList* SwTextNode::GetWrong()
+{
+ return m_aParagraphIdleData.pWrong.get();
+}
+
+// #i71360#
+const SwWrongList* SwTextNode::GetWrong() const
+{
+ return m_aParagraphIdleData.pWrong.get();
+}
+
+void SwTextNode::SetGrammarCheck( std::unique_ptr<SwGrammarMarkUp> pNew )
+{
+ m_aParagraphIdleData.pGrammarCheck = std::move(pNew);
+}
+
+void SwTextNode::ClearGrammarCheck()
+{
+ m_aParagraphIdleData.pGrammarCheck.reset();
+}
+
+std::unique_ptr<SwGrammarMarkUp> SwTextNode::ReleaseGrammarCheck()
+{
+ return std::move(m_aParagraphIdleData.pGrammarCheck);
+}
+
+SwGrammarMarkUp* SwTextNode::GetGrammarCheck()
+{
+ return m_aParagraphIdleData.pGrammarCheck.get();
+}
+
+SwWrongList const* SwTextNode::GetGrammarCheck() const
+{
+ return static_cast<SwWrongList const*>(const_cast<SwTextNode*>(this)->GetGrammarCheck());
+}
+
+void SwTextNode::SetSmartTags( std::unique_ptr<SwWrongList> pNew )
+{
+ OSL_ENSURE( !pNew || SwSmartTagMgr::Get().IsSmartTagsEnabled(),
+ "Weird - we have a smart tag list without any recognizers?" );
+
+ m_aParagraphIdleData.pSmartTags = std::move(pNew);
+}
+
+void SwTextNode::ClearSmartTags()
+{
+ m_aParagraphIdleData.pSmartTags.reset();
+}
+
+std::unique_ptr<SwWrongList> SwTextNode::ReleaseSmartTags()
+{
+ return std::move(m_aParagraphIdleData.pSmartTags);
+}
+
+SwWrongList* SwTextNode::GetSmartTags()
+{
+ return m_aParagraphIdleData.pSmartTags.get();
+}
+
+SwWrongList const* SwTextNode::GetSmartTags() const
+{
+ return const_cast<SwWrongList const*>(const_cast<SwTextNode*>(this)->GetSmartTags());
+}
+
+void SwTextNode::SetWordCountDirty( bool bNew ) const
+{
+ m_aParagraphIdleData.bWordCountDirty = bNew;
+}
+
+bool SwTextNode::IsWordCountDirty() const
+{
+ return m_aParagraphIdleData.bWordCountDirty;
+}
+
+void SwTextNode::SetWrongDirty(sw::WrongState eNew) const
+{
+ m_aParagraphIdleData.eWrongDirty = eNew;
+}
+
+sw::WrongState SwTextNode::GetWrongDirty() const
+{
+ return m_aParagraphIdleData.eWrongDirty;
+}
+
+bool SwTextNode::IsWrongDirty() const
+{
+ return m_aParagraphIdleData.eWrongDirty != sw::WrongState::DONE;
+}
+
+void SwTextNode::SetGrammarCheckDirty( bool bNew ) const
+{
+ m_aParagraphIdleData.bGrammarCheckDirty = bNew;
+}
+
+bool SwTextNode::IsGrammarCheckDirty() const
+{
+ return m_aParagraphIdleData.bGrammarCheckDirty;
+}
+
+void SwTextNode::SetSmartTagDirty( bool bNew ) const
+{
+ m_aParagraphIdleData.bSmartTagDirty = bNew;
+}
+
+bool SwTextNode::IsSmartTagDirty() const
+{
+ return m_aParagraphIdleData.bSmartTagDirty;
+}
+
+void SwTextNode::SetAutoCompleteWordDirty( bool bNew ) const
+{
+ m_aParagraphIdleData.bAutoComplDirty = bNew;
+}
+
+bool SwTextNode::IsAutoCompleteWordDirty() const
+{
+ return m_aParagraphIdleData.bAutoComplDirty;
+}
+
+// <-- Paragraph statistics end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */