android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / source / core / text / porlay.cxx
blob50a8489b5088b16d0a6cfc8db107e8d5d933e5c6
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 <sal/config.h>
22 #include <string_view>
24 #include "porlay.hxx"
25 #include "itrform2.hxx"
26 #include "porglue.hxx"
27 #include "redlnitr.hxx"
28 #include "porfly.hxx"
29 #include "porrst.hxx"
30 #include "pormulti.hxx"
31 #include "pordrop.hxx"
32 #include <breakit.hxx>
33 #include <unicode/uchar.h>
34 #include <com/sun/star/i18n/ScriptType.hpp>
35 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
36 #include <com/sun/star/i18n/WordType.hpp>
37 #include <com/sun/star/i18n/XBreakIterator.hpp>
38 #include <paratr.hxx>
39 #include <sal/log.hxx>
40 #include <optional>
41 #include <editeng/adjustitem.hxx>
42 #include <editeng/charhiddenitem.hxx>
43 #include <svl/asiancfg.hxx>
44 #include <svl/languageoptions.hxx>
45 #include <tools/multisel.hxx>
46 #include <unotools/charclass.hxx>
47 #include <charfmt.hxx>
48 #include <docary.hxx>
49 #include <fmtanchr.hxx>
50 #include <redline.hxx>
51 #include <calbck.hxx>
52 #include <doc.hxx>
53 #include <swscanner.hxx>
54 #include <txatbase.hxx>
55 #include <IDocumentRedlineAccess.hxx>
56 #include <IDocumentSettingAccess.hxx>
57 #include <IDocumentContentOperations.hxx>
58 #include <IMark.hxx>
59 #include <sortedobjs.hxx>
60 #include <com/sun/star/frame/XModel.hpp>
61 #include <com/sun/star/text/XBookmarksSupplier.hpp>
62 #include <officecfg/Office/Common.hxx>
63 #include <comphelper/processfactory.hxx>
64 #include <docsh.hxx>
65 #include <unobookmark.hxx>
66 #include <unocrsrhelper.hxx>
67 #include <frmatr.hxx>
68 #include <vcl/kernarray.hxx>
69 #include <editeng/ulspitem.hxx>
70 #include <com/sun/star/rdf/Statement.hpp>
71 #include <com/sun/star/rdf/URI.hpp>
72 #include <com/sun/star/rdf/URIs.hpp>
73 #include <com/sun/star/rdf/XDocumentMetadataAccess.hpp>
74 #include <com/sun/star/rdf/XLiteral.hpp>
75 #include <com/sun/star/text/XTextContent.hpp>
77 using namespace ::com::sun::star;
78 using namespace i18n::ScriptType;
80 #include <unicode/ubidi.h>
81 #include <i18nutil/scripttypedetector.hxx>
82 #include <i18nutil/unicode.hxx>
85 https://www.khtt.net/en/page/1821/the-big-kashida-secret
87 the rules of priorities that govern the addition of kashidas in Arabic text
88 made ... for ... Explorer 5.5 browser.
90 The kashida justification is based on a connection priority scheme that
91 decides where kashidas are put automatically.
93 This is how the software decides on kashida-inserting priorities:
94 1. First it looks for characters with the highest priority in each word,
95 which means kashida-extensions will only been used in one position in each
96 word. Not more.
97 2. The kashida will be connected to the character with the highest priority.
98 3. If kashida connection opportunities are found with an equal level of
99 priority in one word, the kashida will be placed towards the end of the
100 word.
102 The priority list of characters and the positioning is as follows:
103 1. after a kashida that is manually placed in the text by the user,
104 2. after a Seen or Sad (initial and medial form),
105 3. before the final form of Taa Marbutah, Haa, Dal,
106 4. before the final form of Alef, Tah Lam, Kaf and Gaf,
107 5. before the preceding medial Baa of Ra, Ya and Alef Maqsurah,
108 6. before the final form of Waw, Ain, Qaf and Fa,
109 7. before the final form of other characters that can be connected.
112 #define IS_JOINING_GROUP(c, g) ( u_getIntPropertyValue( (c), UCHAR_JOINING_GROUP ) == U_JG_##g )
113 #define isAinChar(c) IS_JOINING_GROUP((c), AIN)
114 #define isAlefChar(c) IS_JOINING_GROUP((c), ALEF)
115 #define isDalChar(c) IS_JOINING_GROUP((c), DAL)
116 #if U_ICU_VERSION_MAJOR_NUM >= 58
117 #define isFehChar(c) (IS_JOINING_GROUP((c), FEH) || IS_JOINING_GROUP((c), AFRICAN_FEH))
118 #else
119 #define isFehChar(c) IS_JOINING_GROUP((c), FEH)
120 #endif
121 #define isGafChar(c) IS_JOINING_GROUP((c), GAF)
122 #define isHehChar(c) IS_JOINING_GROUP((c), HEH)
123 #define isKafChar(c) IS_JOINING_GROUP((c), KAF)
124 #define isLamChar(c) IS_JOINING_GROUP((c), LAM)
125 #if U_ICU_VERSION_MAJOR_NUM >= 58
126 #define isQafChar(c) (IS_JOINING_GROUP((c), QAF) || IS_JOINING_GROUP((c), AFRICAN_QAF))
127 #else
128 #define isQafChar(c) IS_JOINING_GROUP((c), QAF)
129 #endif
130 #define isRehChar(c) IS_JOINING_GROUP((c), REH)
131 #define isTahChar(c) IS_JOINING_GROUP((c), TAH)
132 #define isTehMarbutaChar(c) IS_JOINING_GROUP((c), TEH_MARBUTA)
133 #define isWawChar(c) IS_JOINING_GROUP((c), WAW)
134 #define isSeenOrSadChar(c) (IS_JOINING_GROUP((c), SAD) || IS_JOINING_GROUP((c), SEEN))
136 // Beh and characters that behave like Beh in medial form.
137 static bool isBehChar(sal_Unicode cCh)
139 bool bRet = false;
140 switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP))
142 case U_JG_BEH:
143 case U_JG_NOON:
144 #if U_ICU_VERSION_MAJOR_NUM >= 58
145 case U_JG_AFRICAN_NOON:
146 #endif
147 case U_JG_NYA:
148 case U_JG_YEH:
149 case U_JG_FARSI_YEH:
150 case U_JG_BURUSHASKI_YEH_BARREE:
151 bRet = true;
152 break;
153 default:
154 bRet = false;
155 break;
158 return bRet;
161 // Yeh and characters that behave like Yeh in final form.
162 static bool isYehChar(sal_Unicode cCh)
164 bool bRet = false;
165 switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP))
167 case U_JG_YEH:
168 case U_JG_FARSI_YEH:
169 case U_JG_YEH_BARREE:
170 case U_JG_BURUSHASKI_YEH_BARREE:
171 case U_JG_YEH_WITH_TAIL:
172 bRet = true;
173 break;
174 default:
175 bRet = false;
176 break;
179 return bRet;
182 static bool isTransparentChar ( sal_Unicode cCh )
184 return u_getIntPropertyValue( cCh, UCHAR_JOINING_TYPE ) == U_JT_TRANSPARENT;
187 // Checks if cCh + cNectCh builds a ligature (used for Kashidas)
188 static bool lcl_IsLigature( sal_Unicode cCh, sal_Unicode cNextCh )
190 // Lam + Alef
191 return ( isLamChar ( cCh ) && isAlefChar ( cNextCh ));
194 // Checks if cCh is connectable to cPrevCh (used for Kashidas)
195 static bool lcl_ConnectToPrev( sal_Unicode cCh, sal_Unicode cPrevCh )
197 const int32_t nJoiningType = u_getIntPropertyValue( cPrevCh, UCHAR_JOINING_TYPE );
198 bool bRet = nJoiningType != U_JT_RIGHT_JOINING && nJoiningType != U_JT_NON_JOINING;
200 // check for ligatures cPrevChar + cChar
201 if( bRet )
202 bRet = !lcl_IsLigature( cPrevCh, cCh );
204 return bRet;
207 static bool lcl_HasStrongLTR ( std::u16string_view rText, sal_Int32 nStart, sal_Int32 nEnd )
209 for( sal_Int32 nCharIdx = nStart; nCharIdx < nEnd; ++nCharIdx )
211 const UCharDirection nCharDir = u_charDirection ( rText[ nCharIdx ] );
212 if ( nCharDir == U_LEFT_TO_RIGHT ||
213 nCharDir == U_LEFT_TO_RIGHT_EMBEDDING ||
214 nCharDir == U_LEFT_TO_RIGHT_OVERRIDE )
215 return true;
217 return false;
220 // This is (meant to be) functionally equivalent to 'delete m_pNext' where
221 // deleting a SwLineLayout recursively deletes the owned m_pNext SwLineLayout.
223 // Here, instead of using a potentially deep stack, iterate over all the
224 // SwLineLayouts that would be deleted recursively and delete them linearly
225 void SwLineLayout::DeleteNext()
227 if (!m_pNext)
228 return;
229 SwLineLayout* pNext = m_pNext;
232 SwLineLayout* pLastNext = pNext;
233 pNext = pNext->GetNext();
234 pLastNext->SetNext(nullptr);
235 delete pLastNext;
237 while (pNext);
240 void SwLineLayout::Height(const SwTwips nNew, const bool bText)
242 SwPosSize::Height(nNew);
243 if (bText)
244 m_nTextHeight = nNew;
247 // class SwLineLayout: This is the layout of a single line, which is made
248 // up of its dimension, the character count and the word spacing in the line.
249 // Line objects are managed in an own pool, in order to store them continuously
250 // in memory so that they are paged out together and don't fragment memory.
251 SwLineLayout::~SwLineLayout()
253 Truncate();
254 DeleteNext();
255 m_pLLSpaceAdd.reset();
256 m_pKanaComp.reset();
259 SwLinePortion *SwLineLayout::Insert( SwLinePortion *pIns )
261 // First attribute change: copy mass and length from *pIns into the first
262 // text portion
263 if( !mpNextPortion )
265 if( GetLen() )
267 mpNextPortion = SwTextPortion::CopyLinePortion(*this);
268 if( IsBlinking() )
270 SetBlinking( false );
273 else
275 SetNextPortion( pIns );
276 return pIns;
279 // Call with scope or we'll end up with recursion!
280 return mpNextPortion->SwLinePortion::Insert( pIns );
283 SwLinePortion *SwLineLayout::Append( SwLinePortion *pIns )
285 // First attribute change: copy mass and length from *pIns into the first
286 // text portion
287 if( !mpNextPortion )
288 mpNextPortion = SwTextPortion::CopyLinePortion(*this);
289 // Call with scope or we'll end up with recursion!
290 return mpNextPortion->SwLinePortion::Append( pIns );
293 // For special treatment of empty lines
295 bool SwLineLayout::Format( SwTextFormatInfo &rInf )
297 if( GetLen() )
298 return SwTextPortion::Format( rInf );
300 Height( rInf.GetTextHeight() );
301 return true;
304 // We collect all FlyPortions at the beginning of the line and make that a
305 // MarginPortion.
306 SwMarginPortion *SwLineLayout::CalcLeftMargin()
308 SwMarginPortion *pLeft = (GetNextPortion() && GetNextPortion()->IsMarginPortion()) ?
309 static_cast<SwMarginPortion *>(GetNextPortion()) : nullptr;
310 if( !GetNextPortion() )
311 SetNextPortion(SwTextPortion::CopyLinePortion(*this));
312 if( !pLeft )
314 pLeft = new SwMarginPortion;
315 pLeft->SetNextPortion( GetNextPortion() );
316 SetNextPortion( pLeft );
318 else
320 pLeft->Height( 0 );
321 pLeft->Width( 0 );
322 pLeft->SetLen(TextFrameIndex(0));
323 pLeft->SetAscent( 0 );
324 pLeft->SetNextPortion( nullptr );
325 pLeft->SetFixWidth(0);
328 SwLinePortion *pPos = pLeft->GetNextPortion();
329 while( pPos )
331 if( pPos->IsFlyPortion() )
333 // The FlyPortion gets sucked out...
334 pLeft->Join( static_cast<SwGluePortion*>(pPos) );
335 pPos = pLeft->GetNextPortion();
336 if( GetpKanaComp() && !GetKanaComp().empty() )
337 GetKanaComp().pop_front();
339 else
340 pPos = nullptr;
342 return pLeft;
345 void SwLineLayout::InitSpaceAdd()
347 if ( !m_pLLSpaceAdd )
348 CreateSpaceAdd();
349 else
350 SetLLSpaceAdd( 0, 0 );
353 void SwLineLayout::CreateSpaceAdd( const tools::Long nInit )
355 m_pLLSpaceAdd.reset( new std::vector<tools::Long> );
356 SetLLSpaceAdd( nInit, 0 );
359 // #i3952# Returns true if there are only blanks in [nStt, nEnd[
360 // Used to implement IgnoreTabsAndBlanksForLineCalculation compat flag
361 static bool lcl_HasOnlyBlanks(std::u16string_view rText, TextFrameIndex nStt, TextFrameIndex nEnd)
363 while ( nStt < nEnd )
365 switch (rText[sal_Int32(nStt++)])
367 case 0x0020: // SPACE
368 case 0x2002: // EN SPACE
369 case 0x2003: // EM SPACE
370 case 0x2005: // FOUR-PER-EM SPACE
371 case 0x3000: // IDEOGRAPHIC SPACE
372 continue;
373 default:
374 return false;
377 return true;
380 // Swapped out from FormatLine()
381 void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf )
383 const sal_uInt16 nLineWidth = rInf.RealWidth();
385 sal_uInt16 nFlyAscent = 0;
386 sal_uInt16 nFlyHeight = 0;
387 sal_uInt16 nFlyDescent = 0;
389 // If this line has a clearing break, then this is the portion's height.
390 sal_uInt16 nBreakHeight = 0;
392 bool bOnlyPostIts = true;
393 SetHanging( false );
395 bool bTmpDummy = !GetLen();
396 SwFlyCntPortion* pFlyCnt = nullptr;
397 if( bTmpDummy )
399 nFlyAscent = 0;
400 nFlyHeight = 0;
401 nFlyDescent = 0;
404 // #i3952#
405 const bool bIgnoreBlanksAndTabsForLineHeightCalculation =
406 rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
407 DocumentSettingId::IGNORE_TABS_AND_BLANKS_FOR_LINE_CALCULATION);
409 bool bHasBlankPortion = false;
410 bool bHasOnlyBlankPortions = true;
411 bool bHasFlyPortion = false;
413 if( mpNextPortion )
415 SetContent( false );
416 if( mpNextPortion->IsBreakPortion() )
418 SetLen( mpNextPortion->GetLen() );
419 if( GetLen() )
420 bTmpDummy = false;
422 else
424 const SwTwips nLineHeight = Height();
425 Init( GetNextPortion() );
426 SwLinePortion *pPos = mpNextPortion;
427 SwLinePortion *pLast = this;
428 sal_uInt16 nMaxDescent = 0;
430 // A group is a segment in the portion chain of pCurr or a fixed
431 // portion spanning to the end or the next fixed portion
432 while( pPos )
434 SAL_WARN_IF( PortionType::NONE == pPos->GetWhichPor(),
435 "sw.core", "SwLineLayout::CalcLine: don't use SwLinePortions !" );
437 // Null portions are eliminated. They can form if two FlyFrames
438 // overlap.
439 // coverity[deref_arg] - "Cut" means next "GetNextPortion" returns a different Portion
440 if( !pPos->Compress() )
442 // Only take over Height and Ascent if the rest of the line
443 // is empty.
444 if( !pPos->GetNextPortion() )
446 if( !Height() )
447 Height( pPos->Height(), false );
448 if( !GetAscent() )
449 SetAscent( pPos->GetAscent() );
451 SwLinePortion* pPortion = pLast->Cut( pPos );
452 rLine.ClearIfIsFirstOfBorderMerge(pPortion);
453 delete pPortion;
454 pPos = pLast->GetNextPortion();
455 continue;
458 TextFrameIndex const nPorSttIdx = rInf.GetLineStart() + mnLineLength;
459 mnLineLength += pPos->GetLen();
460 AddPrtWidth( pPos->Width() );
462 // #i3952#
463 if (bIgnoreBlanksAndTabsForLineHeightCalculation && !rInf.GetLineStart())
465 if ( pPos->InTabGrp() || pPos->IsHolePortion() ||
466 ( pPos->IsTextPortion() &&
467 lcl_HasOnlyBlanks( rInf.GetText(), nPorSttIdx, nPorSttIdx + pPos->GetLen() ) ) )
469 pLast = pPos;
470 pPos = pPos->GetNextPortion();
471 bHasBlankPortion = true;
472 continue;
476 // Ignore drop portion height
477 // tdf#130804 ... and bookmark portions
478 if ((pPos->IsDropPortion() && static_cast<SwDropPortion*>(pPos)->GetLines() > 1)
479 || pPos->GetWhichPor() == PortionType::Bookmark)
481 pLast = pPos;
482 pPos = pPos->GetNextPortion();
483 continue;
486 bHasOnlyBlankPortions = false;
488 // We had an attribute change: Sum up/build maxima of length and mass
490 SwTwips nPosHeight = pPos->Height();
491 SwTwips nPosAscent = pPos->GetAscent();
493 SAL_WARN_IF( nPosHeight < nPosAscent,
494 "sw.core", "SwLineLayout::CalcLine: bad ascent or height" );
496 if( pPos->IsHangingPortion() )
498 SetHanging(true);
499 rInf.GetParaPortion()->SetMargin();
501 else if( !bHasFlyPortion && ( pPos->IsFlyCntPortion() || pPos->IsFlyPortion() ) )
502 bHasFlyPortion = true;
504 // A line break portion only influences the height of the line in case it's the only
505 // portion in the line, except when it's a clearing break.
506 bool bClearingBreak = false;
507 if (pPos->IsBreakPortion())
509 auto pBreakPortion = static_cast<SwBreakPortion*>(pPos);
510 bClearingBreak = pBreakPortion->GetClear() != SwLineBreakClear::NONE;
511 nBreakHeight = nPosHeight;
513 if (!(pPos->IsBreakPortion() && !bClearingBreak) || !Height())
515 if (!pPos->IsPostItsPortion()) bOnlyPostIts = false;
517 if( bTmpDummy && !mnLineLength )
519 if( pPos->IsFlyPortion() )
521 if( nFlyHeight < nPosHeight )
522 nFlyHeight = nPosHeight;
523 if( nFlyAscent < nPosAscent )
524 nFlyAscent = nPosAscent;
525 if( nFlyDescent < nPosHeight - nPosAscent )
526 nFlyDescent = nPosHeight - nPosAscent;
528 else
530 if( pPos->InNumberGrp() )
532 sal_uInt16 nTmp = rInf.GetFont()->GetAscent(
533 rInf.GetVsh(), *rInf.GetOut() );
534 if( nTmp > nPosAscent )
536 nPosHeight += nTmp - nPosAscent;
537 nPosAscent = nTmp;
539 nTmp = rInf.GetFont()->GetHeight( rInf.GetVsh(),
540 *rInf.GetOut() );
541 if( nTmp > nPosHeight )
542 nPosHeight = nTmp;
544 Height( nPosHeight, false );
545 mnAscent = nPosAscent;
546 nMaxDescent = nPosHeight - nPosAscent;
549 else if( !pPos->IsFlyPortion() )
551 if( Height() < nPosHeight )
553 // Height is set to 0 when Init() is called.
554 if (bIgnoreBlanksAndTabsForLineHeightCalculation && pPos->IsFlyCntPortion())
555 // Compat flag set: take the line height, if it's larger.
556 Height(std::max(nPosHeight, nLineHeight), false);
557 else
558 // Just care about the portion height.
559 Height(nPosHeight, pPos->IsTextPortion());
561 SwFlyCntPortion* pAsFly(nullptr);
562 if(pPos->IsFlyCntPortion())
563 pAsFly = static_cast<SwFlyCntPortion*>(pPos);
564 if( pAsFly || ( pPos->IsMultiPortion()
565 && static_cast<SwMultiPortion*>(pPos)->HasFlyInContent() ) )
566 rLine.SetFlyInCntBase();
567 if(pAsFly && pAsFly->GetAlign() != sw::LineAlign::NONE)
569 pAsFly->SetMax(false);
570 if( !pFlyCnt || pPos->Height() > pFlyCnt->Height() )
571 pFlyCnt = pAsFly;
573 else
575 if( mnAscent < nPosAscent )
576 mnAscent = nPosAscent;
577 if( nMaxDescent < nPosHeight - nPosAscent )
578 nMaxDescent = nPosHeight - nPosAscent;
582 else if( pPos->GetLen() )
583 bTmpDummy = false;
585 if( !HasContent() && !pPos->InNumberGrp() )
587 if ( pPos->InExpGrp() )
589 OUString aText;
590 if( pPos->GetExpText( rInf, aText ) && !aText.isEmpty() )
591 SetContent(true);
593 else if( ( pPos->InTextGrp() || pPos->IsMultiPortion() ) &&
594 pPos->GetLen() )
595 SetContent(true);
598 bTmpDummy &= !HasContent() && ( !pPos->Width() || pPos->IsFlyPortion() );
600 pLast = pPos;
601 pPos = pPos->GetNextPortion();
604 if( pFlyCnt )
606 if( pFlyCnt->Height() == Height() )
608 pFlyCnt->SetMax( true );
609 if( Height() > nMaxDescent + mnAscent )
611 if( sw::LineAlign::BOTTOM == pFlyCnt->GetAlign() )
612 mnAscent = Height() - nMaxDescent;
613 else if( sw::LineAlign::CENTER == pFlyCnt->GetAlign() )
614 mnAscent = ( Height() + mnAscent - nMaxDescent ) / 2;
616 pFlyCnt->SetAscent( mnAscent );
620 if( bTmpDummy && nFlyHeight )
622 mnAscent = nFlyAscent;
623 if( nFlyDescent > nFlyHeight - nFlyAscent )
624 Height( nFlyHeight + nFlyDescent, false );
625 else
627 if (nBreakHeight > nFlyHeight)
629 // The line has no content, but it has a clearing break: then the line
630 // height is not only the intersection of the fly and line's rectangle, but
631 // also includes the clearing break's height.
632 Height(nBreakHeight, false);
634 else
636 Height(nFlyHeight, false);
640 else if( nMaxDescent > Height() - mnAscent )
641 Height( nMaxDescent + mnAscent, false );
643 if( bOnlyPostIts && !( bHasBlankPortion && bHasOnlyBlankPortions ) )
645 Height( rInf.GetFont()->GetHeight( rInf.GetVsh(), *rInf.GetOut() ) );
646 mnAscent = rInf.GetFont()->GetAscent( rInf.GetVsh(), *rInf.GetOut() );
650 else
652 SetContent( !bTmpDummy );
654 // #i3952#
655 if ( bIgnoreBlanksAndTabsForLineHeightCalculation &&
656 lcl_HasOnlyBlanks( rInf.GetText(), rInf.GetLineStart(), rInf.GetLineStart() + GetLen() ) )
658 bHasBlankPortion = true;
662 // #i3952# Whitespace does not increase line height
663 if ( bHasBlankPortion && bHasOnlyBlankPortions )
665 sal_uInt16 nTmpAscent = GetAscent();
666 sal_uInt16 nTmpHeight = Height();
667 rLine.GetAttrHandler().GetDefaultAscentAndHeight( rInf.GetVsh(), *rInf.GetOut(), nTmpAscent, nTmpHeight );
668 if (nTmpAscent < GetAscent() || GetAscent() <= 0)
669 SetAscent(nTmpAscent);
670 if (nTmpHeight < Height() || Height() <= 0)
671 Height(nTmpHeight, false);
674 // Robust:
675 if( nLineWidth < Width() )
676 Width( nLineWidth );
677 SAL_WARN_IF( nLineWidth < Width(), "sw.core", "SwLineLayout::CalcLine: line is bursting" );
678 SetDummy( bTmpDummy );
679 std::pair<SwTextNode const*, sal_Int32> const start(
680 rInf.GetTextFrame()->MapViewToModel(rLine.GetStart()));
681 std::pair<SwTextNode const*, sal_Int32> const end(
682 rInf.GetTextFrame()->MapViewToModel(rLine.GetEnd()));
683 bool bHasRedline = rLine.GetRedln();
684 if( bHasRedline )
686 OUString sRedlineText;
687 bool bHasRedlineEnd;
688 enum RedlineType eRedlineEnd;
689 bHasRedline = rLine.GetRedln()->CheckLine(start.first->GetIndex(), start.second,
690 end.first->GetIndex(), end.second, sRedlineText, bHasRedlineEnd, eRedlineEnd);
691 if( bHasRedline )
693 SetRedlineText( sRedlineText );
694 if( bHasRedlineEnd )
695 SetRedlineEnd( bHasRedlineEnd );
696 if( eRedlineEnd != RedlineType::None )
697 SetRedlineEndType( eRedlineEnd );
700 SetRedline( bHasRedline );
702 // redlining: set crossing out for deleted anchored objects
703 if ( !bHasFlyPortion )
704 return;
706 SwLinePortion *pPos = mpNextPortion;
707 TextFrameIndex nLineLength;
708 while ( pPos )
710 TextFrameIndex const nPorSttIdx = rInf.GetLineStart() + nLineLength;
711 nLineLength += pPos->GetLen();
712 // anchored as characters
713 if( pPos->IsFlyCntPortion() )
715 bool bDeleted = false;
716 size_t nAuthor = std::string::npos;
717 if ( bHasRedline )
719 OUString sRedlineText;
720 bool bHasRedlineEnd;
721 enum RedlineType eRedlineEnd;
722 std::pair<SwTextNode const*, sal_Int32> const flyStart(
723 rInf.GetTextFrame()->MapViewToModel(nPorSttIdx));
724 bool bHasFlyRedline = rLine.GetRedln()->CheckLine(flyStart.first->GetIndex(),
725 flyStart.second, flyStart.first->GetIndex(), flyStart.second, sRedlineText,
726 bHasRedlineEnd, eRedlineEnd, /*pAuthorAtPos=*/&nAuthor);
727 bDeleted = bHasFlyRedline && eRedlineEnd == RedlineType::Delete;
729 static_cast<SwFlyCntPortion*>(pPos)->SetDeleted(bDeleted);
730 static_cast<SwFlyCntPortion*>(pPos)->SetAuthor(nAuthor);
732 // anchored to characters
733 else if ( pPos->IsFlyPortion() )
735 const IDocumentRedlineAccess& rIDRA =
736 rInf.GetTextFrame()->GetDoc().getIDocumentRedlineAccess();
737 SwSortedObjs *pObjs = rInf.GetTextFrame()->GetDrawObjs();
738 if ( pObjs && IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) )
740 for ( size_t i = 0; rInf.GetTextFrame()->GetDrawObjs() && i < pObjs->size(); ++i )
742 SwAnchoredObject* pAnchoredObj = (*rInf.GetTextFrame()->GetDrawObjs())[i];
743 if ( auto pFly = pAnchoredObj->DynCastFlyFrame() )
745 bool bDeleted = false;
746 size_t nAuthor = std::string::npos;
747 const SwFormatAnchor& rAnchor = pAnchoredObj->GetFrameFormat().GetAnchor();
748 if ( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR )
750 SwPosition aAnchor = *rAnchor.GetContentAnchor();
751 SwRedlineTable::size_type n = 0;
752 const SwRangeRedline* pFnd =
753 rIDRA.GetRedlineTable().FindAtPosition( aAnchor, n );
754 if ( pFnd && RedlineType::Delete == pFnd->GetType() )
756 bDeleted = true;
757 nAuthor = pFnd->GetAuthor();
760 pFly->SetDeleted(bDeleted);
761 pFly->SetAuthor(nAuthor);
766 pPos = pPos->GetNextPortion();
770 // #i47162# - add optional parameter <_bNoFlyCntPorAndLinePor>
771 // to control, if the fly content portions and line portion are considered.
772 void SwLineLayout::MaxAscentDescent( SwTwips& _orAscent,
773 SwTwips& _orDescent,
774 SwTwips& _orObjAscent,
775 SwTwips& _orObjDescent,
776 const SwLinePortion* _pDontConsiderPortion,
777 const bool _bNoFlyCntPorAndLinePor ) const
779 _orAscent = 0;
780 _orDescent = 0;
781 _orObjAscent = 0;
782 _orObjDescent = 0;
784 const SwLinePortion* pTmpPortion = this;
785 if ( !pTmpPortion->GetLen() && pTmpPortion->GetNextPortion() )
787 pTmpPortion = pTmpPortion->GetNextPortion();
790 while ( pTmpPortion )
792 if ( !pTmpPortion->IsBreakPortion() && !pTmpPortion->IsFlyPortion() &&
793 // tdf#130804 ignore bookmark portions
794 pTmpPortion->GetWhichPor() != PortionType::Bookmark &&
795 ( !_bNoFlyCntPorAndLinePor ||
796 ( !pTmpPortion->IsFlyCntPortion() &&
797 !(pTmpPortion == this && pTmpPortion->GetNextPortion() ) ) ) )
799 SwTwips nPortionAsc = pTmpPortion->GetAscent();
800 SwTwips nPortionDesc = pTmpPortion->Height() - nPortionAsc;
802 const bool bFlyCmp = pTmpPortion->IsFlyCntPortion() ?
803 static_cast<const SwFlyCntPortion*>(pTmpPortion)->IsMax() :
804 ( pTmpPortion != _pDontConsiderPortion );
806 if ( bFlyCmp )
808 _orObjAscent = std::max( _orObjAscent, nPortionAsc );
809 _orObjDescent = std::max( _orObjDescent, nPortionDesc );
812 if ( !pTmpPortion->IsFlyCntPortion() && !pTmpPortion->IsGrfNumPortion() )
814 _orAscent = std::max( _orAscent, nPortionAsc );
815 _orDescent = std::max( _orDescent, nPortionDesc );
818 pTmpPortion = pTmpPortion->GetNextPortion();
822 void SwLineLayout::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
823 TextFrameIndex& nOffset) const
825 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwLineLayout"));
826 dumpAsXmlAttributes(pWriter, rText, nOffset);
827 nOffset += GetLen();
829 (void)xmlTextWriterEndElement(pWriter);
832 void SwLineLayout::ResetFlags()
834 m_bFormatAdj = m_bDummy = m_bEndHyph = m_bMidHyph = m_bFly
835 = m_bRest = m_bBlinking = m_bClipping = m_bContent = m_bRedline
836 = m_bRedlineEnd = m_bForcedLeftMargin = m_bHanging = false;
837 m_eRedlineEnd = RedlineType::None;
840 SwLineLayout::SwLineLayout()
841 : m_pNext( nullptr ),
842 m_nRealHeight( 0 ),
843 m_nTextHeight( 0 ),
844 m_bUnderscore( false )
846 ResetFlags();
847 SetWhichPor( PortionType::Lay );
850 SwLinePortion *SwLineLayout::GetFirstPortion() const
852 const SwLinePortion *pRet = mpNextPortion ? mpNextPortion : this;
853 return const_cast<SwLinePortion*>(pRet);
856 SwCharRange &SwCharRange::operator+=(const SwCharRange &rRange)
858 if (TextFrameIndex(0) != rRange.m_nLen)
860 if (TextFrameIndex(0) == m_nLen) {
861 m_nStart = rRange.m_nStart;
862 m_nLen = rRange.m_nLen ;
864 else {
865 if(rRange.m_nStart + rRange.m_nLen > m_nStart + m_nLen) {
866 m_nLen = rRange.m_nStart + rRange.m_nLen - m_nStart;
868 if(rRange.m_nStart < m_nStart) {
869 m_nLen += m_nStart - rRange.m_nStart;
870 m_nStart = rRange.m_nStart;
874 return *this;
877 SwScriptInfo::SwScriptInfo()
878 : m_nInvalidityPos(0)
879 , m_nDefaultDir(0)
883 SwScriptInfo::~SwScriptInfo()
887 // Converts i18n Script Type (LATIN, ASIAN, COMPLEX, WEAK) to
888 // Sw Script Types (SwFontScript::Latin, SwFontScript::CJK, SwFontScript::CTL), used to identify the font
889 static SwFontScript lcl_ScriptToFont(sal_uInt16 const nScript)
891 switch ( nScript ) {
892 case i18n::ScriptType::LATIN : return SwFontScript::Latin;
893 case i18n::ScriptType::ASIAN : return SwFontScript::CJK;
894 case i18n::ScriptType::COMPLEX : return SwFontScript::CTL;
897 OSL_FAIL( "Somebody tells lies about the script type!" );
898 return SwFontScript::Latin;
901 SwFontScript SwScriptInfo::WhichFont(TextFrameIndex const nIdx) const
903 const sal_uInt16 nScript(ScriptType(nIdx));
904 return lcl_ScriptToFont(nScript);
907 SwFontScript SwScriptInfo::WhichFont(sal_Int32 nIdx, OUString const& rText)
909 const sal_uInt16 nScript(g_pBreakIt->GetRealScriptOfText(rText, nIdx));
910 return lcl_ScriptToFont(nScript);
913 static Color getBookmarkColor(const SwTextNode& rNode, const sw::mark::IBookmark* pBookmark)
915 // search custom color in metadata, otherwise use COL_TRANSPARENT;
916 Color c = COL_TRANSPARENT;
920 SwDoc& rDoc = const_cast<SwDoc&>(rNode.GetDoc());
921 const uno::Reference< text::XTextContent > xRef = SwXBookmark::CreateXBookmark(rDoc,
922 const_cast<sw::mark::IMark*>(static_cast<const sw::mark::IMark*>(pBookmark)));
923 const css::uno::Reference<css::rdf::XResource> xSubject(xRef, uno::UNO_QUERY);
924 uno::Reference<frame::XModel> xModel = rDoc.GetDocShell()->GetBaseModel();
926 static uno::Reference< uno::XComponentContext > xContext(
927 ::comphelper::getProcessComponentContext());
929 static uno::Reference< rdf::XURI > xODF_SHADING(
930 rdf::URI::createKnown(xContext, rdf::URIs::LO_EXT_SHADING), uno::UNO_SET_THROW);
932 uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(
933 rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY);
934 const uno::Reference<rdf::XRepository>& xRepository =
935 xDocumentMetadataAccess->getRDFRepository();
936 const uno::Reference<container::XEnumeration> xEnum(
937 xRepository->getStatements(xSubject, xODF_SHADING, nullptr), uno::UNO_SET_THROW);
939 rdf::Statement stmt;
940 if ( xEnum->hasMoreElements() && (xEnum->nextElement() >>= stmt) )
942 const uno::Reference<rdf::XLiteral> xObject(stmt.Object, uno::UNO_QUERY);
943 if ( xObject.is() )
944 c = Color::STRtoRGB(xObject->getValue());
947 catch (const lang::IllegalArgumentException&)
951 return c;
954 static void InitBookmarks(
955 std::optional<std::vector<sw::Extent>::const_iterator> oPrevIter,
956 std::vector<sw::Extent>::const_iterator iter,
957 std::vector<sw::Extent>::const_iterator const end,
958 TextFrameIndex nOffset,
959 std::vector<std::pair<sw::mark::IBookmark const*, SwScriptInfo::MarkKind>> & rBookmarks,
960 std::vector<std::tuple<TextFrameIndex, SwScriptInfo::MarkKind, Color, OUString>> & o_rBookmarks)
962 SwTextNode const*const pNode(iter->pNode);
963 for (auto const& it : rBookmarks)
965 assert(iter->pNode == pNode || pNode->GetIndex() < iter->pNode->GetIndex());
966 assert(!oPrevIter || (*oPrevIter)->pNode->GetIndex() <= pNode->GetIndex());
968 // search for custom bookmark boundary mark color
969 Color c = getBookmarkColor(*pNode, it.first);
971 switch (it.second)
973 case SwScriptInfo::MarkKind::Start:
975 // SwUndoSaveContent::DelContentIndex() is rather messy but
976 // apparently bookmarks "on the edge" are deleted if
977 // * point: equals start-of-selection (not end-of-selection)
978 // * expanded: one position equals edge of selection
979 // and other does not (is inside)
980 // interesting case: if end[/start] of the mark is on the
981 // start of first[/end of last] extent, and the other one
982 // is outside this merged paragraph, is it deleted or not?
983 // assume "no" because the line break it contains isn't deleted.
984 SwPosition const& rStart(it.first->GetMarkStart());
985 SwPosition const& rEnd(it.first->GetMarkEnd());
986 assert(&rStart.GetNode() == pNode);
987 while (iter != end)
989 if (&rStart.GetNode() != iter->pNode // iter moved to next node
990 || rStart.GetContentIndex() < iter->nStart)
992 if (rEnd.GetNodeIndex() < iter->pNode->GetIndex()
993 || (&rEnd.GetNode() == iter->pNode && rEnd.GetContentIndex() <= iter->nStart))
995 break; // deleted - skip it
997 else
999 o_rBookmarks.emplace_back(nOffset, it.second, c, it.first->GetName());
1000 break;
1003 else if (rStart.GetContentIndex() <= iter->nEnd)
1005 auto const iterNext(iter + 1);
1006 if (rStart.GetContentIndex() == iter->nEnd
1007 && (iterNext == end
1008 ? &rEnd.GetNode() == iter->pNode
1009 : (rEnd.GetNodeIndex() < iterNext->pNode->GetIndex()
1010 || (&rEnd.GetNode() == iterNext->pNode && rEnd.GetContentIndex() < iterNext->nStart))))
1012 break; // deleted - skip it
1014 else
1016 o_rBookmarks.emplace_back(
1017 nOffset + TextFrameIndex(rStart.GetContentIndex() - iter->nStart),
1018 it.second, c, it.first->GetName());
1019 break;
1022 else
1024 nOffset += TextFrameIndex(iter->nEnd - iter->nStart);
1025 oPrevIter = iter;
1026 ++iter; // bookmarks are sorted...
1029 if (iter == end)
1031 if (pNode->GetIndex() < rEnd.GetNodeIndex()) // pNode is last node of merged
1033 break; // deleted - skip it
1035 else
1037 o_rBookmarks.emplace_back(nOffset, it.second, c, it.first->GetName());
1040 break;
1042 case SwScriptInfo::MarkKind::End:
1044 SwPosition const& rEnd(it.first->GetMarkEnd());
1045 assert(&rEnd.GetNode() == pNode);
1046 while (true)
1048 if (iter == end
1049 || &rEnd.GetNode() != iter->pNode // iter moved to next node
1050 || rEnd.GetContentIndex() <= iter->nStart)
1052 SwPosition const& rStart(it.first->GetMarkStart());
1053 // oPrevIter may point to pNode or a preceding node
1054 if (oPrevIter
1055 ? ((*oPrevIter)->pNode->GetIndex() < rStart.GetNodeIndex()
1056 || ((*oPrevIter)->pNode == &rStart.GetNode()
1057 && ((iter != end && &rEnd.GetNode() == iter->pNode && rEnd.GetContentIndex() == iter->nStart)
1058 ? (*oPrevIter)->nEnd < rStart.GetContentIndex()
1059 : (*oPrevIter)->nEnd <= rStart.GetContentIndex())))
1060 : rStart.GetNode() == rEnd.GetNode())
1062 break; // deleted - skip it
1064 else
1066 o_rBookmarks.emplace_back(nOffset, it.second, c, it.first->GetName());
1067 break;
1070 else if (rEnd.GetContentIndex() <= iter->nEnd)
1072 o_rBookmarks.emplace_back(
1073 nOffset + TextFrameIndex(rEnd.GetContentIndex() - iter->nStart),
1074 it.second, c, it.first->GetName());
1075 break;
1077 else
1079 nOffset += TextFrameIndex(iter->nEnd - iter->nStart);
1080 oPrevIter = iter;
1081 ++iter;
1084 break;
1086 case SwScriptInfo::MarkKind::Point:
1088 SwPosition const& rPos(it.first->GetMarkPos());
1089 assert(&rPos.GetNode() == pNode);
1090 while (iter != end)
1092 if (&rPos.GetNode() != iter->pNode // iter moved to next node
1093 || rPos.GetContentIndex() < iter->nStart)
1095 break; // deleted - skip it
1097 else if (rPos.GetContentIndex() <= iter->nEnd)
1099 if (rPos.GetContentIndex() == iter->nEnd
1100 && rPos.GetContentIndex() != iter->pNode->Len())
1102 break; // deleted - skip it
1104 else
1106 o_rBookmarks.emplace_back(
1107 nOffset + TextFrameIndex(rPos.GetContentIndex() - iter->nStart),
1108 it.second, c, it.first->GetName());
1110 break;
1112 else
1114 nOffset += TextFrameIndex(iter->nEnd - iter->nStart);
1115 oPrevIter = iter;
1116 ++iter;
1119 break;
1122 if (iter == end)
1124 break; // remaining marks are hidden
1129 // searches for script changes in rText and stores them
1130 void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode,
1131 sw::MergedPara const*const pMerged)
1133 InitScriptInfo( rNode, pMerged, m_nDefaultDir == UBIDI_RTL );
1136 void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode,
1137 sw::MergedPara const*const pMerged, bool bRTL)
1139 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
1141 const OUString& rText(pMerged ? pMerged->mergedText : rNode.GetText());
1143 // HIDDEN TEXT INFORMATION
1145 m_Bookmarks.clear();
1146 m_HiddenChg.clear();
1147 if (pMerged)
1149 SwTextNode const* pNode(nullptr);
1150 TextFrameIndex nOffset(0);
1151 std::optional<std::vector<sw::Extent>::const_iterator> oPrevIter;
1152 for (auto iter = pMerged->extents.begin(); iter != pMerged->extents.end();
1153 oPrevIter = iter)
1155 if (iter->pNode == pNode)
1157 nOffset += TextFrameIndex(iter->nEnd - iter->nStart);
1158 ++iter;
1159 continue; // skip extents at end of previous node
1161 pNode = iter->pNode;
1162 Range aRange( 0, pNode->Len() > 0 ? pNode->Len() - 1 : 0 );
1163 MultiSelection aHiddenMulti( aRange );
1164 std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> bookmarks;
1165 CalcHiddenRanges(*pNode, aHiddenMulti, &bookmarks);
1167 InitBookmarks(oPrevIter, iter, pMerged->extents.end(), nOffset, bookmarks, m_Bookmarks);
1169 for (sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i)
1171 const Range& rRange = aHiddenMulti.GetRange( i );
1172 const sal_Int32 nStart = rRange.Min();
1173 const sal_Int32 nEnd = rRange.Max() + 1;
1174 bool isStartHandled(false);
1175 ::std::optional<sal_Int32> oExtend;
1177 if (nEnd <= iter->nStart)
1178 { // entirely in gap, skip this hidden range
1179 continue;
1184 if (!isStartHandled && nStart <= iter->nEnd)
1186 isStartHandled = true;
1187 if (nStart <= iter->nStart && !m_HiddenChg.empty()
1188 && m_HiddenChg.back() == nOffset)
1190 // previous one went until end of extent, extend it
1191 oExtend.emplace(::std::min(iter->nEnd, nEnd) - ::std::max(iter->nStart, nStart));
1193 else
1195 m_HiddenChg.push_back(nOffset + TextFrameIndex(::std::max(nStart - iter->nStart, sal_Int32(0))));
1198 else if (oExtend)
1200 *oExtend += ::std::min(iter->nEnd, nEnd) - iter->nStart;
1202 if (nEnd <= iter->nEnd)
1204 if (oExtend)
1206 m_HiddenChg.back() += TextFrameIndex(*oExtend);
1208 else
1210 m_HiddenChg.push_back(nOffset + TextFrameIndex(::std::max(nEnd - iter->nStart, sal_Int32(0))));
1212 break; // iterate to next hidden range
1214 nOffset += TextFrameIndex(iter->nEnd - iter->nStart);
1215 ++iter;
1217 while (iter != pMerged->extents.end() && iter->pNode == pNode);
1218 if (iter == pMerged->extents.end() || iter->pNode != pNode)
1220 if (isStartHandled)
1221 { // dangling end
1222 if (oExtend)
1224 m_HiddenChg.back() += TextFrameIndex(*oExtend);
1226 else
1228 m_HiddenChg.push_back(nOffset);
1230 } // else: beyond last extent in node, ignore
1231 break; // skip hidden ranges beyond last extent in node
1236 else
1238 Range aRange( 0, !rText.isEmpty() ? rText.getLength() - 1 : 0 );
1239 MultiSelection aHiddenMulti( aRange );
1240 std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> bookmarks;
1241 CalcHiddenRanges(rNode, aHiddenMulti, &bookmarks);
1243 for (auto const& it : bookmarks)
1245 // don't show __RefHeading__ bookmarks, which are hidden in Navigator, too
1246 // (They are inserted automatically e.g. with the ToC at the beginning of
1247 // the headings)
1248 if (it.first->GetName().startsWith(
1249 IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix()))
1251 continue;
1254 // search for custom bookmark boundary mark color
1255 Color c = getBookmarkColor(rNode, it.first);
1257 switch (it.second)
1259 case MarkKind::Start:
1260 m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkStart().GetContentIndex()), it.second, c, it.first->GetName());
1261 break;
1262 case MarkKind::End:
1263 m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkEnd().GetContentIndex()), it.second, c, it.first->GetName());
1264 break;
1265 case MarkKind::Point:
1266 m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkPos().GetContentIndex()), it.second, c, it.first->GetName());
1267 break;
1271 m_HiddenChg.reserve( aHiddenMulti.GetRangeCount() * 2 );
1272 for (sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i)
1274 const Range& rRange = aHiddenMulti.GetRange( i );
1275 const sal_Int32 nStart = rRange.Min();
1276 const sal_Int32 nEnd = rRange.Max() + (rText.isEmpty() ? 0 : 1);
1278 m_HiddenChg.push_back( TextFrameIndex(nStart) );
1279 m_HiddenChg.push_back( TextFrameIndex(nEnd) );
1283 // SCRIPT AND SCRIPT RELATED INFORMATION
1285 TextFrameIndex nChg = m_nInvalidityPos;
1287 // COMPLETE_STRING means the data structure is up to date
1288 m_nInvalidityPos = TextFrameIndex(COMPLETE_STRING);
1290 // this is the default direction
1291 m_nDefaultDir = static_cast<sal_uInt8>(bRTL ? UBIDI_RTL : UBIDI_LTR);
1293 // counter for script info arrays
1294 size_t nCnt = 0;
1295 // counter for compression information arrays
1296 size_t nCntComp = 0;
1297 // counter for kashida array
1298 size_t nCntKash = 0;
1300 sal_Int16 nScript = i18n::ScriptType::LATIN;
1302 // compression type
1303 const CharCompressType aCompEnum = rNode.getIDocumentSettingAccess()->getCharacterCompressionType();
1305 auto const& rParaItems((pMerged ? *pMerged->pParaPropsNode : rNode).GetSwAttrSet());
1306 // justification type
1307 const bool bAdjustBlock = SvxAdjust::Block == rParaItems.GetAdjust().GetAdjust();
1309 // FIND INVALID RANGES IN SCRIPT INFO ARRAYS:
1311 if( nChg )
1313 // if change position = 0 we do not use any data from the arrays
1314 // because by deleting all characters of the first group at the beginning
1315 // of a paragraph nScript is set to a wrong value
1316 SAL_WARN_IF( !CountScriptChg(), "sw.core", "Where're my changes of script?" );
1317 while( nCnt < CountScriptChg() )
1319 if ( nChg > GetScriptChg( nCnt ) )
1320 nCnt++;
1321 else
1323 nScript = GetScriptType( nCnt );
1324 break;
1327 if( CharCompressType::NONE != aCompEnum )
1329 while( nCntComp < CountCompChg() )
1331 if ( nChg <= GetCompStart( nCntComp ) )
1332 break;
1333 nCntComp++;
1336 if ( bAdjustBlock )
1338 while( nCntKash < CountKashida() )
1340 if ( nChg <= GetKashida( nCntKash ) )
1341 break;
1342 nCntKash++;
1347 // ADJUST nChg VALUE:
1349 // by stepping back one position we know that we are inside a group
1350 // declared as an nScript group
1351 if ( nChg )
1352 --nChg;
1354 const TextFrameIndex nGrpStart = nCnt ? GetScriptChg(nCnt - 1) : TextFrameIndex(0);
1356 // we go back in our group until we reach the first character of
1357 // type nScript
1358 while ( nChg > nGrpStart &&
1359 nScript != g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg)))
1360 --nChg;
1362 // If we are at the start of a group, we do not trust nScript,
1363 // we better get nScript from the breakiterator:
1364 if ( nChg == nGrpStart )
1365 nScript = static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg)));
1367 // INVALID DATA FROM THE SCRIPT INFO ARRAYS HAS TO BE DELETED:
1369 // remove invalid entries from script information arrays
1370 m_ScriptChanges.erase(m_ScriptChanges.begin() + nCnt, m_ScriptChanges.end());
1372 // get the start of the last compression group
1373 TextFrameIndex nLastCompression = nChg;
1374 if( nCntComp )
1376 --nCntComp;
1377 nLastCompression = GetCompStart( nCntComp );
1378 if( nChg >= nLastCompression + GetCompLen( nCntComp ) )
1380 nLastCompression = nChg;
1381 ++nCntComp;
1385 // remove invalid entries from compression information arrays
1386 m_CompressionChanges.erase(m_CompressionChanges.begin() + nCntComp,
1387 m_CompressionChanges.end());
1389 // get the start of the last kashida group
1390 TextFrameIndex nLastKashida = nChg;
1391 if( nCntKash && i18n::ScriptType::COMPLEX == nScript )
1393 --nCntKash;
1394 nLastKashida = GetKashida( nCntKash );
1397 // remove invalid entries from kashida array
1398 m_Kashida.erase(m_Kashida.begin() + nCntKash, m_Kashida.end());
1400 // TAKE CARE OF WEAK CHARACTERS: WE MUST FIND AN APPROPRIATE
1401 // SCRIPT FOR WEAK CHARACTERS AT THE BEGINNING OF A PARAGRAPH
1403 if (WEAK == g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg)))
1405 // If the beginning of the current group is weak, this means that
1406 // all of the characters in this group are weak. We have to assign
1407 // the scripts to these characters depending on the fonts which are
1408 // set for these characters to display them.
1409 TextFrameIndex nEnd(
1410 g_pBreakIt->GetBreakIter()->endOfScript(rText, sal_Int32(nChg), WEAK));
1412 if (nEnd > TextFrameIndex(rText.getLength()) || nEnd < TextFrameIndex(0))
1413 nEnd = TextFrameIndex(rText.getLength());
1415 nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() );
1417 SAL_WARN_IF( i18n::ScriptType::LATIN != nScript &&
1418 i18n::ScriptType::ASIAN != nScript &&
1419 i18n::ScriptType::COMPLEX != nScript, "sw.core", "Wrong default language" );
1421 nChg = nEnd;
1423 // Get next script type or set to weak in order to exit
1424 sal_uInt8 nNextScript = (nEnd < TextFrameIndex(rText.getLength()))
1425 ? static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nEnd)))
1426 : sal_uInt8(WEAK);
1428 if ( nScript != nNextScript )
1430 m_ScriptChanges.emplace_back(nEnd, nScript);
1431 nCnt++;
1432 nScript = nNextScript;
1436 // UPDATE THE SCRIPT INFO ARRAYS:
1438 while (nChg < TextFrameIndex(rText.getLength())
1439 || (m_ScriptChanges.empty() && rText.isEmpty()))
1441 SAL_WARN_IF( i18n::ScriptType::WEAK == nScript,
1442 "sw.core", "Inserting WEAK into SwScriptInfo structure" );
1444 TextFrameIndex nSearchStt = nChg;
1445 nChg = TextFrameIndex(g_pBreakIt->GetBreakIter()->endOfScript(
1446 rText, sal_Int32(nSearchStt), nScript));
1448 if (nChg > TextFrameIndex(rText.getLength()) || nChg < TextFrameIndex(0))
1449 nChg = TextFrameIndex(rText.getLength());
1451 // special case for dotted circle since it can be used with complex
1452 // before a mark, so we want it associated with the mark's script
1453 // tdf#112594: another soecial case for NNBSP followed by a Mongolian
1454 // character, since NNBSP has special uses in Mongolian (tdf#112594)
1455 auto nPos = sal_Int32(nChg);
1456 auto nPrevPos = nPos;
1457 auto nPrevChar = rText.iterateCodePoints(&nPrevPos, -1);
1458 if (nChg < TextFrameIndex(rText.getLength()) && nChg > TextFrameIndex(0)
1459 && (i18n::ScriptType::WEAK ==
1460 g_pBreakIt->GetBreakIter()->getScriptType(rText, nPrevPos)))
1462 auto nChar = rText.iterateCodePoints(&nPos, 0);
1463 auto nType = u_charType(nChar);
1464 if (nType == U_NON_SPACING_MARK ||
1465 nType == U_ENCLOSING_MARK ||
1466 nType == U_COMBINING_SPACING_MARK ||
1467 (nPrevChar == CHAR_NNBSP &&
1468 u_getIntPropertyValue(nChar, UCHAR_SCRIPT) == USCRIPT_MONGOLIAN))
1470 nPos = nPrevPos;
1473 m_ScriptChanges.emplace_back(TextFrameIndex(nPos), nScript);
1474 ++nCnt;
1476 // if current script is asian, we search for compressible characters
1477 // in this range
1478 if ( CharCompressType::NONE != aCompEnum &&
1479 i18n::ScriptType::ASIAN == nScript )
1481 CompType ePrevState = NONE;
1482 CompType eState = NONE;
1483 TextFrameIndex nPrevChg = nLastCompression;
1485 while ( nLastCompression < nChg )
1487 sal_Unicode cChar = rText[ sal_Int32(nLastCompression) ];
1489 // examine current character
1490 switch ( cChar )
1492 // Left punctuation found
1493 case 0x3008: case 0x300A: case 0x300C: case 0x300E:
1494 case 0x3010: case 0x3014: case 0x3016: case 0x3018:
1495 case 0x301A: case 0x301D:
1496 case 0xFF08: case 0xFF3B: case 0xFF5B:
1497 eState = SPECIAL_LEFT;
1498 break;
1499 // Right punctuation found
1500 case 0x3009: case 0x300B:
1501 case 0x300D: case 0x300F: case 0x3011: case 0x3015:
1502 case 0x3017: case 0x3019: case 0x301B: case 0x301E:
1503 case 0x301F:
1504 case 0xFF09: case 0xFF3D: case 0xFF5D:
1505 eState = SPECIAL_RIGHT;
1506 break;
1507 case 0x3001: case 0x3002: // Fullstop or comma
1508 case 0xFF0C: case 0xFF0E: case 0xFF1A: case 0xFF1B:
1509 eState = SPECIAL_MIDDLE ;
1510 break;
1511 default:
1512 eState = ( 0x3040 <= cChar && 0x3100 > cChar ) ? KANA : NONE;
1515 // insert range of compressible characters
1516 if( ePrevState != eState )
1518 if ( ePrevState != NONE )
1520 // insert start and type
1521 if ( CharCompressType::PunctuationAndKana == aCompEnum ||
1522 ePrevState != KANA )
1524 m_CompressionChanges.emplace_back(nPrevChg,
1525 nLastCompression - nPrevChg, ePrevState);
1529 ePrevState = eState;
1530 nPrevChg = nLastCompression;
1533 nLastCompression++;
1536 // we still have to examine last entry
1537 if ( ePrevState != NONE )
1539 // insert start and type
1540 if ( CharCompressType::PunctuationAndKana == aCompEnum ||
1541 ePrevState != KANA )
1543 m_CompressionChanges.emplace_back(nPrevChg,
1544 nLastCompression - nPrevChg, ePrevState);
1549 // we search for connecting opportunities (kashida)
1550 else if ( bAdjustBlock && i18n::ScriptType::COMPLEX == nScript )
1552 // sw_redlinehide: this is the only place that uses SwScanner with
1553 // frame text, so we convert to sal_Int32 here
1554 std::function<LanguageType (sal_Int32, sal_Int32, bool)> const pGetLangOfCharM(
1555 [&pMerged](sal_Int32 const nBegin, sal_uInt16 const script, bool const bNoChar)
1557 std::pair<SwTextNode const*, sal_Int32> const pos(
1558 sw::MapViewToModel(*pMerged, TextFrameIndex(nBegin)));
1559 return pos.first->GetLang(pos.second, bNoChar ? 0 : 1, script);
1561 std::function<LanguageType (sal_Int32, sal_Int32, bool)> const pGetLangOfChar1(
1562 [&rNode](sal_Int32 const nBegin, sal_uInt16 const script, bool const bNoChar)
1563 { return rNode.GetLang(nBegin, bNoChar ? 0 : 1, script); });
1564 auto pGetLangOfChar(pMerged ? pGetLangOfCharM : pGetLangOfChar1);
1565 SwScanner aScanner( std::move(pGetLangOfChar), rText, nullptr, ModelToViewHelper(),
1566 i18n::WordType::DICTIONARY_WORD,
1567 sal_Int32(nLastKashida), sal_Int32(nChg));
1569 // the search has to be performed on a per word base
1570 while ( aScanner.NextWord() )
1572 const OUString& rWord = aScanner.GetWord();
1574 sal_Int32 nIdx = 0, nPrevIdx = 0;
1575 sal_Int32 nKashidaPos = -1;
1576 sal_Unicode cCh, cPrevCh = 0;
1578 int nPriorityLevel = 7; // 0..6 = level found
1579 // 7 not found
1581 sal_Int32 nWordLen = rWord.getLength();
1583 // ignore trailing vowel chars
1584 while( nWordLen && isTransparentChar( rWord[ nWordLen - 1 ] ))
1585 --nWordLen;
1587 while (nIdx < nWordLen)
1589 cCh = rWord[ nIdx ];
1591 // 1. Priority:
1592 // after user inserted kashida
1593 if ( 0x640 == cCh )
1595 nKashidaPos = aScanner.GetBegin() + nIdx;
1596 nPriorityLevel = 0;
1599 // 2. Priority:
1600 // after a Seen or Sad
1601 if (nPriorityLevel >= 1 && nIdx < nWordLen - 1)
1603 if( isSeenOrSadChar( cCh )
1604 && (rWord[ nIdx+1 ] != 0x200C) ) // #i98410#: prevent ZWNJ expansion
1606 nKashidaPos = aScanner.GetBegin() + nIdx;
1607 nPriorityLevel = 1;
1611 // 3. Priority:
1612 // before final form of Teh Marbuta, Heh, Dal
1613 if ( nPriorityLevel >= 2 && nIdx > 0 )
1615 if ( isTehMarbutaChar ( cCh ) || // Teh Marbuta (right joining)
1616 isDalChar ( cCh ) || // Dal (right joining) final form may appear in the middle of word
1617 ( isHehChar ( cCh ) && nIdx == nWordLen - 1)) // Heh (dual joining) only at end of word
1620 SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" );
1621 // check if character is connectable to previous character,
1622 if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
1624 nKashidaPos = aScanner.GetBegin() + nPrevIdx;
1625 nPriorityLevel = 2;
1630 // 4. Priority:
1631 // before final form of Alef, Tah, Lam, Kaf or Gaf
1632 if ( nPriorityLevel >= 3 && nIdx > 0 )
1634 if ( isAlefChar ( cCh ) || // Alef (right joining) final form may appear in the middle of word
1635 (( isLamChar ( cCh ) || // Lam,
1636 isTahChar ( cCh ) || // Tah,
1637 isKafChar ( cCh ) || // Kaf (all dual joining)
1638 isGafChar ( cCh ) )
1639 && nIdx == nWordLen - 1)) // only at end of word
1641 SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" );
1642 // check if character is connectable to previous character,
1643 if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
1645 nKashidaPos = aScanner.GetBegin() + nPrevIdx;
1646 nPriorityLevel = 3;
1651 // 5. Priority:
1652 // before medial Beh-like
1653 if ( nPriorityLevel >= 4 && nIdx > 0 && nIdx < nWordLen - 1 )
1655 if ( isBehChar ( cCh ) )
1657 // check if next character is Reh or Yeh-like
1658 sal_Unicode cNextCh = rWord[ nIdx + 1 ];
1659 if ( isRehChar ( cNextCh ) || isYehChar ( cNextCh ))
1661 SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" );
1662 // check if character is connectable to previous character,
1663 if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
1665 nKashidaPos = aScanner.GetBegin() + nPrevIdx;
1666 nPriorityLevel = 4;
1672 // 6. Priority:
1673 // before the final form of Waw, Ain, Qaf and Feh
1674 if ( nPriorityLevel >= 5 && nIdx > 0 )
1676 if ( isWawChar ( cCh ) || // Wav (right joining)
1677 // final form may appear in the middle of word
1678 (( isAinChar ( cCh ) || // Ain (dual joining)
1679 isQafChar ( cCh ) || // Qaf (dual joining)
1680 isFehChar ( cCh ) ) // Feh (dual joining)
1681 && nIdx == nWordLen - 1)) // only at end of word
1683 SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" );
1684 // check if character is connectable to previous character,
1685 if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
1687 nKashidaPos = aScanner.GetBegin() + nPrevIdx;
1688 nPriorityLevel = 5;
1693 // other connecting possibilities
1694 if ( nPriorityLevel >= 6 && nIdx > 0 )
1696 // Reh, Zain
1697 if ( isRehChar ( cCh ) )
1699 SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" );
1700 // check if character is connectable to previous character,
1701 if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
1703 nKashidaPos = aScanner.GetBegin() + nPrevIdx;
1704 nPriorityLevel = 6;
1709 // Do not consider vowel marks when checking if a character
1710 // can be connected to previous character.
1711 if ( !isTransparentChar ( cCh) )
1713 cPrevCh = cCh;
1714 nPrevIdx = nIdx;
1717 ++nIdx;
1718 } // end of current word
1720 if ( -1 != nKashidaPos )
1722 m_Kashida.insert(m_Kashida.begin() + nCntKash, TextFrameIndex(nKashidaPos));
1723 nCntKash++;
1725 } // end of kashida search
1728 if (nChg < TextFrameIndex(rText.getLength()))
1729 nScript = static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg)));
1731 nLastCompression = nChg;
1732 nLastKashida = nChg;
1735 #if OSL_DEBUG_LEVEL > 0
1736 // check kashida data
1737 TextFrameIndex nTmpKashidaPos(-1);
1738 bool bWrongKash = false;
1739 for (size_t i = 0; i < m_Kashida.size(); ++i)
1741 TextFrameIndex nCurrKashidaPos = GetKashida( i );
1742 if ( nCurrKashidaPos <= nTmpKashidaPos )
1744 bWrongKash = true;
1745 break;
1747 nTmpKashidaPos = nCurrKashidaPos;
1749 SAL_WARN_IF( bWrongKash, "sw.core", "Kashida array contains wrong data" );
1750 #endif
1752 // remove invalid entries from direction information arrays
1753 m_DirectionChanges.clear();
1755 // Perform Unicode Bidi Algorithm for text direction information
1757 UpdateBidiInfo( rText );
1759 // #i16354# Change script type for RTL text to CTL:
1760 // 1. All text in RTL runs will use the CTL font
1761 // #i89825# change the script type also to CTL (hennerdrewes)
1762 // 2. Text in embedded LTR runs that does not have any strong LTR characters (numbers!)
1763 for (size_t nDirIdx = 0; nDirIdx < m_DirectionChanges.size(); ++nDirIdx)
1765 const sal_uInt8 nCurrDirType = GetDirType( nDirIdx );
1766 // nStart is start of RTL run:
1767 const TextFrameIndex nStart = nDirIdx > 0 ? GetDirChg(nDirIdx - 1) : TextFrameIndex(0);
1768 // nEnd is end of RTL run:
1769 const TextFrameIndex nEnd = GetDirChg( nDirIdx );
1771 if ( nCurrDirType % 2 == UBIDI_RTL || // text in RTL run
1772 (nCurrDirType > UBIDI_LTR && // non-strong text in embedded LTR run
1773 !lcl_HasStrongLTR(rText, sal_Int32(nStart), sal_Int32(nEnd))))
1775 // nScriptIdx points into the ScriptArrays:
1776 size_t nScriptIdx = 0;
1778 // Skip entries in ScriptArray which are not inside the RTL run:
1779 // Make nScriptIdx become the index of the script group with
1780 // 1. nStartPosOfGroup <= nStart and
1781 // 2. nEndPosOfGroup > nStart
1782 while ( GetScriptChg( nScriptIdx ) <= nStart )
1783 ++nScriptIdx;
1785 const TextFrameIndex nStartPosOfGroup = nScriptIdx
1786 ? GetScriptChg(nScriptIdx - 1)
1787 : TextFrameIndex(0);
1788 const sal_uInt8 nScriptTypeOfGroup = GetScriptType( nScriptIdx );
1790 SAL_WARN_IF( nStartPosOfGroup > nStart || GetScriptChg( nScriptIdx ) <= nStart,
1791 "sw.core", "Script override with CTL font trouble" );
1793 // Check if we have to insert a new script change at
1794 // position nStart. If nStartPosOfGroup < nStart,
1795 // we have to insert a new script change:
1796 if (nStart > TextFrameIndex(0) && nStartPosOfGroup < nStart)
1798 m_ScriptChanges.insert(m_ScriptChanges.begin() + nScriptIdx,
1799 ScriptChangeInfo(nStart, nScriptTypeOfGroup) );
1800 ++nScriptIdx;
1803 // Remove entries in ScriptArray which end inside the RTL run:
1804 while (nScriptIdx < m_ScriptChanges.size()
1805 && GetScriptChg(nScriptIdx) <= nEnd)
1807 m_ScriptChanges.erase(m_ScriptChanges.begin() + nScriptIdx);
1810 // Insert a new entry in ScriptArray for the end of the RTL run:
1811 m_ScriptChanges.insert(m_ScriptChanges.begin() + nScriptIdx,
1812 ScriptChangeInfo(nEnd, i18n::ScriptType::COMPLEX) );
1814 #if OSL_DEBUG_LEVEL > 1
1815 // Check that ScriptChangeInfos are in increasing order of
1816 // position and that we don't have "empty" changes.
1817 sal_uInt8 nLastTyp = i18n::ScriptType::WEAK;
1818 TextFrameIndex nLastPos = TextFrameIndex(0);
1819 for (const auto& rScriptChange : m_ScriptChanges)
1821 SAL_WARN_IF( nLastTyp == rScriptChange.type ||
1822 nLastPos >= rScriptChange.position,
1823 "sw.core", "Heavy InitScriptType() confusion" );
1824 nLastPos = rScriptChange.position;
1825 nLastTyp = rScriptChange.type;
1827 #endif
1833 void SwScriptInfo::UpdateBidiInfo( const OUString& rText )
1835 // remove invalid entries from direction information arrays
1836 m_DirectionChanges.clear();
1838 // Bidi functions from icu 2.0
1840 UErrorCode nError = U_ZERO_ERROR;
1841 UBiDi* pBidi = ubidi_openSized( rText.getLength(), 0, &nError );
1842 nError = U_ZERO_ERROR;
1844 ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(rText.getStr()), rText.getLength(),
1845 m_nDefaultDir, nullptr, &nError );
1846 nError = U_ZERO_ERROR;
1847 int nCount = ubidi_countRuns( pBidi, &nError );
1848 int32_t nStart = 0;
1849 int32_t nEnd;
1850 UBiDiLevel nCurrDir;
1851 for ( int nIdx = 0; nIdx < nCount; ++nIdx )
1853 ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir );
1854 m_DirectionChanges.emplace_back(TextFrameIndex(nEnd), nCurrDir);
1855 nStart = nEnd;
1858 ubidi_close( pBidi );
1861 // returns the position of the next character which belongs to another script
1862 // than the character of the actual (input) position.
1863 // If there's no script change until the end of the paragraph, it will return
1864 // COMPLETE_STRING.
1865 // Scripts are Asian (Chinese, Japanese, Korean),
1866 // Latin ( English etc.)
1867 // and Complex ( Hebrew, Arabian )
1868 TextFrameIndex SwScriptInfo::NextScriptChg(const TextFrameIndex nPos) const
1870 const size_t nEnd = CountScriptChg();
1871 for( size_t nX = 0; nX < nEnd; ++nX )
1873 if( nPos < GetScriptChg( nX ) )
1874 return GetScriptChg( nX );
1877 return TextFrameIndex(COMPLETE_STRING);
1880 // returns the script of the character at the input position
1881 sal_Int16 SwScriptInfo::ScriptType(const TextFrameIndex nPos) const
1883 const size_t nEnd = CountScriptChg();
1884 for( size_t nX = 0; nX < nEnd; ++nX )
1886 if( nPos < GetScriptChg( nX ) )
1887 return GetScriptType( nX );
1890 // the default is the application language script
1891 return SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() );
1894 TextFrameIndex SwScriptInfo::NextDirChg(const TextFrameIndex nPos,
1895 const sal_uInt8* pLevel ) const
1897 const sal_uInt8 nCurrDir = pLevel ? *pLevel : 62;
1898 const size_t nEnd = CountDirChg();
1899 for( size_t nX = 0; nX < nEnd; ++nX )
1901 if( nPos < GetDirChg( nX ) &&
1902 ( nX + 1 == nEnd || GetDirType( nX + 1 ) <= nCurrDir ) )
1903 return GetDirChg( nX );
1906 return TextFrameIndex(COMPLETE_STRING);
1909 sal_uInt8 SwScriptInfo::DirType(const TextFrameIndex nPos) const
1911 const size_t nEnd = CountDirChg();
1912 for( size_t nX = 0; nX < nEnd; ++nX )
1914 if( nPos < GetDirChg( nX ) )
1915 return GetDirType( nX );
1918 return 0;
1921 TextFrameIndex SwScriptInfo::NextHiddenChg(TextFrameIndex const nPos) const
1923 for (auto const& it : m_HiddenChg)
1925 if (nPos < it)
1927 return it;
1930 return TextFrameIndex(COMPLETE_STRING);
1933 TextFrameIndex SwScriptInfo::NextBookmark(TextFrameIndex const nPos) const
1935 for (auto const& it : m_Bookmarks)
1937 if (nPos < std::get<0>(it))
1939 return std::get<0>(it);
1942 return TextFrameIndex(COMPLETE_STRING);
1945 std::vector<std::tuple<SwScriptInfo::MarkKind, Color, OUString>>
1946 SwScriptInfo::GetBookmarks(TextFrameIndex const nPos)
1948 std::vector<std::tuple<SwScriptInfo::MarkKind, Color, OUString>> aColors;
1949 for (auto const& it : m_Bookmarks)
1951 if (nPos == std::get<0>(it))
1953 const OUString& sName = std::get<3>(it);
1954 // filter hidden bookmarks imported from OOXML
1955 // TODO import them as hidden bookmarks
1956 if ( !( sName.startsWith("_Toc") || sName.startsWith("_Ref") ) )
1957 aColors.push_back(std::tuple<MarkKind, Color,
1958 OUString>(std::get<1>(it), std::get<2>(it), std::get<3>(it)));
1960 else if (nPos < std::get<0>(it))
1962 break;
1966 // sort bookmark boundary marks at the same position
1967 // mark order: ] | [
1968 // color order: [c1 [c2 [c3 ... c3] c2] c1]
1969 sort(aColors.begin(), aColors.end(),
1970 [](std::tuple<MarkKind, Color, OUString> const a, std::tuple<MarkKind, Color, OUString> const b) {
1971 return (MarkKind::End == std::get<0>(a) && MarkKind::End != std::get<0>(b)) ||
1972 (MarkKind::Point == std::get<0>(a) && MarkKind::Start == std::get<0>(b)) ||
1973 // if both are end or start, order by color
1974 (MarkKind::End == std::get<0>(a) && MarkKind::End == std::get<0>(b) && std::get<1>(a) < std::get<1>(b)) ||
1975 (MarkKind::Start == std::get<0>(a) && MarkKind::Start == std::get<0>(b) && std::get<1>(b) < std::get<1>(a));});
1977 return aColors;
1980 // Takes a string and replaced the hidden ranges with cChar.
1981 sal_Int32 SwScriptInfo::MaskHiddenRanges( const SwTextNode& rNode, OUStringBuffer & rText,
1982 const sal_Int32 nStt, const sal_Int32 nEnd,
1983 const sal_Unicode cChar )
1985 assert(rNode.GetText().getLength() == rText.getLength());
1987 std::vector<sal_Int32> aList;
1988 sal_Int32 nHiddenStart;
1989 sal_Int32 nHiddenEnd;
1990 sal_Int32 nNumOfHiddenChars = 0;
1991 GetBoundsOfHiddenRange( rNode, 0, nHiddenStart, nHiddenEnd, &aList );
1992 auto rFirst( aList.crbegin() );
1993 auto rLast( aList.crend() );
1994 while ( rFirst != rLast )
1996 nHiddenEnd = *(rFirst++);
1997 nHiddenStart = *(rFirst++);
1999 if ( nHiddenEnd < nStt || nHiddenStart > nEnd )
2000 continue;
2002 while ( nHiddenStart < nHiddenEnd && nHiddenStart < nEnd )
2004 if (nHiddenStart >= nStt)
2006 rText[nHiddenStart] = cChar;
2007 ++nNumOfHiddenChars;
2009 ++nHiddenStart;
2013 return nNumOfHiddenChars;
2016 // Takes a SwTextNode and deletes the hidden ranges from the node.
2017 void SwScriptInfo::DeleteHiddenRanges( SwTextNode& rNode )
2019 std::vector<sal_Int32> aList;
2020 sal_Int32 nHiddenStart;
2021 sal_Int32 nHiddenEnd;
2022 GetBoundsOfHiddenRange( rNode, 0, nHiddenStart, nHiddenEnd, &aList );
2023 auto rFirst( aList.crbegin() );
2024 auto rLast( aList.crend() );
2025 while ( rFirst != rLast )
2027 nHiddenEnd = *(rFirst++);
2028 nHiddenStart = *(rFirst++);
2030 SwPaM aPam( rNode, nHiddenStart, rNode, nHiddenEnd );
2031 rNode.getIDocumentContentOperations().DeleteRange( aPam );
2035 bool SwScriptInfo::GetBoundsOfHiddenRange( const SwTextNode& rNode, sal_Int32 nPos,
2036 sal_Int32& rnStartPos, sal_Int32& rnEndPos,
2037 std::vector<sal_Int32>* pList )
2039 rnStartPos = COMPLETE_STRING;
2040 rnEndPos = 0;
2042 bool bNewContainsHiddenChars = false;
2044 // Optimization: First examine the flags at the text node:
2046 if ( !rNode.IsCalcHiddenCharFlags() )
2048 bool bWholePara = rNode.HasHiddenCharAttribute( true );
2049 bool bContainsHiddenChars = rNode.HasHiddenCharAttribute( false );
2050 if ( !bContainsHiddenChars )
2051 return false;
2053 if ( bWholePara )
2055 if ( pList )
2057 pList->push_back( 0 );
2058 pList->push_back(rNode.GetText().getLength());
2061 rnStartPos = 0;
2062 rnEndPos = rNode.GetText().getLength();
2063 return true;
2067 // sw_redlinehide: this won't work if it's merged
2068 #if 0
2069 const SwScriptInfo* pSI = SwScriptInfo::GetScriptInfo( rNode );
2070 if ( pSI )
2073 // Check first, if we have a valid SwScriptInfo object for this text node:
2075 bNewContainsHiddenChars = pSI->GetBoundsOfHiddenRange( nPos, rnStartPos, rnEndPos, pList );
2076 const bool bNewHiddenCharsHidePara =
2077 rnStartPos == 0 && rnEndPos >= rNode.GetText().getLength();
2078 rNode.SetHiddenCharAttribute( bNewHiddenCharsHidePara, bNewContainsHiddenChars );
2080 else
2081 #endif
2084 // No valid SwScriptInfo Object, we have to do it the hard way:
2086 Range aRange(0, (!rNode.GetText().isEmpty())
2087 ? rNode.GetText().getLength() - 1
2088 : 0);
2089 MultiSelection aHiddenMulti( aRange );
2090 SwScriptInfo::CalcHiddenRanges(rNode, aHiddenMulti, nullptr);
2091 for( sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i )
2093 const Range& rRange = aHiddenMulti.GetRange( i );
2094 const sal_Int32 nHiddenStart = rRange.Min();
2095 const sal_Int32 nHiddenEnd = rRange.Max() + 1;
2097 if ( nHiddenStart > nPos )
2098 break;
2099 if (nPos < nHiddenEnd)
2101 rnStartPos = nHiddenStart;
2102 rnEndPos = std::min<sal_Int32>(nHiddenEnd,
2103 rNode.GetText().getLength());
2104 break;
2108 if ( pList )
2110 for( sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i )
2112 const Range& rRange = aHiddenMulti.GetRange( i );
2113 pList->push_back( rRange.Min() );
2114 pList->push_back( rRange.Max() + 1 );
2118 bNewContainsHiddenChars = aHiddenMulti.GetRangeCount() > 0;
2121 return bNewContainsHiddenChars;
2124 bool SwScriptInfo::GetBoundsOfHiddenRange(TextFrameIndex nPos,
2125 TextFrameIndex & rnStartPos, TextFrameIndex & rnEndPos) const
2127 rnStartPos = TextFrameIndex(COMPLETE_STRING);
2128 rnEndPos = TextFrameIndex(0);
2130 const size_t nEnd = CountHiddenChg();
2131 for( size_t nX = 0; nX < nEnd; ++nX )
2133 const TextFrameIndex nHiddenStart = GetHiddenChg( nX++ );
2134 const TextFrameIndex nHiddenEnd = GetHiddenChg( nX );
2136 if ( nHiddenStart > nPos )
2137 break;
2138 if (nPos < nHiddenEnd)
2140 rnStartPos = nHiddenStart;
2141 rnEndPos = nHiddenEnd;
2142 break;
2146 return CountHiddenChg() > 0;
2149 bool SwScriptInfo::IsInHiddenRange( const SwTextNode& rNode, sal_Int32 nPos )
2151 sal_Int32 nStartPos;
2152 sal_Int32 nEndPos;
2153 SwScriptInfo::GetBoundsOfHiddenRange( rNode, nPos, nStartPos, nEndPos );
2154 return nStartPos != COMPLETE_STRING;
2157 #ifdef DBG_UTIL
2158 // returns the type of the compressed character
2159 SwScriptInfo::CompType SwScriptInfo::DbgCompType(const TextFrameIndex nPos) const
2161 const size_t nEnd = CountCompChg();
2162 for( size_t nX = 0; nX < nEnd; ++nX )
2164 const TextFrameIndex nChg = GetCompStart(nX);
2166 if ( nPos < nChg )
2167 return NONE;
2169 if( nPos < nChg + GetCompLen( nX ) )
2170 return GetCompType( nX );
2172 return NONE;
2174 #endif
2176 // returns, if there are compressible kanas or specials
2177 // between nStart and nEnd
2178 size_t SwScriptInfo::HasKana(TextFrameIndex const nStart, TextFrameIndex const nLen) const
2180 const size_t nCnt = CountCompChg();
2181 TextFrameIndex nEnd = nStart + nLen;
2183 for( size_t nX = 0; nX < nCnt; ++nX )
2185 TextFrameIndex nKanaStart = GetCompStart(nX);
2186 TextFrameIndex nKanaEnd = nKanaStart + GetCompLen(nX);
2188 if ( nKanaStart >= nEnd )
2189 return SAL_MAX_SIZE;
2191 if ( nStart < nKanaEnd )
2192 return nX;
2195 return SAL_MAX_SIZE;
2198 tools::Long SwScriptInfo::Compress(KernArray& rKernArray, TextFrameIndex nIdx, TextFrameIndex nLen,
2199 const sal_uInt16 nCompress, const sal_uInt16 nFontHeight,
2200 bool bCenter,
2201 Point* pPoint ) const
2203 SAL_WARN_IF( !nCompress, "sw.core", "Compression without compression?!" );
2204 SAL_WARN_IF( !nLen, "sw.core", "Compression without text?!" );
2205 const size_t nCompCount = CountCompChg();
2207 // In asian typography, there are full width and half width characters.
2208 // Full width punctuation characters can be compressed by 50%
2209 // to determine this, we compare the font width with 75% of its height
2210 const tools::Long nMinWidth = ( 3 * nFontHeight ) / 4;
2212 size_t nCompIdx = HasKana( nIdx, nLen );
2214 if ( SAL_MAX_SIZE == nCompIdx )
2215 return 0;
2217 TextFrameIndex nChg = GetCompStart( nCompIdx );
2218 TextFrameIndex nCompLen = GetCompLen( nCompIdx );
2219 sal_Int32 nI = 0;
2220 nLen += nIdx;
2222 if( nChg > nIdx )
2224 nI = sal_Int32(nChg - nIdx);
2225 nIdx = nChg;
2227 else if( nIdx < nChg + nCompLen )
2228 nCompLen -= nIdx - nChg;
2230 if( nIdx > nLen || nCompIdx >= nCompCount )
2231 return 0;
2233 tools::Long nSub = 0;
2234 tools::Long nLast = nI ? rKernArray[ nI - 1 ] : 0;
2237 const CompType nType = GetCompType( nCompIdx );
2238 #ifdef DBG_UTIL
2239 SAL_WARN_IF( nType != DbgCompType( nIdx ), "sw.core", "Gimme the right type!" );
2240 #endif
2241 nCompLen += nIdx;
2242 if( nCompLen > nLen )
2243 nCompLen = nLen;
2245 // are we allowed to compress the character?
2246 if ( rKernArray[ nI ] - nLast < nMinWidth )
2248 nIdx++; nI++;
2250 else
2252 while( nIdx < nCompLen )
2254 SAL_WARN_IF( SwScriptInfo::NONE == nType, "sw.core", "None compression?!" );
2256 // nLast is width of current character
2257 nLast -= rKernArray[ nI ];
2259 nLast *= nCompress;
2260 tools::Long nMove = 0;
2261 if( SwScriptInfo::KANA != nType )
2263 nLast /= 24000;
2264 if( pPoint && SwScriptInfo::SPECIAL_LEFT == nType )
2266 if( nI )
2267 nMove = nLast;
2268 else
2270 pPoint->AdjustX(nLast );
2271 nLast = 0;
2274 else if( bCenter && SwScriptInfo::SPECIAL_MIDDLE == nType )
2275 nMove = nLast / 2;
2277 else
2278 nLast /= 100000;
2279 nSub -= nLast;
2280 nLast = rKernArray[ nI ];
2281 if( nI && nMove )
2282 rKernArray.adjust(nI - 1, nMove);
2283 rKernArray.adjust(nI, -nSub);
2284 ++nI;
2285 ++nIdx;
2289 if( nIdx >= nLen )
2290 break;
2292 TextFrameIndex nTmpChg = nLen;
2293 if( ++nCompIdx < nCompCount )
2295 nTmpChg = GetCompStart( nCompIdx );
2296 if( nTmpChg > nLen )
2297 nTmpChg = nLen;
2298 nCompLen = GetCompLen( nCompIdx );
2301 while( nIdx < nTmpChg )
2303 nLast = rKernArray[ nI ];
2304 rKernArray.adjust(nI, -nSub);
2305 ++nI;
2306 ++nIdx;
2308 } while( nIdx < nLen );
2309 return nSub;
2312 // Note on calling KashidaJustify():
2313 // Kashida positions may be marked as invalid. Therefore KashidaJustify may return the clean
2314 // total number of kashida positions, or the number of kashida positions after some positions
2315 // have been dropped, depending on the state of the m_KashidaInvalid set.
2317 sal_Int32 SwScriptInfo::KashidaJustify( KernArray* pKernArray,
2318 sal_Bool* pKashidaArray,
2319 TextFrameIndex const nStt,
2320 TextFrameIndex const nLen,
2321 tools::Long nSpaceAdd ) const
2323 SAL_WARN_IF( !nLen, "sw.core", "Kashida justification without text?!" );
2325 if( !IsKashidaLine(nStt))
2326 return -1;
2328 // evaluate kashida information in collected in SwScriptInfo
2330 size_t nCntKash = 0;
2331 while( nCntKash < CountKashida() )
2333 if ( nStt <= GetKashida( nCntKash ) )
2334 break;
2335 ++nCntKash;
2338 const TextFrameIndex nEnd = nStt + nLen;
2340 size_t nCntKashEnd = nCntKash;
2341 while ( nCntKashEnd < CountKashida() )
2343 if ( nEnd <= GetKashida( nCntKashEnd ) )
2344 break;
2345 ++nCntKashEnd;
2348 size_t nActualKashCount = nCntKashEnd - nCntKash;
2349 for (size_t i = nCntKash; i < nCntKashEnd; ++i)
2351 if ( nActualKashCount && !IsKashidaValid ( i ) )
2352 --nActualKashCount;
2355 if ( !pKernArray )
2356 return nActualKashCount;
2358 // do nothing if there is no more kashida
2359 if ( nCntKash < CountKashida() )
2361 // skip any invalid kashidas
2362 while (nCntKash < nCntKashEnd && !IsKashidaValid(nCntKash))
2363 ++nCntKash;
2365 TextFrameIndex nIdx = nCntKash < nCntKashEnd && IsKashidaValid(nCntKash)
2366 ? GetKashida(nCntKash)
2367 : nEnd;
2368 tools::Long nKashAdd = nSpaceAdd;
2370 while ( nIdx < nEnd )
2372 TextFrameIndex nArrayPos = nIdx - nStt;
2374 // mark Kashida insertion positions, code in VCL will use this
2375 // array to know where to insert Kashida.
2376 if (pKashidaArray)
2377 pKashidaArray[sal_Int32(nArrayPos)] = true;
2379 // next kashida position
2380 ++nCntKash;
2381 while (nCntKash < nCntKashEnd && !IsKashidaValid(nCntKash))
2382 ++nCntKash;
2384 nIdx = nCntKash < nCntKashEnd && IsKashidaValid(nCntKash) ? GetKashida(nCntKash) : nEnd;
2385 if ( nIdx > nEnd )
2386 nIdx = nEnd;
2388 const TextFrameIndex nArrayEnd = nIdx - nStt;
2390 while ( nArrayPos < nArrayEnd )
2392 pKernArray->adjust(sal_Int32(nArrayPos), nKashAdd);
2393 ++nArrayPos;
2395 nKashAdd += nSpaceAdd;
2399 return 0;
2402 // Checks if the current text is 'Arabic' text. Note that only the first
2403 // character has to be checked because a ctl portion only contains one
2404 // script, see NewTextPortion
2405 bool SwScriptInfo::IsArabicText(const OUString& rText,
2406 TextFrameIndex const nStt, TextFrameIndex const nLen)
2408 using namespace ::com::sun::star::i18n;
2409 static const ScriptTypeList typeList[] = {
2410 { UnicodeScript_kArabic, UnicodeScript_kArabic, sal_Int16(UnicodeScript_kArabic) }, // 11,
2411 { UnicodeScript_kScriptCount, UnicodeScript_kScriptCount, sal_Int16(UnicodeScript_kScriptCount) } // 88
2414 // go forward if current position does not hold a regular character:
2415 const CharClass& rCC = GetAppCharClass();
2416 sal_Int32 nIdx = sal_Int32(nStt);
2417 const sal_Int32 nEnd = sal_Int32(nStt + nLen);
2418 while ( nIdx < nEnd && !rCC.isLetterNumeric( rText, nIdx ) )
2420 ++nIdx;
2423 if( nIdx == nEnd )
2425 // no regular character found in this portion. Go backward:
2426 --nIdx;
2427 while ( nIdx >= 0 && !rCC.isLetterNumeric( rText, nIdx ) )
2429 --nIdx;
2433 if( nIdx >= 0 )
2435 const sal_Unicode cCh = rText[nIdx];
2436 const sal_Int16 type = unicode::getUnicodeScriptType( cCh, typeList, sal_Int16(UnicodeScript_kScriptCount) );
2437 return type == sal_Int16(UnicodeScript_kArabic);
2439 return false;
2442 bool SwScriptInfo::IsKashidaValid(size_t const nKashPos) const
2444 return m_KashidaInvalid.find(nKashPos) == m_KashidaInvalid.end();
2447 void SwScriptInfo::ClearKashidaInvalid(size_t const nKashPos)
2449 m_KashidaInvalid.erase(nKashPos);
2452 // bMark == true:
2453 // marks the first valid kashida in the given text range as invalid
2454 // bMark == false:
2455 // clears all kashida invalid flags in the given text range
2456 bool SwScriptInfo::MarkOrClearKashidaInvalid(
2457 TextFrameIndex const nStt, TextFrameIndex const nLen,
2458 bool bMark, sal_Int32 nMarkCount)
2460 size_t nCntKash = 0;
2461 while( nCntKash < CountKashida() )
2463 if ( nStt <= GetKashida( nCntKash ) )
2464 break;
2465 nCntKash++;
2468 const TextFrameIndex nEnd = nStt + nLen;
2470 while ( nCntKash < CountKashida() )
2472 if ( nEnd <= GetKashida( nCntKash ) )
2473 break;
2474 if(bMark)
2476 if ( MarkKashidaInvalid ( nCntKash ) )
2478 --nMarkCount;
2479 if (!nMarkCount)
2480 return true;
2483 else
2485 ClearKashidaInvalid ( nCntKash );
2487 nCntKash++;
2489 return false;
2492 bool SwScriptInfo::MarkKashidaInvalid(size_t const nKashPos)
2494 return m_KashidaInvalid.insert(nKashPos).second;
2497 // retrieve the kashida positions in the given text range
2498 void SwScriptInfo::GetKashidaPositions(
2499 TextFrameIndex const nStt, TextFrameIndex const nLen,
2500 std::vector<TextFrameIndex>& rKashidaPosition)
2502 size_t nCntKash = 0;
2503 while( nCntKash < CountKashida() )
2505 if ( nStt <= GetKashida( nCntKash ) )
2506 break;
2507 nCntKash++;
2510 const TextFrameIndex nEnd = nStt + nLen;
2512 size_t nCntKashEnd = nCntKash;
2513 while ( nCntKashEnd < CountKashida() )
2515 if ( nEnd <= GetKashida( nCntKashEnd ) )
2516 break;
2517 rKashidaPosition.push_back(GetKashida(nCntKashEnd));
2518 nCntKashEnd++;
2522 void SwScriptInfo::SetNoKashidaLine(TextFrameIndex const nStt, TextFrameIndex const nLen)
2524 m_NoKashidaLine.push_back( nStt );
2525 m_NoKashidaLineEnd.push_back( nStt + nLen );
2528 // determines if the line uses kashida justification
2529 bool SwScriptInfo::IsKashidaLine(TextFrameIndex const nCharIdx) const
2531 for (size_t i = 0; i < m_NoKashidaLine.size(); ++i)
2533 if (nCharIdx >= m_NoKashidaLine[i] && nCharIdx < m_NoKashidaLineEnd[i])
2534 return false;
2536 return true;
2539 void SwScriptInfo::ClearNoKashidaLine(TextFrameIndex const nStt, TextFrameIndex const nLen)
2541 size_t i = 0;
2542 while (i < m_NoKashidaLine.size())
2544 if (nStt + nLen >= m_NoKashidaLine[i] && nStt < m_NoKashidaLineEnd[i])
2546 m_NoKashidaLine.erase(m_NoKashidaLine.begin() + i);
2547 m_NoKashidaLineEnd.erase(m_NoKashidaLineEnd.begin() + i);
2549 else
2550 ++i;
2554 // mark the given character indices as invalid kashida positions
2555 void SwScriptInfo::MarkKashidasInvalid(sal_Int32 const nCnt,
2556 const TextFrameIndex* pKashidaPositions)
2558 SAL_WARN_IF( !pKashidaPositions || nCnt == 0, "sw.core", "Where are kashidas?" );
2560 size_t nCntKash = 0;
2561 sal_Int32 nKashidaPosIdx = 0;
2563 while (nCntKash < CountKashida() && nKashidaPosIdx < nCnt)
2565 if ( pKashidaPositions [nKashidaPosIdx] > GetKashida( nCntKash ) )
2567 ++nCntKash;
2568 continue;
2571 if ( pKashidaPositions [nKashidaPosIdx] != GetKashida( nCntKash ) || !IsKashidaValid ( nCntKash ) )
2572 return; // something is wrong
2574 MarkKashidaInvalid ( nCntKash );
2575 nKashidaPosIdx++;
2579 TextFrameIndex SwScriptInfo::ThaiJustify( std::u16string_view aText, KernArray* pKernArray,
2580 TextFrameIndex const nStt,
2581 TextFrameIndex const nLen,
2582 TextFrameIndex nNumberOfBlanks,
2583 tools::Long nSpaceAdd )
2585 SAL_WARN_IF( nStt + nLen > TextFrameIndex(aText.size()), "sw.core", "String in ThaiJustify too small" );
2587 SwTwips nNumOfTwipsToDistribute = nSpaceAdd * sal_Int32(nNumberOfBlanks) /
2588 SPACING_PRECISION_FACTOR;
2590 tools::Long nSpaceSum = 0;
2591 TextFrameIndex nCnt(0);
2593 for (sal_Int32 nI = 0; nI < sal_Int32(nLen); ++nI)
2595 const sal_Unicode cCh = aText[sal_Int32(nStt) + nI];
2597 // check if character is not above or below base
2598 if ( ( 0xE34 > cCh || cCh > 0xE3A ) &&
2599 ( 0xE47 > cCh || cCh > 0xE4E ) && cCh != 0xE31 )
2601 if (nNumberOfBlanks > TextFrameIndex(0))
2603 nSpaceAdd = nNumOfTwipsToDistribute / sal_Int32(nNumberOfBlanks);
2604 --nNumberOfBlanks;
2605 nNumOfTwipsToDistribute -= nSpaceAdd;
2607 nSpaceSum += nSpaceAdd;
2608 ++nCnt;
2611 if (pKernArray)
2612 pKernArray->adjust(nI, nSpaceSum);
2615 return nCnt;
2618 SwScriptInfo* SwScriptInfo::GetScriptInfo( const SwTextNode& rTNd,
2619 SwTextFrame const**const o_ppFrame,
2620 bool const bAllowInvalid)
2622 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rTNd);
2623 SwScriptInfo* pScriptInfo = nullptr;
2625 for( SwTextFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() )
2627 pScriptInfo = const_cast<SwScriptInfo*>(pLast->GetScriptInfo());
2628 if ( pScriptInfo )
2630 if (bAllowInvalid ||
2631 TextFrameIndex(COMPLETE_STRING) == pScriptInfo->GetInvalidityA())
2633 if (o_ppFrame)
2635 *o_ppFrame = pLast;
2637 break;
2639 pScriptInfo = nullptr;
2643 return pScriptInfo;
2646 SwParaPortion::SwParaPortion()
2648 FormatReset();
2649 m_bFlys = m_bFootnoteNum = m_bMargin = false;
2650 SetWhichPor( PortionType::Para );
2653 SwParaPortion::~SwParaPortion()
2657 TextFrameIndex SwParaPortion::GetParLen() const
2659 TextFrameIndex nLen(0);
2660 const SwLineLayout *pLay = this;
2661 while( pLay )
2663 nLen += pLay->GetLen();
2664 pLay = pLay->GetNext();
2666 return nLen;
2669 bool SwParaPortion::HasNumberingPortion(FootnoteOrNot const eFootnote) const
2671 SwLinePortion const* pPortion(nullptr);
2672 // the first line may contain only fly portion...
2673 for (SwLineLayout const* pLine = this; pLine && !pPortion; pLine = pLine->GetNext())
2675 pPortion = pLine->GetFirstPortion();
2676 while (pPortion && (pPortion->InGlueGrp() || pPortion->IsKernPortion() || pPortion->IsFlyPortion()))
2677 { // skip margins and fly spacers - numbering should be first then
2678 pPortion = pPortion->GetNextPortion();
2681 if (pPortion && pPortion->InHyphGrp())
2682 { // weird special case, bullet with soft hyphen
2683 pPortion = pPortion->GetNextPortion();
2685 return pPortion && pPortion->InNumberGrp()
2686 && (eFootnote == SwParaPortion::FootnoteToo || !pPortion->IsFootnoteNumPortion());
2689 bool SwParaPortion::HasContentPortions() const
2691 SwLinePortion const* pPortion(nullptr);
2692 for (SwLineLayout const* pLine = this; pLine && !pPortion; pLine = pLine->GetNext())
2694 pPortion = pLine->GetFirstPortion();
2695 while (pPortion && (pPortion->InGlueGrp() || pPortion->IsKernPortion() || pPortion->IsFlyPortion()))
2696 { // skip margins and fly spacers
2697 pPortion = pPortion->GetNextPortion();
2700 return pPortion != nullptr;
2703 const SwDropPortion *SwParaPortion::FindDropPortion() const
2705 const SwLineLayout *pLay = this;
2706 while( pLay && pLay->IsDummy() )
2707 pLay = pLay->GetNext();
2708 while( pLay )
2710 const SwLinePortion *pPos = pLay->GetNextPortion();
2711 while ( pPos && !pPos->GetLen() )
2712 pPos = pPos->GetNextPortion();
2713 if( pPos && pPos->IsDropPortion() )
2714 return static_cast<const SwDropPortion *>(pPos);
2715 pLay = pLay->GetLen() ? nullptr : pLay->GetNext();
2717 return nullptr;
2720 void SwParaPortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
2721 TextFrameIndex& nOffset) const
2723 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwParaPortion"));
2724 dumpAsXmlAttributes(pWriter, rText, nOffset);
2725 nOffset += GetLen();
2727 (void)xmlTextWriterEndElement(pWriter);
2730 void SwLineLayout::Init( SwLinePortion* pNextPortion )
2732 Height( 0, false );
2733 Width( 0 );
2734 SetLen(TextFrameIndex(0));
2735 SetAscent( 0 );
2736 SetRealHeight( 0 );
2737 SetNextPortion( pNextPortion );
2740 // looks for hanging punctuation portions in the paragraph
2741 // and return the maximum right offset of them.
2742 // If no such portion is found, the Margin/Hanging-flags will be updated.
2743 SwTwips SwLineLayout::GetHangingMargin_() const
2745 SwLinePortion* pPor = GetNextPortion();
2746 bool bFound = false;
2747 SwTwips nDiff = 0;
2748 while( pPor)
2750 if( pPor->IsHangingPortion() )
2752 nDiff = static_cast<SwHangingPortion*>(pPor)->GetInnerWidth() - pPor->Width();
2753 if( nDiff )
2754 bFound = true;
2756 // the last post its portion
2757 else if ( pPor->IsPostItsPortion() && ! pPor->GetNextPortion() )
2758 nDiff = mnAscent;
2760 pPor = pPor->GetNextPortion();
2762 if( !bFound ) // update the hanging-flag
2763 const_cast<SwLineLayout*>(this)->SetHanging( false );
2764 return nDiff;
2767 SwTwips SwTextFrame::HangingMargin() const
2769 SAL_WARN_IF( !HasPara(), "sw.core", "Don't call me without a paraportion" );
2770 if( !GetPara()->IsMargin() )
2771 return 0;
2772 const SwLineLayout* pLine = GetPara();
2773 SwTwips nRet = 0;
2776 SwTwips nDiff = pLine->GetHangingMargin();
2777 if( nDiff > nRet )
2778 nRet = nDiff;
2779 pLine = pLine->GetNext();
2780 } while ( pLine );
2781 if( !nRet ) // update the margin-flag
2782 const_cast<SwParaPortion*>(GetPara())->SetMargin( false );
2783 return nRet;
2786 SwTwips SwTextFrame::GetLowerMarginForFlyIntersect() const
2788 const IDocumentSettingAccess& rIDSA = GetDoc().getIDocumentSettingAccess();
2789 if (!rIDSA.get(DocumentSettingId::TAB_OVER_MARGIN))
2791 // Word >= 2013 style or Writer style: lower margin is ignored when determining the text
2792 // frame height.
2793 return 0;
2796 const SwAttrSet* pAttrSet = GetTextNodeForParaProps()->GetpSwAttrSet();
2797 if (!pAttrSet)
2799 return 0;
2802 // If it has multiple lines, then probably it already has the needed fly portion.
2803 // Limit this to empty paragraphs for now.
2804 if ((GetPara() && GetPara()->GetNext()) || !GetText().isEmpty())
2806 return 0;
2809 return pAttrSet->GetULSpace().GetLower();
2812 void SwScriptInfo::selectHiddenTextProperty(const SwTextNode& rNode,
2813 MultiSelection & rHiddenMulti,
2814 std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> *const pBookmarks)
2816 assert((rNode.GetText().isEmpty() && rHiddenMulti.GetTotalRange().Len() == 1)
2817 || (rNode.GetText().getLength() == rHiddenMulti.GetTotalRange().Len()));
2819 const SvxCharHiddenItem* pItem = rNode.GetSwAttrSet().GetItemIfSet( RES_CHRATR_HIDDEN );
2820 if( pItem && pItem->GetValue() )
2822 rHiddenMulti.SelectAll();
2825 const SwpHints* pHints = rNode.GetpSwpHints();
2827 if( pHints )
2829 for( size_t nTmp = 0; nTmp < pHints->Count(); ++nTmp )
2831 const SwTextAttr* pTextAttr = pHints->Get( nTmp );
2832 const SvxCharHiddenItem* pHiddenItem = CharFormat::GetItem( *pTextAttr, RES_CHRATR_HIDDEN );
2833 if( pHiddenItem )
2835 const sal_Int32 nSt = pTextAttr->GetStart();
2836 const sal_Int32 nEnd = *pTextAttr->End();
2837 if( nEnd > nSt )
2839 Range aTmp( nSt, nEnd - 1 );
2840 rHiddenMulti.Select( aTmp, pHiddenItem->GetValue() );
2846 for (const SwContentIndex* pIndex = rNode.GetFirstIndex(); pIndex; pIndex = pIndex->GetNext())
2848 const sw::mark::IMark* pMark = pIndex->GetMark();
2849 const sw::mark::IBookmark* pBookmark = dynamic_cast<const sw::mark::IBookmark*>(pMark);
2850 if (pBookmarks && pBookmark)
2852 if (!pBookmark->IsExpanded())
2854 pBookmarks->emplace_back(pBookmark, MarkKind::Point);
2856 else if (pIndex == &pBookmark->GetMarkStart().nContent)
2858 pBookmarks->emplace_back(pBookmark, MarkKind::Start);
2860 else
2862 assert(pIndex == &pBookmark->GetMarkEnd().nContent);
2863 pBookmarks->emplace_back(pBookmark, MarkKind::End);
2867 // condition is evaluated in DocumentFieldsManager::UpdateExpFields()
2868 if (pBookmark && pBookmark->IsHidden())
2870 // intersect bookmark range with textnode range and add the intersection to rHiddenMulti
2872 const sal_Int32 nSt = pBookmark->GetMarkStart().GetContentIndex();
2873 const sal_Int32 nEnd = pBookmark->GetMarkEnd().GetContentIndex();
2875 if( nEnd > nSt )
2877 Range aTmp( nSt, nEnd - 1 );
2878 rHiddenMulti.Select(aTmp, true);
2884 void SwScriptInfo::selectRedLineDeleted(const SwTextNode& rNode, MultiSelection &rHiddenMulti, bool bSelect)
2886 assert((rNode.GetText().isEmpty() && rHiddenMulti.GetTotalRange().Len() == 1)
2887 || (rNode.GetText().getLength() == rHiddenMulti.GetTotalRange().Len()));
2889 const IDocumentRedlineAccess& rIDRA = rNode.getIDocumentRedlineAccess();
2890 if ( !IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) )
2891 return;
2893 SwRedlineTable::size_type nAct = rIDRA.GetRedlinePos( rNode, RedlineType::Any );
2895 for ( ; nAct < rIDRA.GetRedlineTable().size(); nAct++ )
2897 const SwRangeRedline* pRed = rIDRA.GetRedlineTable()[ nAct ];
2899 if (pRed->Start()->GetNode() > rNode)
2900 break;
2902 if (pRed->GetType() != RedlineType::Delete)
2903 continue;
2905 sal_Int32 nRedlStart;
2906 sal_Int32 nRedlnEnd;
2907 pRed->CalcStartEnd( rNode.GetIndex(), nRedlStart, nRedlnEnd );
2908 //clip it if the redline extends past the end of the nodes text
2909 nRedlnEnd = std::min<sal_Int32>(nRedlnEnd, rNode.GetText().getLength());
2910 if ( nRedlnEnd > nRedlStart )
2912 Range aTmp( nRedlStart, nRedlnEnd - 1 );
2913 rHiddenMulti.Select( aTmp, bSelect );
2918 // Returns a MultiSection indicating the hidden ranges.
2919 void SwScriptInfo::CalcHiddenRanges( const SwTextNode& rNode,
2920 MultiSelection & rHiddenMulti,
2921 std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> *const pBookmarks)
2923 selectHiddenTextProperty(rNode, rHiddenMulti, pBookmarks);
2925 // If there are any hidden ranges in the current text node, we have
2926 // to unhide the redlining ranges:
2927 selectRedLineDeleted(rNode, rHiddenMulti, false);
2929 // We calculated a lot of stuff. Finally we can update the flags at the text node.
2931 const bool bNewContainsHiddenChars = rHiddenMulti.GetRangeCount() > 0;
2932 bool bNewHiddenCharsHidePara = false;
2933 if ( bNewContainsHiddenChars )
2935 const Range& rRange = rHiddenMulti.GetRange( 0 );
2936 const sal_Int32 nHiddenStart = rRange.Min();
2937 const sal_Int32 nHiddenEnd = rRange.Max() + 1;
2938 bNewHiddenCharsHidePara =
2939 (nHiddenStart == 0 && nHiddenEnd >= rNode.GetText().getLength());
2941 rNode.SetHiddenCharAttribute( bNewHiddenCharsHidePara, bNewContainsHiddenChars );
2944 TextFrameIndex SwScriptInfo::CountCJKCharacters(const OUString &rText,
2945 TextFrameIndex nPos, TextFrameIndex const nEnd, LanguageType aLang)
2947 TextFrameIndex nCount(0);
2948 if (nEnd > nPos)
2950 sal_Int32 nDone = 0;
2951 const lang::Locale &rLocale = g_pBreakIt->GetLocale( aLang );
2952 while ( nPos < nEnd )
2954 nPos = TextFrameIndex(g_pBreakIt->GetBreakIter()->nextCharacters(
2955 rText, sal_Int32(nPos),
2956 rLocale,
2957 i18n::CharacterIteratorMode::SKIPCELL, 1, nDone));
2958 nCount++;
2961 else
2962 nCount = nEnd - nPos ;
2964 return nCount;
2967 void SwScriptInfo::CJKJustify( const OUString& rText, KernArray& rKernArray,
2968 TextFrameIndex const nStt,
2969 TextFrameIndex const nLen, LanguageType aLang,
2970 tools::Long nSpaceAdd, bool bIsSpaceStop )
2972 assert( sal_Int32(nStt) >= 0 );
2973 if (sal_Int32(nLen) <= 0)
2974 return;
2976 tools::Long nSpaceSum = 0;
2977 const lang::Locale &rLocale = g_pBreakIt->GetLocale( aLang );
2978 sal_Int32 nDone = 0;
2979 sal_Int32 nNext(nStt);
2980 for ( sal_Int32 nI = 0; nI < sal_Int32(nLen); ++nI )
2982 if (nI + sal_Int32(nStt) == nNext)
2984 nNext = g_pBreakIt->GetBreakIter()->nextCharacters( rText, nNext,
2985 rLocale,
2986 i18n::CharacterIteratorMode::SKIPCELL, 1, nDone );
2987 if (nNext < sal_Int32(nStt + nLen) || !bIsSpaceStop)
2988 nSpaceSum += nSpaceAdd;
2990 rKernArray.adjust(nI, nSpaceSum);
2993 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */