/* -*- 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 "txatbase.hxx" #include "ndhints.hxx" #include #ifdef DBG_UTIL #include #include #include #endif /// sort order: Start, End (reverse), Which (reverse), /// (char style: sort number), at last the pointer static bool lcl_IsLessStart( const SwTextAttr &rHt1, const SwTextAttr &rHt2 ) { 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: End (reverse), Start, Which (reverse), /// (char style: sort number), at last the pointer static bool lcl_IsLessEnd( const SwTextAttr &rHt1, const SwTextAttr &rHt2 ) { 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 ); } bool CompareSwpHtStart::operator()(SwTextAttr const * const lhs, SwTextAttr const * const rhs) const { return lcl_IsLessStart( *lhs, *rhs ); } bool CompareSwpHtEnd::operator()(SwTextAttr const * const lhs, SwTextAttr const * const rhs) const { return lcl_IsLessEnd( *lhs, *rhs ); } void SwpHints::Insert( const SwTextAttr *pHt ) { Resort(); assert(m_HintsByStart.find(const_cast(pHt)) == m_HintsByStart.end()); // "Insert: hint already in HtStart" assert(m_HintsByEnd.find(const_cast(pHt)) == m_HintsByEnd.end()); // "Insert: hint already in HtEnd" m_HintsByStart.insert( const_cast(pHt) ); m_HintsByEnd .insert( const_cast(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 for (size_t i = 0; i < m_HintsByStart.size(); ++i) { if (m_HintsByStart[i] == pHt) { return true; } } return false; } #ifdef DBG_UTIL #define CHECK_ERR(cond, text) \ if(!(cond)) \ { \ SAL_WARN("sw.core", text); \ (const_cast(this))->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; std::set 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); } } } } 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( lcl_IsLessStart( *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( lcl_IsLessEnd( *pLastEnd, *pHtEnd ), "HintsCheck: IsLastEnd" ); nLastEnd = nIdx; pLastEnd = pHtEnd; // --- cross checks --- // 5) same pointers in both arrays if (m_HintsByStart.find(const_cast(pHt)) == m_HintsByStart.end()) nIdx = COMPLETE_STRING; CHECK_ERR( COMPLETE_STRING != nIdx, "HintsCheck: no GetStartOf" ); // 6) same pointers in both arrays if (m_HintsByEnd.find(const_cast(pHt)) == m_HintsByEnd.end()) nIdx = COMPLETE_STRING; 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 < Count(); ++j ) { SwTextAttr const * const pOther( m_HintsByStart[j] ); if ( pOther->IsNesting() && (i != j) ) { 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() { m_HintsByStart.Resort(); m_HintsByEnd.Resort(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */