summaryrefslogtreecommitdiffstats
path: root/sw/source/core/text/redlnitr.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sw/source/core/text/redlnitr.cxx
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/core/text/redlnitr.cxx')
-rw-r--r--sw/source/core/text/redlnitr.cxx1137
1 files changed, 1137 insertions, 0 deletions
diff --git a/sw/source/core/text/redlnitr.cxx b/sw/source/core/text/redlnitr.cxx
new file mode 100644
index 000000000..12e4fea9f
--- /dev/null
+++ b/sw/source/core/text/redlnitr.cxx
@@ -0,0 +1,1137 @@
+/* -*- 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 <string_view>
+
+#include <hintids.hxx>
+#include <o3tl/safeint.hxx>
+#include <svl/whiter.hxx>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <scriptinfo.hxx>
+#include <swmodule.hxx>
+#include <redline.hxx>
+#include <txatbase.hxx>
+#include <docary.hxx>
+#include "itratr.hxx"
+#include <ndtxt.hxx>
+#include <doc.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <IDocumentLayoutAccess.hxx>
+#include <IDocumentMarkAccess.hxx>
+#include <IMark.hxx>
+#include <bookmark.hxx>
+#include <rootfrm.hxx>
+#include <breakit.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/settings.hxx>
+#include <txtfrm.hxx>
+#include <ftnfrm.hxx>
+#include <vcl/svapp.hxx>
+#include "redlnitr.hxx"
+#include <extinput.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <editeng/udlnitem.hxx>
+
+using namespace ::com::sun::star;
+
+namespace {
+
+class HideIterator
+{
+private:
+ IDocumentRedlineAccess const& m_rIDRA;
+ IDocumentMarkAccess const& m_rIDMA;
+ bool const m_isHideRedlines;
+ sw::FieldmarkMode const m_eFieldmarkMode;
+ SwPosition const m_Start;
+ /// next redline
+ SwRedlineTable::size_type m_RedlineIndex;
+ /// next fieldmark
+ std::pair<sw::mark::IFieldmark const*, std::unique_ptr<SwPosition>> m_Fieldmark;
+ std::optional<SwPosition> m_oNextFieldmarkHide;
+ /// current start/end pair
+ SwPosition const* m_pStartPos;
+ SwPosition const* m_pEndPos;
+
+public:
+ SwPosition const* GetStartPos() const { return m_pStartPos; }
+ SwPosition const* GetEndPos() const { return m_pEndPos; }
+
+ HideIterator(SwTextNode & rTextNode,
+ bool const isHideRedlines, sw::FieldmarkMode const eMode)
+ : m_rIDRA(rTextNode.getIDocumentRedlineAccess())
+ , m_rIDMA(*rTextNode.getIDocumentMarkAccess())
+ , m_isHideRedlines(isHideRedlines)
+ , m_eFieldmarkMode(eMode)
+ , m_Start(rTextNode, 0)
+ , m_RedlineIndex(m_rIDRA.GetRedlinePos(rTextNode, RedlineType::Any))
+ , m_pStartPos(nullptr)
+ , m_pEndPos(&m_Start)
+ {
+ }
+
+ // delete redlines and fieldmarks can't overlap, due to sw::CalcBreaks()
+ // and no combining of adjacent redlines
+ // -> dummy chars are delete-redlined *iff* entire fieldmark is
+ // Note: caller is responsible for checking for immediately adjacent hides
+ bool Next()
+ {
+ SwPosition const* pNextRedlineHide(nullptr);
+ assert(m_pEndPos);
+ if (m_isHideRedlines)
+ {
+ // position on current or next redline
+ for (; m_RedlineIndex < m_rIDRA.GetRedlineTable().size(); ++m_RedlineIndex)
+ {
+ SwRangeRedline const*const pRed = m_rIDRA.GetRedlineTable()[m_RedlineIndex];
+
+ if (m_pEndPos->nNode.GetIndex() < pRed->Start()->nNode.GetIndex())
+ break;
+
+ if (pRed->GetType() != RedlineType::Delete)
+ continue;
+
+ SwPosition const*const pStart(pRed->Start());
+ SwPosition const*const pEnd(pRed->End());
+ if (*pStart == *pEnd)
+ { // only allowed while moving (either way?)
+// assert(IDocumentRedlineAccess::IsHideChanges(rIDRA.GetRedlineFlags()));
+ continue;
+ }
+ if (pStart->nNode.GetNode().IsTableNode())
+ {
+ assert(pEnd->nNode == m_Start.nNode && pEnd->nContent.GetIndex() == 0);
+ continue; // known pathology, ignore it
+ }
+ if (*m_pEndPos <= *pStart)
+ {
+ pNextRedlineHide = pStart;
+ break; // the next one
+ }
+ }
+ }
+
+ // position on current or next fieldmark
+ m_oNextFieldmarkHide.reset();
+ if (m_eFieldmarkMode != sw::FieldmarkMode::ShowBoth)
+ {
+ sal_Unicode const magic(m_eFieldmarkMode == sw::FieldmarkMode::ShowResult
+ ? CH_TXT_ATR_FIELDSTART
+ : CH_TXT_ATR_FIELDSEP);
+ SwTextNode* pTextNode = m_pEndPos->nNode.GetNode().GetTextNode();
+ sal_Int32 const nPos = pTextNode ? pTextNode->GetText().indexOf(
+ magic, m_pEndPos->nContent.GetIndex()) : -1;
+ if (nPos != -1)
+ {
+ m_oNextFieldmarkHide.emplace(*pTextNode, nPos);
+ sw::mark::IFieldmark const*const pFieldmark(
+ m_eFieldmarkMode == sw::FieldmarkMode::ShowResult
+ ? m_rIDMA.getFieldmarkAt(*m_oNextFieldmarkHide)
+ : m_rIDMA.getFieldmarkFor(*m_oNextFieldmarkHide));
+ assert(pFieldmark);
+ m_Fieldmark.first = pFieldmark;
+ // for cursor travelling, there should be 2 visible chars;
+ // whichever char is hidden, the cursor travelling needs to
+ // be adapted in any case to skip in some situation or other;
+ // always hide the CH_TXT_ATR_FIELDSEP for now
+ if (m_eFieldmarkMode == sw::FieldmarkMode::ShowResult)
+ {
+ m_Fieldmark.second.reset(
+ new SwPosition(sw::mark::FindFieldSep(*m_Fieldmark.first)));
+ ++m_Fieldmark.second->nContent;
+ ++m_oNextFieldmarkHide->nContent; // skip start
+ }
+ else
+ {
+ m_Fieldmark.second.reset(
+ new SwPosition(pFieldmark->GetMarkEnd()));
+ --m_Fieldmark.second->nContent;
+ }
+ }
+ }
+
+ // == can happen only if redline starts inside field command, and in
+ // that case redline will end before field separator
+ assert(!pNextRedlineHide || !m_oNextFieldmarkHide
+ || *pNextRedlineHide != *m_oNextFieldmarkHide
+ || *m_rIDRA.GetRedlineTable()[m_RedlineIndex]->End() < *m_Fieldmark.second);
+ if (pNextRedlineHide
+ && (!m_oNextFieldmarkHide || *pNextRedlineHide < *m_oNextFieldmarkHide))
+ {
+ SwRangeRedline const*const pRed(m_rIDRA.GetRedlineTable()[m_RedlineIndex]);
+ m_pStartPos = pRed->Start();
+ m_pEndPos = pRed->End();
+ ++m_RedlineIndex;
+ return true;
+ }
+ else if (m_oNextFieldmarkHide)
+ {
+ assert(!pNextRedlineHide || *m_oNextFieldmarkHide <= *pNextRedlineHide);
+ m_pStartPos = &*m_oNextFieldmarkHide;
+ m_pEndPos = m_Fieldmark.second.get();
+ return true;
+ }
+ else // nothing
+ {
+ assert(!pNextRedlineHide && !m_oNextFieldmarkHide);
+ m_pStartPos = nullptr;
+ m_pEndPos = nullptr;
+ return false;
+ }
+ }
+};
+
+}
+
+namespace sw {
+
+std::unique_ptr<sw::MergedPara>
+CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode,
+ FrameMode const eMode)
+{
+ if (!rFrame.getRootFrame()->HasMergedParas())
+ {
+ return nullptr;
+ }
+ bool bHaveRedlines(false);
+ std::vector<SwTextNode *> nodes{ &rTextNode };
+ std::vector<SwTableNode *> tables;
+ std::vector<SwSectionNode *> sections;
+ std::vector<sw::Extent> extents;
+ OUStringBuffer mergedText;
+ SwTextNode * pParaPropsNode(nullptr);
+ SwTextNode * pNode(&rTextNode);
+ sal_Int32 nLastEnd(0);
+ for (auto iter = HideIterator(rTextNode,
+ rFrame.getRootFrame()->IsHideRedlines(),
+ rFrame.getRootFrame()->GetFieldmarkMode()); iter.Next(); )
+ {
+ SwPosition const*const pStart(iter.GetStartPos());
+ SwPosition const*const pEnd(iter.GetEndPos());
+ bHaveRedlines = true;
+ assert(pNode != &rTextNode || &pStart->nNode.GetNode() == &rTextNode); // detect calls with wrong start node
+ if (pStart->nContent != nLastEnd) // not 0 so we eliminate adjacent deletes
+ {
+ extents.emplace_back(pNode, nLastEnd, pStart->nContent.GetIndex());
+ mergedText.append(pNode->GetText().subView(nLastEnd, pStart->nContent.GetIndex() - nLastEnd));
+ }
+ if (&pEnd->nNode.GetNode() != pNode)
+ {
+ if (pNode == &rTextNode)
+ {
+ pNode->SetRedlineMergeFlag(SwNode::Merge::First);
+ } // else: was already set before
+ int nLevel(0);
+ for (SwNodeOffset j = pNode->GetIndex() + 1; j < pEnd->nNode.GetIndex(); ++j)
+ {
+ SwNode *const pTmp(pNode->GetNodes()[j]);
+ if (nLevel == 0)
+ {
+ if (pTmp->IsTextNode())
+ {
+ nodes.push_back(pTmp->GetTextNode());
+ }
+ else if (pTmp->IsTableNode())
+ {
+ tables.push_back(pTmp->GetTableNode());
+ }
+ else if (pTmp->IsSectionNode())
+ {
+ sections.push_back(pTmp->GetSectionNode());
+ }
+ }
+ if (pTmp->IsStartNode())
+ {
+ ++nLevel;
+ }
+ else if (pTmp->IsEndNode())
+ {
+ --nLevel;
+ }
+ pTmp->SetRedlineMergeFlag(SwNode::Merge::Hidden);
+ }
+ // note: in DelLastPara() case, the end node is not actually merged
+ // and is likely a SwTableNode!
+ if (!pEnd->nNode.GetNode().IsTextNode())
+ {
+ assert(pEnd->nNode != pStart->nNode);
+ // must set pNode too because it will mark the last node
+ pNode = nodes.back();
+ assert(pNode == pNode->GetNodes()[pEnd->nNode.GetIndex() - 1]);
+ if (pNode != &rTextNode)
+ { // something might depend on last merged one being NonFirst?
+ pNode->SetRedlineMergeFlag(SwNode::Merge::NonFirst);
+ }
+ nLastEnd = pNode->Len();
+ }
+ else
+ {
+ pNode = pEnd->nNode.GetNode().GetTextNode();
+ nodes.push_back(pNode);
+ pNode->SetRedlineMergeFlag(SwNode::Merge::NonFirst);
+ nLastEnd = pEnd->nContent.GetIndex();
+ }
+ }
+ else
+ {
+ nLastEnd = pEnd->nContent.GetIndex();
+ }
+ }
+ if (pNode == &rTextNode)
+ {
+ if (rTextNode.GetRedlineMergeFlag() != SwNode::Merge::None)
+ {
+ rTextNode.SetRedlineMergeFlag(SwNode::Merge::None);
+ }
+ }
+ // Reset flag of the following text node since we know it's not merged;
+ // also any table/sections in between.
+ // * the following SwTextNode is in same nodes section as pNode (nLevel=0)
+ // * the start nodes that don't have a SwTextNode before them
+ // on their level, and their corresponding end nodes
+ // * the first SwTextNode inside each start node of the previous point
+ // Other (non-first) SwTextNodes in nested sections shouldn't be reset!
+ int nLevel(0);
+ for (SwNodeOffset j = pNode->GetIndex() + 1; j < pNode->GetNodes().Count(); ++j)
+ {
+ SwNode *const pTmp(pNode->GetNodes()[j]);
+ if (!pTmp->IsCreateFrameWhenHidingRedlines())
+ { // clear stale flag caused by editing with redlines shown
+ pTmp->SetRedlineMergeFlag(SwNode::Merge::None);
+ }
+ if (pTmp->IsStartNode())
+ {
+ ++nLevel;
+ }
+ else if (pTmp->IsEndNode())
+ {
+ if (nLevel == 0)
+ {
+ break; // there is no following text node; avoid leaving section
+ }
+ --nLevel;
+ }
+ else if (pTmp->IsTextNode())
+ {
+ if (nLevel == 0)
+ {
+ break; // done
+ }
+ else
+ { // skip everything other than 1st text node in section!
+ j = pTmp->EndOfSectionIndex() - 1; // will be incremented again
+ }
+ }
+ }
+ if (!bHaveRedlines)
+ {
+ if (rTextNode.IsInList() && !rTextNode.GetNum(rFrame.getRootFrame()))
+ {
+ rTextNode.AddToListRLHidden(); // try to add it...
+ }
+ return nullptr;
+ }
+ if (nLastEnd != pNode->Len())
+ {
+ extents.emplace_back(pNode, nLastEnd, pNode->Len());
+ mergedText.append(pNode->GetText().subView(nLastEnd, pNode->Len() - nLastEnd));
+ }
+ if (extents.empty()) // there was no text anywhere
+ {
+ assert(mergedText.isEmpty());
+ pParaPropsNode = pNode; // if every node is empty, the last one wins
+ }
+ else
+ {
+ assert(!mergedText.isEmpty());
+ pParaPropsNode = extents.begin()->pNode; // para props from first node that isn't empty
+ }
+// pParaPropsNode = &rTextNode; // well, actually...
+ // keep lists up to date with visible nodes
+ if (pParaPropsNode->IsInList() && !pParaPropsNode->GetNum(rFrame.getRootFrame()))
+ {
+ pParaPropsNode->AddToListRLHidden(); // try to add it...
+ }
+ for (auto const pTextNode : nodes)
+ {
+ if (pTextNode != pParaPropsNode)
+ {
+ pTextNode->RemoveFromListRLHidden();
+ }
+ }
+ if (eMode == FrameMode::Existing)
+ {
+ // remove existing footnote frames for first node;
+ // for non-first nodes with own frames, DelFrames will remove all
+ // (could possibly call lcl_ChangeFootnoteRef, not sure if worth it)
+ // note: must be done *before* changing listeners!
+ // for non-first nodes that are already merged with this frame,
+ // need to remove here too, otherwise footnotes can be removed only
+ // by lucky accident, e.g. TruncLines().
+ auto itExtent(extents.begin());
+ for (auto const pTextNode : nodes)
+ {
+ sal_Int32 nLast(0);
+ std::vector<std::pair<sal_Int32, sal_Int32>> hidden;
+ for ( ; itExtent != extents.end(); ++itExtent)
+ {
+ if (itExtent->pNode != pTextNode)
+ {
+ break;
+ }
+ if (itExtent->nStart != 0)
+ {
+ assert(itExtent->nStart != nLast);
+ hidden.emplace_back(nLast, itExtent->nStart);
+ }
+ nLast = itExtent->nEnd;
+ }
+ if (nLast != pTextNode->Len())
+ {
+ hidden.emplace_back(nLast, pTextNode->Len());
+ }
+ sw::RemoveFootnotesForNode(*rFrame.getRootFrame(), *pTextNode, &hidden);
+ }
+ // unfortunately DelFrames() must be done before StartListening too,
+ // otherwise footnotes cannot be deleted by SwTextFootnote::DelFrames!
+ auto const end(--nodes.rend());
+ for (auto iter = nodes.rbegin(); iter != end; ++iter)
+ {
+ (**iter).DelFrames(rFrame.getRootFrame());
+ }
+ // also delete tables & sections here; not necessary, but convenient
+ for (auto const pTableNode : tables)
+ {
+ pTableNode->DelFrames(rFrame.getRootFrame());
+ }
+ for (auto const pSectionNode : sections)
+ {
+ pSectionNode->GetSection().GetFormat()->DelFrames(/*rFrame.getRootFrame()*/);
+ }
+ }
+ auto pRet(std::make_unique<sw::MergedPara>(rFrame, std::move(extents),
+ mergedText.makeStringAndClear(), pParaPropsNode, &rTextNode,
+ nodes.back()));
+ for (SwTextNode * pTmp : nodes)
+ {
+ pRet->listener.StartListening(pTmp);
+ }
+ rFrame.EndListeningAll();
+ return pRet;
+}
+
+} // namespace sw
+
+void SwAttrIter::InitFontAndAttrHandler(
+ SwTextNode const& rPropsNode,
+ SwTextNode const& rTextNode,
+ OUString const& rText,
+ bool const*const pbVertLayout,
+ bool const*const pbVertLayoutLRBT)
+{
+ // Build a font matching the default paragraph style:
+ SwFontAccess aFontAccess( &rPropsNode.GetAnyFormatColl(), m_pViewShell );
+ // It is possible that Init is called more than once, e.g., in a
+ // SwTextFrame::FormatOnceMore situation or (since sw_redlinehide)
+ // from SwAttrIter::Seek(); in the latter case SwTextSizeInfo::m_pFnt
+ // is an alias of m_pFont so it must not be deleted!
+ if (m_pFont)
+ {
+ *m_pFont = aFontAccess.Get()->GetFont();
+ }
+ else
+ {
+ m_pFont = new SwFont( aFontAccess.Get()->GetFont() );
+ }
+
+ // set font to vertical if frame layout is vertical
+ // if it's a re-init, the vert flag never changes
+ bool bVertLayoutLRBT = false;
+ if (pbVertLayoutLRBT)
+ bVertLayoutLRBT = *pbVertLayoutLRBT;
+ if (pbVertLayout ? *pbVertLayout : m_aAttrHandler.IsVertLayout())
+ {
+ m_pFont->SetVertical(m_pFont->GetOrientation(), true, bVertLayoutLRBT);
+ }
+
+ // Initialize the default attribute of the attribute handler
+ // based on the attribute array cached together with the font.
+ // If any further attributes for the paragraph are given in pAttrSet
+ // consider them during construction of the default array, and apply
+ // them to the font
+ m_aAttrHandler.Init(aFontAccess.Get()->GetDefault(), rTextNode.GetpSwAttrSet(),
+ *rTextNode.getIDocumentSettingAccess(), m_pViewShell, *m_pFont,
+ pbVertLayout ? *pbVertLayout : m_aAttrHandler.IsVertLayout(),
+ bVertLayoutLRBT );
+
+ m_aFontCacheIds[SwFontScript::Latin] = m_aFontCacheIds[SwFontScript::CJK] = m_aFontCacheIds[SwFontScript::CTL] = nullptr;
+
+ assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
+
+ m_pFont->SetActual( m_pScriptInfo->WhichFont(TextFrameIndex(0)) );
+
+ TextFrameIndex nChg(0);
+ size_t nCnt = 0;
+
+ do
+ {
+ if ( nCnt >= m_pScriptInfo->CountScriptChg() )
+ break;
+ nChg = m_pScriptInfo->GetScriptChg( nCnt );
+ SwFontScript nTmp = SW_SCRIPTS;
+ switch ( m_pScriptInfo->GetScriptType( nCnt++ ) ) {
+ case i18n::ScriptType::ASIAN :
+ if( !m_aFontCacheIds[SwFontScript::CJK] ) nTmp = SwFontScript::CJK;
+ break;
+ case i18n::ScriptType::COMPLEX :
+ if( !m_aFontCacheIds[SwFontScript::CTL] ) nTmp = SwFontScript::CTL;
+ break;
+ default:
+ if( !m_aFontCacheIds[SwFontScript::Latin ] ) nTmp = SwFontScript::Latin;
+ }
+ if( nTmp < SW_SCRIPTS )
+ {
+ m_pFont->CheckFontCacheId( m_pViewShell, nTmp );
+ m_pFont->GetFontCacheId( m_aFontCacheIds[ nTmp ], m_aFontIdx[ nTmp ], nTmp );
+ }
+ }
+ while (nChg < TextFrameIndex(rText.getLength()));
+}
+
+void SwAttrIter::CtorInitAttrIter(SwTextNode & rTextNode,
+ SwScriptInfo & rScriptInfo, SwTextFrame const*const pFrame)
+{
+ // during HTML-Import it can happen, that no layout exists
+ SwRootFrame* pRootFrame = rTextNode.getIDocumentLayoutAccess().GetCurrentLayout();
+ m_pViewShell = pRootFrame ? pRootFrame->GetCurrShell() : nullptr;
+
+ m_pScriptInfo = &rScriptInfo;
+
+ // set font to vertical if frame layout is vertical
+ bool bVertLayout = false;
+ bool bVertLayoutLRBT = false;
+ bool bRTL = false;
+ if ( pFrame )
+ {
+ if ( pFrame->IsVertical() )
+ {
+ bVertLayout = true;
+ }
+ if (pFrame->IsVertLRBT())
+ {
+ bVertLayoutLRBT = true;
+ }
+ bRTL = pFrame->IsRightToLeft();
+ m_pMergedPara = pFrame->GetMergedPara();
+ }
+
+ // determine script changes if not already done for current paragraph
+ assert(m_pScriptInfo);
+ if (m_pScriptInfo->GetInvalidityA() != TextFrameIndex(COMPLETE_STRING))
+ m_pScriptInfo->InitScriptInfo(rTextNode, m_pMergedPara, bRTL);
+
+ InitFontAndAttrHandler(
+ m_pMergedPara ? *m_pMergedPara->pParaPropsNode : rTextNode,
+ rTextNode,
+ m_pMergedPara ? m_pMergedPara->mergedText : rTextNode.GetText(),
+ & bVertLayout,
+ & bVertLayoutLRBT);
+
+ m_nStartIndex = m_nEndIndex = m_nPosition = m_nChgCnt = 0;
+ m_nPropFont = 0;
+ SwDoc& rDoc = rTextNode.GetDoc();
+ const IDocumentRedlineAccess& rIDRA = rTextNode.getIDocumentRedlineAccess();
+
+ // sw_redlinehide: this is a Ring - pExtInp is the first PaM that's inside
+ // the node. It's not clear whether there can be more than 1 PaM in the
+ // Ring, and this code doesn't handle that case; neither did the old code.
+ const SwExtTextInput* pExtInp = rDoc.GetExtTextInput( rTextNode );
+ if (!pExtInp && m_pMergedPara)
+ {
+ SwTextNode const* pNode(&rTextNode);
+ for (auto const& rExtent : m_pMergedPara->extents)
+ {
+ if (rExtent.pNode != pNode)
+ {
+ pNode = rExtent.pNode;
+ pExtInp = rDoc.GetExtTextInput(*pNode);
+ if (pExtInp)
+ break;
+ }
+ }
+ }
+ const bool bShow = IDocumentRedlineAccess::IsShowChanges(rIDRA.GetRedlineFlags())
+ && pRootFrame && !pRootFrame->IsHideRedlines();
+ if (!(pExtInp || m_pMergedPara || bShow))
+ return;
+
+ SwRedlineTable::size_type nRedlPos = rIDRA.GetRedlinePos( rTextNode, RedlineType::Any );
+ if (SwRedlineTable::npos == nRedlPos && m_pMergedPara)
+ {
+ SwTextNode const* pNode(&rTextNode);
+ for (auto const& rExtent : m_pMergedPara->extents)
+ { // note: have to search because extents based only on Delete
+ if (rExtent.pNode != pNode)
+ {
+ pNode = rExtent.pNode;
+ nRedlPos = rIDRA.GetRedlinePos(*pNode, RedlineType::Any);
+ if (SwRedlineTable::npos != nRedlPos)
+ break;
+ }
+ }
+ // TODO this is true initially but after delete ops it may be false... need to delete m_pMerged somewhere?
+ // assert(SwRedlineTable::npos != nRedlPos);
+ // false now with fieldmarks
+ assert(!pRootFrame
+ || pRootFrame->GetFieldmarkMode() != sw::FieldmarkMode::ShowBoth
+ || SwRedlineTable::npos != nRedlPos || m_pMergedPara->extents.size() <= 1);
+ }
+ if (!(pExtInp || m_pMergedPara || SwRedlineTable::npos != nRedlPos))
+ return;
+
+ const std::vector<ExtTextInputAttr> *pArr = nullptr;
+ if( pExtInp )
+ {
+ pArr = &pExtInp->GetAttrs();
+ Seek( TextFrameIndex(0) );
+ }
+
+ m_pRedline.reset(new SwRedlineItr( rTextNode, *m_pFont, m_aAttrHandler, nRedlPos,
+ (pRootFrame && pRootFrame->IsHideRedlines())
+ ? SwRedlineItr::Mode::Hide
+ : bShow
+ ? SwRedlineItr::Mode::Show
+ : SwRedlineItr::Mode::Ignore,
+ pArr, pExtInp ? pExtInp->Start() : nullptr));
+
+ if( m_pRedline->IsOn() )
+ ++m_nChgCnt;
+}
+
+// The Redline-Iterator
+// The following information/states exist in RedlineIterator:
+//
+// m_nFirst is the first index of RedlineTable, which overlaps with the paragraph.
+//
+// m_nAct is the currently active (if m_bOn is set) or the next possible index.
+// m_nStart and m_nEnd give you the borders of the object within the paragraph.
+//
+// If m_bOn is set, the font has been manipulated according to it.
+//
+// If m_nAct is set to SwRedlineTable::npos (via Reset()), then currently no
+// Redline is active, m_nStart and m_nEnd are invalid.
+SwRedlineItr::SwRedlineItr( const SwTextNode& rTextNd, SwFont& rFnt,
+ SwAttrHandler& rAH, sal_Int32 nRed,
+ Mode const mode,
+ const std::vector<ExtTextInputAttr> *pArr,
+ SwPosition const*const pExtInputStart)
+ : m_rDoc( rTextNd.GetDoc() )
+ , m_rAttrHandler( rAH )
+ , m_nNdIdx( rTextNd.GetIndex() )
+ , m_nFirst( nRed )
+ , m_nAct( SwRedlineTable::npos )
+ , m_bOn( false )
+ , m_eMode( mode )
+{
+ if( pArr )
+ {
+ assert(pExtInputStart);
+ m_pExt.reset( new SwExtend(*pArr, pExtInputStart->nNode.GetIndex(),
+ pExtInputStart->nContent.GetIndex()) );
+ }
+ else
+ m_pExt = nullptr;
+ assert(m_pExt || m_eMode != Mode::Ignore); // only create if necessary
+ Seek(rFnt, m_nNdIdx, 0, COMPLETE_STRING);
+}
+
+SwRedlineItr::~SwRedlineItr() COVERITY_NOEXCEPT_FALSE
+{
+ Clear( nullptr );
+ m_pExt.reset();
+}
+
+// The return value of SwRedlineItr::Seek tells you if the current font
+// has been manipulated by leaving (-1) or accessing (+1) of a section
+short SwRedlineItr::Seek(SwFont& rFnt,
+ SwNodeOffset const nNode, sal_Int32 const nNew, sal_Int32 const nOld)
+{
+ short nRet = 0;
+ if( ExtOn() )
+ return 0; // Abbreviation: if we're within an ExtendTextInputs
+ // there can't be other changes of attributes (not even by redlining)
+ if (m_eMode == Mode::Show)
+ {
+ if (m_bOn)
+ {
+ if (nNew >= m_nEnd)
+ {
+ --nRet;
+ Clear_( &rFnt ); // We go behind the current section
+ ++m_nAct; // and check the next one
+ }
+ else if (nNew < m_nStart)
+ {
+ --nRet;
+ Clear_( &rFnt ); // We go in front of the current section
+ if (m_nAct > m_nFirst)
+ m_nAct = m_nFirst; // the test has to run from the beginning
+ else
+ return nRet + EnterExtend(rFnt, nNode, nNew); // There's none prior to us
+ }
+ else
+ return nRet + EnterExtend(rFnt, nNode, nNew); // We stayed in the same section
+ }
+ if (SwRedlineTable::npos == m_nAct || nOld > nNew)
+ m_nAct = m_nFirst;
+
+ m_nStart = COMPLETE_STRING;
+ m_nEnd = COMPLETE_STRING;
+ const SwRedlineTable& rTable = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable();
+
+ for ( ; m_nAct < rTable.size() ; ++m_nAct)
+ {
+ rTable[ m_nAct ]->CalcStartEnd(nNode, m_nStart, m_nEnd);
+
+ if (nNew < m_nEnd)
+ {
+ if (nNew >= m_nStart) // only possible candidate
+ {
+ m_bOn = true;
+ const SwRangeRedline *pRed = rTable[ m_nAct ];
+
+ if (m_pSet)
+ m_pSet->ClearItem();
+ else
+ {
+ SwAttrPool& rPool =
+ const_cast<SwDoc&>(m_rDoc).GetAttrPool();
+ m_pSet = std::make_unique<SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END-1>>(rPool);
+ }
+
+ if( 1 < pRed->GetStackCount() )
+ FillHints( pRed->GetAuthor( 1 ), pRed->GetType( 1 ) );
+ FillHints( pRed->GetAuthor(), pRed->GetType() );
+
+ SfxWhichIter aIter( *m_pSet );
+
+ // moved text: dark green with double underline or strikethrough
+ if ( pRed->IsMoved() )
+ {
+ m_pSet->Put(SvxColorItem( COL_GREEN, RES_CHRATR_COLOR ));
+ if (SfxItemState::SET == m_pSet->GetItemState(RES_CHRATR_CROSSEDOUT, true))
+ m_pSet->Put(SvxCrossedOutItem( STRIKEOUT_DOUBLE, RES_CHRATR_CROSSEDOUT ));
+ else
+ m_pSet->Put(SvxUnderlineItem( LINESTYLE_DOUBLE, RES_CHRATR_UNDERLINE ));
+ }
+
+ sal_uInt16 nWhich = aIter.FirstWhich();
+ while( nWhich )
+ {
+ const SfxPoolItem* pItem;
+ if( ( nWhich < RES_CHRATR_END ) &&
+ ( SfxItemState::SET == aIter.GetItemState( true, &pItem ) ) )
+ {
+ SwTextAttr* pAttr = MakeRedlineTextAttr(
+ const_cast<SwDoc&>(m_rDoc),
+ *const_cast<SfxPoolItem*>(pItem) );
+ pAttr->SetPriorityAttr( true );
+ m_Hints.push_back(pAttr);
+ m_rAttrHandler.PushAndChg( *pAttr, rFnt );
+ }
+ nWhich = aIter.NextWhich();
+ }
+
+ ++nRet;
+ }
+ break;
+ }
+ m_nStart = COMPLETE_STRING;
+ m_nEnd = COMPLETE_STRING;
+ }
+ }
+ else if (m_eMode == Mode::Hide)
+ { // ... just iterate to update m_nAct for GetNextRedln();
+ // there is no need to care about formatting in this mode
+ if (m_nAct == SwRedlineTable::npos || nOld == COMPLETE_STRING)
+ { // reset, or move backward
+ m_nAct = m_nFirst;
+ }
+ for ( ; m_nAct < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); ++m_nAct)
+ { // only Start matters in this mode
+ // Seeks until it finds a RL that starts at or behind the seek pos.
+ // - then update m_nStart/m_nEnd to the intersection of it with the
+ // current node (if any).
+ // The only way to skip to a different node is if there is a Delete
+ // RL, so if there is no intersection we'll never skip again.
+ // Note: here, assume that delete can't nest inside delete!
+ SwRangeRedline const*const pRedline(
+ m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[m_nAct]);
+ SwPosition const*const pStart(pRedline->Start());
+ if (pRedline->GetType() == RedlineType::Delete
+ && (nNode < pStart->nNode.GetIndex()
+ || (nNode == pStart->nNode.GetIndex()
+ && nNew <= pStart->nContent.GetIndex())))
+ {
+ pRedline->CalcStartEnd(nNode, m_nStart, m_nEnd);
+ break;
+ }
+ m_nStart = COMPLETE_STRING;
+ m_nEnd = COMPLETE_STRING;
+ }
+ }
+ return nRet + EnterExtend(rFnt, nNode, nNew);
+}
+
+void SwRedlineItr::FillHints( std::size_t nAuthor, RedlineType eType )
+{
+ switch ( eType )
+ {
+ case RedlineType::Insert:
+ SW_MOD()->GetInsertAuthorAttr(nAuthor, *m_pSet);
+ break;
+ case RedlineType::Delete:
+ SW_MOD()->GetDeletedAuthorAttr(nAuthor, *m_pSet);
+ break;
+ case RedlineType::Format:
+ case RedlineType::FmtColl:
+ SW_MOD()->GetFormatAuthorAttr(nAuthor, *m_pSet);
+ break;
+ default:
+ break;
+ }
+}
+
+void SwRedlineItr::ChangeTextAttr( SwFont* pFnt, SwTextAttr const &rHt, bool bChg )
+{
+ OSL_ENSURE( IsOn(), "SwRedlineItr::ChangeTextAttr: Off?" );
+
+ if (m_eMode != Mode::Show && !m_pExt)
+ return;
+
+ if( bChg )
+ {
+ if (m_pExt && m_pExt->IsOn())
+ m_rAttrHandler.PushAndChg( rHt, *m_pExt->GetFont() );
+ else
+ m_rAttrHandler.PushAndChg( rHt, *pFnt );
+ }
+ else
+ {
+ OSL_ENSURE( ! m_pExt || ! m_pExt->IsOn(), "Pop of attribute during opened extension" );
+ m_rAttrHandler.PopAndChg( rHt, *pFnt );
+ }
+}
+
+void SwRedlineItr::Clear_( SwFont* pFnt )
+{
+ OSL_ENSURE( m_bOn, "SwRedlineItr::Clear: Off?" );
+ m_bOn = false;
+ for (auto const& hint : m_Hints)
+ {
+ if( pFnt )
+ m_rAttrHandler.PopAndChg( *hint, *pFnt );
+ else
+ m_rAttrHandler.Pop( *hint );
+ SwTextAttr::Destroy(hint, const_cast<SwDoc&>(m_rDoc).GetAttrPool() );
+ }
+ m_Hints.clear();
+}
+
+/// Ignore mode: does nothing.
+/// Show mode: returns end of redline if currently in one, or start of next
+/// Hide mode: returns start of next redline in current node, plus (if it's a
+/// Delete) its end position and number of consecutive RLs
+std::pair<sal_Int32, std::pair<SwRangeRedline const*, size_t>>
+SwRedlineItr::GetNextRedln(sal_Int32 nNext, SwTextNode const*const pNode,
+ SwRedlineTable::size_type & rAct)
+{
+ sal_Int32 nStart(m_nStart);
+ sal_Int32 nEnd(m_nEnd);
+ nNext = NextExtend(pNode->GetIndex(), nNext);
+ if (m_eMode == Mode::Ignore || SwRedlineTable::npos == m_nFirst)
+ return std::make_pair(nNext, std::make_pair(nullptr, 0));
+ if (SwRedlineTable::npos == rAct)
+ {
+ rAct = m_nFirst;
+ }
+ if (rAct != m_nAct)
+ {
+ while (rAct < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size())
+ {
+ SwRangeRedline const*const pRedline(
+ m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[rAct]);
+ pRedline->CalcStartEnd(pNode->GetIndex(), nStart, nEnd);
+ if (m_eMode != Mode::Hide
+ || pRedline->GetType() == RedlineType::Delete)
+ {
+ break;
+ }
+ ++rAct; // Hide mode: search a Delete RL
+ }
+ }
+ if (rAct == m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size())
+ {
+ return std::make_pair(nNext, std::make_pair(nullptr, 0)); // no Delete here
+ }
+ if (m_bOn || (m_eMode == Mode::Show && nStart == 0))
+ { // in Ignore mode, the end of redlines isn't relevant, except as returned in the second in the pair!
+ if (nEnd < nNext)
+ nNext = nEnd;
+ }
+ else if (nStart <= nNext)
+ {
+ if (m_eMode == Mode::Show)
+ {
+ nNext = nStart;
+ }
+ else
+ {
+ assert(m_eMode == Mode::Hide);
+ SwRangeRedline const* pRedline(
+ m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[rAct]);
+ assert(pRedline->GetType() == RedlineType::Delete); //?
+ if (pRedline->GetType() == RedlineType::Delete)
+ {
+ nNext = nStart;
+ size_t nSkipped(1); // (consecutive) candidates to be skipped
+ while (rAct + nSkipped <
+ m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size())
+ {
+ SwRangeRedline const*const pNext =
+ m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[rAct + nSkipped];
+ if (*pRedline->End() < *pNext->Start())
+ {
+ break; // done for now
+ }
+ else if (*pNext->Start() == *pRedline->End() &&
+ pNext->GetType() == RedlineType::Delete)
+ {
+ // consecutive delete - continue
+ pRedline = pNext;
+ }
+ ++nSkipped;
+ }
+ return std::make_pair(nNext, std::make_pair(pRedline, nSkipped));
+ }
+ }
+ }
+ return std::make_pair(nNext, std::make_pair(nullptr, 0));
+}
+
+bool SwRedlineItr::ChkSpecialUnderline_() const
+{
+ // If the underlining or the escapement is caused by redlining,
+ // we always apply the SpecialUnderlining, i.e. the underlining
+ // below the base line
+ for (SwTextAttr* pHint : m_Hints)
+ {
+ const sal_uInt16 nWhich = pHint->Which();
+ if( RES_CHRATR_UNDERLINE == nWhich ||
+ RES_CHRATR_ESCAPEMENT == nWhich )
+ return true;
+ }
+ return false;
+}
+
+bool SwRedlineItr::CheckLine(
+ SwNodeOffset const nStartNode, sal_Int32 const nChkStart,
+ SwNodeOffset const nEndNode, sal_Int32 nChkEnd, OUString& rRedlineText,
+ bool& bRedlineEnd, RedlineType& eRedlineEnd, size_t* pAuthorAtPos)
+{
+ // note: previously this would return true in the (!m_bShow && m_pExt)
+ // case, but surely that was a bug?
+ if (m_nFirst == SwRedlineTable::npos || m_eMode != Mode::Show)
+ return false;
+ if( nChkEnd == nChkStart && pAuthorAtPos == nullptr ) // empty lines look one char further
+ ++nChkEnd;
+ sal_Int32 nOldStart = m_nStart;
+ sal_Int32 nOldEnd = m_nEnd;
+ SwRedlineTable::size_type const nOldAct = m_nAct;
+ bool bRet = bRedlineEnd = false;
+ eRedlineEnd = RedlineType::None;
+
+ SwPosition const start(*m_rDoc.GetNodes()[nStartNode]->GetContentNode(), nChkStart);
+ SwPosition const end(*m_rDoc.GetNodes()[nEndNode]->GetContentNode(), nChkEnd);
+ SwRangeRedline const* pPrevRedline = nullptr;
+ bool isBreak(false);
+ for (m_nAct = m_nFirst; m_nAct < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); ++m_nAct)
+ {
+ SwRangeRedline const*const pRedline(
+ m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ m_nAct ] );
+ // collect text of the hidden redlines at the end of the line
+ bool isExtendText(false);
+ switch (ComparePosition(*pRedline->Start(), *pRedline->End(), start, end))
+ {
+ case SwComparePosition::Behind:
+ isBreak = true;
+ break;
+ case SwComparePosition::OverlapBehind:
+ case SwComparePosition::CollideStart:
+ case SwComparePosition::Outside:
+ case SwComparePosition::Equal:
+ // store redlining at line end (for line break formatting)
+ eRedlineEnd = pRedline->GetType();
+ bRedlineEnd = true;
+ isBreak = true;
+ if (pAuthorAtPos)
+ *pAuthorAtPos = pRedline->GetAuthor();
+ [[fallthrough]];
+ case SwComparePosition::OverlapBefore:
+ case SwComparePosition::CollideEnd:
+ case SwComparePosition::Inside:
+ {
+ bRet = true;
+ // start to collect text of invisible redlines for ChangesInMargin layout
+ if (rRedlineText.isEmpty() && !pRedline->IsVisible())
+ {
+ rRedlineText = const_cast<SwRangeRedline*>(pRedline)->GetDescr(/*bSimplified=*/true);
+ pPrevRedline = pRedline;
+ isExtendText = true;
+ }
+ // join the text of the next invisible redlines in the same position
+ // i.e. characters deleted by pressing backspace or delete
+ else if (pPrevRedline && !pRedline->IsVisible() &&
+ *pRedline->Start() == *pPrevRedline->Start() && *pRedline->End() == *pPrevRedline->End() )
+ {
+ OUString sExtendText(const_cast<SwRangeRedline*>(pRedline)->GetDescr(/*bSimplified=*/true));
+ if (!sExtendText.isEmpty())
+ {
+ if (rRedlineText.getLength() < 12)
+ {
+ // TODO: remove extra space from GetDescr(true),
+ // but show deletion of paragraph or line break
+ rRedlineText = rRedlineText +
+ const_cast<SwRangeRedline*>(pRedline)->GetDescr(/*bSimplified=*/true).subView(1);
+ }
+ else
+ rRedlineText = OUString::Concat(rRedlineText.subView(0, rRedlineText.getLength() - 3)) + "...";
+ }
+ isExtendText = true;
+ }
+ break;
+ }
+ case SwComparePosition::Before:
+ break; // -Werror=switch
+ }
+ if (isBreak && !isExtendText)
+ {
+ break;
+ }
+ }
+
+ m_nStart = nOldStart;
+ m_nEnd = nOldEnd;
+ m_nAct = nOldAct;
+ return bRet;
+}
+
+void SwExtend::ActualizeFont( SwFont &rFnt, ExtTextInputAttr nAttr )
+{
+ if ( nAttr & ExtTextInputAttr::Underline )
+ rFnt.SetUnderline( LINESTYLE_SINGLE );
+ else if ( nAttr & ExtTextInputAttr::DoubleUnderline )
+ rFnt.SetUnderline( LINESTYLE_DOUBLE );
+ else if ( nAttr & ExtTextInputAttr::BoldUnderline )
+ rFnt.SetUnderline( LINESTYLE_BOLD );
+ else if ( nAttr & ExtTextInputAttr::DottedUnderline )
+ rFnt.SetUnderline( LINESTYLE_DOTTED );
+ else if ( nAttr & ExtTextInputAttr::DashDotUnderline )
+ rFnt.SetUnderline( LINESTYLE_DOTTED );
+
+ if ( nAttr & ExtTextInputAttr::RedText )
+ rFnt.SetColor( COL_RED );
+
+ if ( nAttr & ExtTextInputAttr::Highlight )
+ {
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ rFnt.SetColor( rStyleSettings.GetHighlightTextColor() );
+ rFnt.SetBackColor( rStyleSettings.GetHighlightColor() );
+ }
+ if ( nAttr & ExtTextInputAttr::GrayWaveline )
+ rFnt.SetGreyWave( true );
+}
+
+short SwExtend::Enter(SwFont& rFnt, SwNodeOffset const nNode, sal_Int32 const nNew)
+{
+ OSL_ENSURE( !m_pFont, "SwExtend: Enter with Font" );
+ if (nNode != m_nNode)
+ return 0;
+ OSL_ENSURE( !Inside(), "SwExtend: Enter without Leave" );
+ m_nPos = nNew;
+ if( Inside() )
+ {
+ m_pFont.reset( new SwFont(rFnt) );
+ ActualizeFont( rFnt, m_rArr[m_nPos - m_nStart] );
+ return 1;
+ }
+ return 0;
+}
+
+bool SwExtend::Leave_(SwFont& rFnt, SwNodeOffset const nNode, sal_Int32 const nNew)
+{
+ OSL_ENSURE(nNode == m_nNode && Inside(), "SwExtend: Leave without Enter");
+ if (nNode != m_nNode)
+ return true;
+ const ExtTextInputAttr nOldAttr = m_rArr[m_nPos - m_nStart];
+ m_nPos = nNew;
+ if( Inside() )
+ { // We stayed within the ExtendText-section
+ const ExtTextInputAttr nAttr = m_rArr[m_nPos - m_nStart];
+ if( nOldAttr != nAttr ) // Is there an (inner) change of attributes?
+ {
+ rFnt = *m_pFont;
+ ActualizeFont( rFnt, nAttr );
+ }
+ }
+ else
+ {
+ rFnt = *m_pFont;
+ m_pFont.reset();
+ return true;
+ }
+ return false;
+}
+
+sal_Int32 SwExtend::Next(SwNodeOffset const nNode, sal_Int32 nNext)
+{
+ if (nNode != m_nNode)
+ return nNext;
+ if (m_nPos < m_nStart)
+ {
+ if (nNext > m_nStart)
+ nNext = m_nStart;
+ }
+ else if (m_nPos < m_nEnd)
+ {
+ sal_Int32 nIdx = m_nPos - m_nStart;
+ const ExtTextInputAttr nAttr = m_rArr[ nIdx ];
+ while (o3tl::make_unsigned(++nIdx) < m_rArr.size() && nAttr == m_rArr[nIdx])
+ ; //nothing
+ nIdx = nIdx + m_nStart;
+ if( nNext > nIdx )
+ nNext = nIdx;
+ }
+ return nNext;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */