1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <editeng/rsiditem.hxx>
21 #include <sal/log.hxx>
22 #include <txatbase.hxx>
23 #include <ndhints.hxx>
28 #include <fmtautofmt.hxx>
31 /// sort order: Start, End (reverse), Which (reverse),
32 /// (char style: sort number), at last the pointer
33 static bool CompareSwpHtStart( const SwTextAttr
* lhs
, const SwTextAttr
* rhs
)
35 const SwTextAttr
&rHt1
= *lhs
;
36 const SwTextAttr
&rHt2
= *rhs
;
37 if ( rHt1
.GetStart() == rHt2
.GetStart() )
39 const sal_Int32 nHt1
= rHt1
.GetAnyEnd();
40 const sal_Int32 nHt2
= rHt2
.GetAnyEnd();
43 const sal_uInt16 nWhich1
= rHt1
.Which();
44 const sal_uInt16 nWhich2
= rHt2
.Which();
45 if ( nWhich1
== nWhich2
)
47 if ( RES_TXTATR_CHARFMT
== nWhich1
)
49 const sal_uInt16 nS1
=
50 static_txtattr_cast
<const SwTextCharFormat
&>(rHt1
).GetSortNumber();
51 const sal_uInt16 nS2
=
52 static_txtattr_cast
<const SwTextCharFormat
&>(rHt2
).GetSortNumber();
53 if ( nS1
!= nS2
) // robust
57 return reinterpret_cast<sal_IntPtr
>(&rHt1
) < reinterpret_cast<sal_IntPtr
>(&rHt2
);
59 // order is important! for requirements see hintids.hxx
60 return ( nWhich1
> nWhich2
);
62 return ( nHt1
> nHt2
);
64 return ( rHt1
.GetStart() < rHt2
.GetStart() );
68 /// (char style: sort number), at last the pointer
70 struct CompareSwpHtStartOnly
72 bool operator()( const SwTextAttr
* lhs
, sal_Int32 rhs
) const
74 return lhs
->GetStart() < rhs
;
76 bool operator()( sal_Int32 lhs
, const SwTextAttr
* rhs
) const
78 return lhs
< rhs
->GetStart();
81 struct CompareSwpHtEndOnly
83 bool operator()( const SwTextAttr
* lhs
, sal_Int32 rhs
) const
85 return lhs
->GetAnyEnd() < rhs
;
87 bool operator()( sal_Int32 lhs
, const SwTextAttr
* rhs
) const
89 return lhs
< rhs
->GetAnyEnd();
94 /// sort order: Which, Start, End(reverse) at last the pointer
95 bool CompareSwpHtWhichStart::operator()( const SwTextAttr
* lhs
, const sal_uInt16 nWhich
) const
97 return lhs
->Which() < nWhich
;
99 bool CompareSwpHtWhichStart::operator()( const SwTextAttr
* lhs
, const SwTextAttr
* rhs
) const
101 const SwTextAttr
&rHt1
= *lhs
;
102 const SwTextAttr
&rHt2
= *rhs
;
103 const sal_uInt16 nWhich1
= rHt1
.Which();
104 const sal_uInt16 nWhich2
= rHt2
.Which();
105 if ( nWhich1
< nWhich2
)
107 if ( nWhich1
> nWhich2
)
109 if (rHt1
.GetStart() < rHt2
.GetStart())
111 if (rHt1
.GetStart() > rHt2
.GetStart())
113 if ( RES_TXTATR_CHARFMT
== nWhich1
)
115 const sal_uInt16 nS1
=
116 static_txtattr_cast
<const SwTextCharFormat
&>(rHt1
).GetSortNumber();
117 const sal_uInt16 nS2
=
118 static_txtattr_cast
<const SwTextCharFormat
&>(rHt2
).GetSortNumber();
119 if ( nS1
!= nS2
) // robust
122 const sal_Int32 nEnd1
= rHt1
.GetAnyEnd();
123 const sal_Int32 nEnd2
= rHt2
.GetAnyEnd();
128 return reinterpret_cast<sal_IntPtr
>(&rHt1
) < reinterpret_cast<sal_IntPtr
>(&rHt2
);
130 bool CompareSwpHtWhichStart::operator()( const SwTextAttr
* lhs
, const WhichStartPair rhs
) const
132 if ( lhs
->Which() < rhs
.first
)
134 if ( lhs
->Which() > rhs
.first
)
136 return lhs
->GetStart() < rhs
.second
;
138 bool CompareSwpHtWhichStart::operator()( const WhichStartPair lhs
, const SwTextAttr
* rhs
) const
140 if ( lhs
.first
< rhs
->Which() )
142 if ( lhs
.first
> rhs
->Which() )
144 return lhs
.second
< rhs
->GetStart();
147 /// sort order: End, Start(reverse), Which
148 /// (char style: sort number), at last the pointer(reverse)
149 bool CompareSwpHtEnd::operator()( sal_Int32 nEndPos
, const SwTextAttr
* rhs
) const
151 return nEndPos
< rhs
->GetAnyEnd();
153 bool CompareSwpHtEnd::operator()( const SwTextAttr
* lhs
, const SwTextAttr
* rhs
) const
155 const SwTextAttr
&rHt1
= *lhs
;
156 const SwTextAttr
&rHt2
= *rhs
;
157 const sal_Int32 nHt1
= rHt1
.GetAnyEnd();
158 const sal_Int32 nHt2
= rHt2
.GetAnyEnd();
161 if ( rHt1
.GetStart() == rHt2
.GetStart() )
163 const sal_uInt16 nWhich1
= rHt1
.Which();
164 const sal_uInt16 nWhich2
= rHt2
.Which();
165 if ( nWhich1
== nWhich2
)
167 if ( RES_TXTATR_CHARFMT
== nWhich1
)
169 const sal_uInt16 nS1
=
170 static_txtattr_cast
<const SwTextCharFormat
&>(rHt1
).GetSortNumber();
171 const sal_uInt16 nS2
=
172 static_txtattr_cast
<const SwTextCharFormat
&>(rHt2
).GetSortNumber();
173 if ( nS1
!= nS2
) // robust
177 return reinterpret_cast<sal_IntPtr
>(&rHt1
) > reinterpret_cast<sal_IntPtr
>(&rHt2
);
179 // order is important! for requirements see hintids.hxx
180 return ( nWhich1
< nWhich2
);
183 return ( rHt1
.GetStart() > rHt2
.GetStart() );
185 return ( nHt1
< nHt2
);
188 void SwpHints::Insert(SwTextAttr
* pHt
)
190 assert(std::find(m_HintsByStart
.begin(), m_HintsByStart
.end(), pHt
)
191 == m_HintsByStart
.end()); // "Insert: hint already in HtStart"
192 assert( pHt
->m_pHints
== nullptr );
193 pHt
->m_pHints
= this;
199 auto it1
= std::lower_bound(m_HintsByStart
.begin(), m_HintsByStart
.end(), pHt
, CompareSwpHtStart
);
200 m_HintsByStart
.insert(it1
, pHt
);
202 auto it2
= std::lower_bound(m_HintsByEnd
.begin(), m_HintsByEnd
.end(), pHt
, CompareSwpHtEnd());
203 m_HintsByEnd
.insert(it2
, pHt
);
205 auto it3
= std::lower_bound(m_HintsByWhichAndStart
.begin(), m_HintsByWhichAndStart
.end(), pHt
, CompareSwpHtWhichStart());
206 m_HintsByWhichAndStart
.insert(it3
, pHt
);
209 bool SwpHints::Contains( const SwTextAttr
*pHt
) const
211 // DO NOT use find() or CHECK here!
212 // if called from SwTextNode::InsertItem, pHt has already been deleted,
213 // so it cannot be dereferenced
214 return std::find(m_HintsByStart
.begin(), m_HintsByStart
.end(), pHt
)
215 != m_HintsByStart
.end();
220 #define CHECK_ERR(cond, text) \
223 SAL_WARN("sw.core", text); \
228 bool SwpHints::Check(bool bPortionsMerged
) const
230 // 1) both arrays have same size
231 CHECK_ERR( m_HintsByStart
.size() == m_HintsByEnd
.size(),
232 "HintsCheck: wrong sizes" );
233 sal_Int32 nLastStart
= 0;
234 sal_Int32 nLastEnd
= 0;
236 const SwTextAttr
*pLastStart
= nullptr;
237 const SwTextAttr
*pLastEnd
= nullptr;
238 o3tl::sorted_vector
<SwTextAttr
const*> RsidOnlyAutoFormats
;
241 for (size_t i
= 0; i
< Count(); ++i
)
243 SwTextAttr
const*const pHint(m_HintsByStart
[i
]);
244 if (RES_TXTATR_AUTOFMT
== pHint
->Which())
246 std::shared_ptr
<SfxItemSet
> const & pSet(
247 pHint
->GetAutoFormat().GetStyleHandle());
248 if (pSet
->Count() == 1 && pSet
->GetItem(RES_CHRATR_RSID
, false))
250 RsidOnlyAutoFormats
.insert(pHint
);
256 // --- cross checks ---
257 // same pointers in both arrays
258 auto tmpHintsByEnd
= m_HintsByEnd
;
259 std::sort(tmpHintsByEnd
.begin(), tmpHintsByEnd
.end(), CompareSwpHtStart
);
260 CHECK_ERR( tmpHintsByEnd
== m_HintsByStart
, "HintsCheck: the two arrays do not contain the same set of pointers" );
262 for( size_t i
= 0; i
< Count(); ++i
)
264 // --- check Starts ---
266 // 2a) valid pointer? depends on overwriting freed mem with 0xFF
267 const SwTextAttr
*pHt
= m_HintsByStart
[i
];
268 CHECK_ERR( 0xFF != *reinterpret_cast<unsigned char const *>(pHt
), "HintsCheck: start ptr was deleted" );
270 // 3a) start sort order?
271 sal_Int32 nIdx
= pHt
->GetStart();
272 CHECK_ERR( nIdx
>= nLastStart
, "HintsCheck: starts are unsorted" );
274 // 4a) IsLessStart consistency
276 CHECK_ERR( CompareSwpHtStart( pLastStart
, pHt
), "HintsCheck: IsLastStart" );
281 // --- check Ends ---
283 // 2b) valid pointer? see DELETEFF
284 const SwTextAttr
*pHtEnd
= m_HintsByEnd
[i
];
285 CHECK_ERR( 0xFF != *reinterpret_cast<unsigned char const *>(pHtEnd
), "HintsCheck: end ptr was deleted" );
287 // 3b) end sort order?
288 nIdx
= pHtEnd
->GetAnyEnd();
289 CHECK_ERR( nIdx
>= nLastEnd
, "HintsCheck: ends are unsorted" );
291 // 4b) IsLessEnd consistency
293 CHECK_ERR( CompareSwpHtEnd()( pLastEnd
, pHtEnd
), "HintsCheck: IsLastEnd" );
298 CHECK_ERR( COMPLETE_STRING
!= nIdx
, "HintsCheck: no GetEndOf" );
300 // 7a) character attributes in array?
301 sal_uInt16 nWhich
= pHt
->Which();
302 CHECK_ERR( !isCHRATR(nWhich
),
303 "HintsCheck: Character attribute in start array" );
305 // 7b) character attributes in array?
306 nWhich
= pHtEnd
->Which();
307 CHECK_ERR( !isCHRATR(nWhich
),
308 "HintsCheck: Character attribute in end array" );
310 // 8) style portion check
311 const SwTextAttr
* pHtThis
= m_HintsByStart
[i
];
312 const SwTextAttr
* pHtLast
= i
> 0 ? m_HintsByStart
[i
-1] : nullptr;
314 || ( (RES_TXTATR_CHARFMT
!= pHtLast
->Which())
315 && (RES_TXTATR_AUTOFMT
!= pHtLast
->Which()))
316 || ( (RES_TXTATR_CHARFMT
!= pHtThis
->Which())
317 && (RES_TXTATR_AUTOFMT
!= pHtThis
->Which()))
318 || (pHtThis
->GetStart() >= *pHtLast
->End()) // no overlap
319 || ( ( (pHtThis
->GetStart() == pHtLast
->GetStart())
320 && (*pHtThis
->End() == *pHtLast
->End())
322 && ( (pHtThis
->Which() != RES_TXTATR_AUTOFMT
)
323 || (pHtLast
->Which() != RES_TXTATR_AUTOFMT
)
324 ) // never two AUTOFMT on same range
325 && ( (pHtThis
->Which() != RES_TXTATR_CHARFMT
)
326 || (pHtLast
->Which() != RES_TXTATR_CHARFMT
)
327 || (static_txtattr_cast
<const SwTextCharFormat
*>(pHtThis
)
329 static_txtattr_cast
<const SwTextCharFormat
*>(pHtLast
)
331 ) // multiple CHARFMT on same range need distinct sorter
333 || (pHtThis
->GetStart() == *pHtThis
->End()), // this empty
334 "HintsCheck: Portion inconsistency. "
335 "This can be temporarily ok during undo operations" );
337 // 8 1/2) format ignore start/end flag check
338 // (problems because MergePortions buggy or not called)
341 if (RES_TXTATR_AUTOFMT
== pHt
->Which() ||
342 RES_TXTATR_CHARFMT
== pHt
->Which())
344 // mostly ignore the annoying no-length hints
345 // BuildPortions inserts these in the middle of an existing one
346 bool const bNoLength(pHt
->GetStart() == *pHt
->End());
347 bool bNeedContinuation(!bNoLength
&& pHt
->IsFormatIgnoreEnd());
348 bool bForbidContinuation(!bNoLength
&& !bNeedContinuation
);
349 if (RES_TXTATR_AUTOFMT
== pHt
->Which())
351 if (RsidOnlyAutoFormats
.find(pHt
) != RsidOnlyAutoFormats
.end())
353 assert(pHt
->IsFormatIgnoreStart());
354 bNeedContinuation
= false;
355 // don't forbid continuation - may be other hint here!
358 if (bNeedContinuation
|| bForbidContinuation
)
361 for (size_t j
= i
+ 1; j
< Count(); ++j
)
363 SwTextAttr
*const pOther(m_HintsByStart
[j
]);
364 if (pOther
->GetStart() > *pHt
->End())
368 else if (pOther
->GetStart() == pOther
->GetAnyEnd())
370 continue; // empty hint: ignore
372 else if (pOther
->GetStart() == *pHt
->End())
374 if (RES_TXTATR_AUTOFMT
== pOther
->Which() ||
375 RES_TXTATR_CHARFMT
== pOther
->Which())
376 { // multiple charfmt on same range must all match
377 if (bNeedContinuation
)
379 assert(pOther
->IsFormatIgnoreStart());
382 else if (bForbidContinuation
&&
383 (RsidOnlyAutoFormats
.find(pOther
) ==
384 RsidOnlyAutoFormats
.end()))
386 assert(!pOther
->IsFormatIgnoreStart());
391 if (bNeedContinuation
)
393 assert(bFound
); // ? can this happen temp. during undo?
399 assert(!pHt
->IsFormatIgnoreStart());
400 assert(!pHt
->IsFormatIgnoreEnd());
404 // 9) nesting portion check
405 if (pHtThis
->IsNesting())
407 for (size_t j
= 0; j
< i
; ++j
)
409 SwTextAttr
const * const pOther( m_HintsByStart
[j
] );
410 if (pOther
->IsNesting())
412 SwComparePosition cmp
= ComparePosition(
413 pHtThis
->GetStart(), *pHtThis
->End(),
414 pOther
->GetStart(), *pOther
->End());
415 CHECK_ERR( (SwComparePosition::OverlapBefore
!= cmp
) &&
416 (SwComparePosition::OverlapBehind
!= cmp
),
417 "HintsCheck: overlapping nesting hints!!!" );
422 // 10) dummy char check (unfortunately cannot check SwTextNode::m_Text)
423 if (pHtThis
->HasDummyChar())
425 for ( size_t j
= 0; j
< i
; ++j
)
427 SwTextAttr
const * const pOther( m_HintsByStart
[j
] );
428 if (pOther
->HasDummyChar())
430 CHECK_ERR( (pOther
->GetStart() != pHtThis
->GetStart()),
431 "HintsCheck: multiple hints claim same CH_TXTATR!");
439 #endif /* DBG_UTIL */
441 // Resort() is called before every Insert and Delete.
442 // Various SwTextNode methods modify hints in a way that violates the
443 // sort order of the m_HintsByStart, m_HintsByEnd arrays, so this method is needed
444 // to restore the order.
446 void SwpHints::Resort() const
453 void SwpHints::ResortStartMap() const
455 if (m_StartMapNeedsSortingRange
.first
!= SAL_MAX_INT32
)
457 auto & rStartMap
= const_cast<SwpHints
*>(this)->m_HintsByStart
;
458 if (m_StartMapNeedsSortingRange
.first
== -1)
459 std::sort(rStartMap
.begin(), rStartMap
.end(), CompareSwpHtStart
);
462 // only need to sort a partial range of the array
463 auto it1
= std::lower_bound(rStartMap
.begin(), rStartMap
.end(), m_StartMapNeedsSortingRange
.first
, CompareSwpHtStartOnly());
464 auto it2
= std::upper_bound(rStartMap
.begin(), rStartMap
.end(), m_StartMapNeedsSortingRange
.second
, CompareSwpHtStartOnly());
465 std::sort(rStartMap
.begin() + std::distance(rStartMap
.begin(), it1
),
466 rStartMap
.begin() + std::distance(rStartMap
.begin(), it2
), CompareSwpHtStart
);
468 m_StartMapNeedsSortingRange
= { SAL_MAX_INT32
, -1 };
472 void SwpHints::ResortEndMap() const
474 if (m_EndMapNeedsSortingRange
.first
!= SAL_MAX_INT32
)
476 auto & rEndMap
= const_cast<SwpHints
*>(this)->m_HintsByEnd
;
477 if (m_EndMapNeedsSortingRange
.first
== -1)
478 std::sort(rEndMap
.begin(), rEndMap
.end(), CompareSwpHtEnd());
481 // only need to sort a partial range of the array
482 auto it1
= std::lower_bound(rEndMap
.begin(), rEndMap
.end(), m_EndMapNeedsSortingRange
.first
, CompareSwpHtEndOnly());
483 auto it2
= std::upper_bound(rEndMap
.begin(), rEndMap
.end(), m_EndMapNeedsSortingRange
.second
, CompareSwpHtEndOnly());
484 std::sort(rEndMap
.begin() + std::distance(rEndMap
.begin(), it1
),
485 rEndMap
.begin() + std::distance(rEndMap
.begin(), it2
), CompareSwpHtEnd());
487 m_EndMapNeedsSortingRange
= { SAL_MAX_INT32
, -1 };
491 void SwpHints::ResortWhichMap() const
493 if (m_WhichMapNeedsSortingRange
.first
.first
!= SAL_MAX_INT32
)
495 auto & rWhichStartMap
= const_cast<SwpHints
*>(this)->m_HintsByWhichAndStart
;
496 if (m_WhichMapNeedsSortingRange
.first
.first
== -1)
497 std::sort(rWhichStartMap
.begin(), rWhichStartMap
.end(), CompareSwpHtWhichStart());
500 // only need to sort a partial range of the array
501 auto it1
= std::lower_bound(rWhichStartMap
.begin(), rWhichStartMap
.end(), m_WhichMapNeedsSortingRange
.first
, CompareSwpHtWhichStart());
502 auto it2
= std::upper_bound(rWhichStartMap
.begin(), rWhichStartMap
.end(), m_WhichMapNeedsSortingRange
.second
, CompareSwpHtWhichStart());
503 std::sort(rWhichStartMap
.begin() + std::distance(rWhichStartMap
.begin(), it1
),
504 rWhichStartMap
.begin() + std::distance(rWhichStartMap
.begin(), it2
), CompareSwpHtWhichStart());
506 m_WhichMapNeedsSortingRange
= { { SAL_MAX_INT32
, -1 }, { -1, -1 } };
510 size_t SwpHints::GetFirstPosSortedByWhichAndStart( sal_uInt16 nWhich
) const
512 if (m_WhichMapNeedsSortingRange
.first
.first
!= SAL_MAX_INT32
)
514 auto it
= std::lower_bound(m_HintsByWhichAndStart
.begin(), m_HintsByWhichAndStart
.end(), nWhich
, CompareSwpHtWhichStart());
515 if ( it
== m_HintsByWhichAndStart
.end() )
517 return it
- m_HintsByWhichAndStart
.begin();
520 int SwpHints::GetLastPosSortedByEnd( sal_Int32 nEndPos
) const
522 if (m_EndMapNeedsSortingRange
.first
!= SAL_MAX_INT32
)
524 auto it
= std::upper_bound(m_HintsByEnd
.begin(), m_HintsByEnd
.end(), nEndPos
, CompareSwpHtEnd());
525 return it
- m_HintsByEnd
.begin() - 1;
528 size_t SwpHints::GetIndexOf( const SwTextAttr
*pHt
) const
530 if (m_StartMapNeedsSortingRange
.first
!= SAL_MAX_INT32
)
532 auto it
= std::lower_bound(m_HintsByStart
.begin(), m_HintsByStart
.end(), const_cast<SwTextAttr
*>(pHt
), CompareSwpHtStart
);
533 if ( it
== m_HintsByStart
.end() || *it
!= pHt
)
535 return it
- m_HintsByStart
.begin();
538 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */