From 267c6f2ac71f92999e969232431ba04678e7437e Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 07:54:39 +0200 Subject: Adding upstream version 4:24.2.0. Signed-off-by: Daniel Baumann --- sw/source/core/txtnode/ndhints.cxx | 486 +++++++++++++++++++++++++++++++++++++ 1 file changed, 486 insertions(+) create mode 100644 sw/source/core/txtnode/ndhints.cxx (limited to 'sw/source/core/txtnode/ndhints.cxx') 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 +#include +#include +#include +#include + +#ifdef DBG_UTIL +#include +#include +#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(rHt1).GetSortNumber(); + const sal_uInt16 nS2 = + static_txtattr_cast(rHt2).GetSortNumber(); + if ( nS1 != nS2 ) // robust + return nS1 < nS2; + } + + return reinterpret_cast(&rHt1) < reinterpret_cast(&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(rHt1).GetSortNumber(); + const sal_uInt16 nS2 = + static_txtattr_cast(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(&rHt1) < reinterpret_cast(&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(rHt1).GetSortNumber(); + const sal_uInt16 nS2 = + static_txtattr_cast(rHt2).GetSortNumber(); + if ( nS1 != nS2 ) // robust + return nS1 > nS2; + } + + return reinterpret_cast(&rHt1) > reinterpret_cast(&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 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 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(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(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(pHtThis) + ->GetSortNumber() != + static_txtattr_cast(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(this)->m_HintsByStart; + std::sort(rStartMap.begin(), rStartMap.end(), CompareSwpHtStart); + m_bStartMapNeedsSorting = false; + } + if (m_bEndMapNeedsSorting) + { + auto & rEndMap = const_cast(this)->m_HintsByEnd; + std::sort(rEndMap.begin(), rEndMap.end(), CompareSwpHtEnd()); + m_bEndMapNeedsSorting = false; + } + if (m_bWhichMapNeedsSorting) + { + auto & rWhichStartMap = const_cast(this)->m_HintsByWhichAndStart; + std::sort(rWhichStartMap.begin(), rWhichStartMap.end(), CompareSwpHtWhichStart()); + m_bWhichMapNeedsSorting = false; + } +} + +void SwpHints::ResortStartMap() const +{ + if (m_bStartMapNeedsSorting) + { + auto & rStartMap = const_cast(this)->m_HintsByStart; + std::sort(rStartMap.begin(), rStartMap.end(), CompareSwpHtStart); + m_bStartMapNeedsSorting = false; + } +} + +void SwpHints::ResortEndMap() const +{ + if (m_bEndMapNeedsSorting) + { + auto & rEndMap = const_cast(this)->m_HintsByEnd; + std::sort(rEndMap.begin(), rEndMap.end(), CompareSwpHtEnd()); + m_bEndMapNeedsSorting = false; + } +} + +void SwpHints::ResortWhichMap() const +{ + if (m_bWhichMapNeedsSorting) + { + auto & rWhichStartMap = const_cast(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(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: */ -- cgit v1.2.3