diff options
Diffstat (limited to 'sw/source/core/txtnode')
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: */ |