sc: factor out some more code
[LibreOffice.git] / sw / source / core / text / itradj.cxx
blob05f62d9ebefadccb25f186a385832944dc7a5aea
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 <o3tl/safeint.hxx>
23 #include <com/sun/star/i18n/WordType.hpp>
24 #include <swscanner.hxx>
25 #include <i18nutil/kashida.hxx>
27 #include <IDocumentSettingAccess.hxx>
28 #include <doc.hxx>
30 #include "itrtxt.hxx"
31 #include "porglue.hxx"
32 #include "porlay.hxx"
33 #include "porfly.hxx"
34 #include "pormulti.hxx"
35 #include "portab.hxx"
36 #include <memory>
38 #define MIN_TAB_WIDTH 60
40 using namespace ::com::sun::star;
42 void SwTextAdjuster::FormatBlock( )
44 // Block format does not apply to the last line.
45 // And for tabs it doesn't exist out of tradition
46 // If we have Flys we continue.
48 const SwLinePortion *pFly = nullptr;
50 bool bSkip = !IsLastBlock() &&
51 // don't skip, if the last paragraph line needs space shrinking
52 m_pCurr->ExtraShrunkWidth() <= m_pCurr->Width() &&
53 m_nStart + m_pCurr->GetLen() >= TextFrameIndex(GetInfo().GetText().getLength());
55 // tdf#162725 if the last line is longer, than the paragraph width,
56 // it contains shrinking spaces: don't skip block format here
57 if( bSkip )
59 // sum width of the text portions to calculate the line width without shrinking
60 tools::Long nBreakWidth = 0;
61 const SwLinePortion *pPos = m_pCurr->GetNextPortion();
62 while( pPos && bSkip )
64 if( // don't calculate with the terminating space,
65 // otherwise it would result justified line mistakenly
66 pPos->GetNextPortion() || !pPos->IsHolePortion() )
68 nBreakWidth += pPos->Width();
71 if( nBreakWidth > m_pCurr->Width() )
72 bSkip = false;
74 pPos = pPos->GetNextPortion();
78 // Multi-line fields are tricky, because we need to check whether there are
79 // any other text portions in the paragraph.
80 if( bSkip )
82 const SwLineLayout *pLay = m_pCurr->GetNext();
83 while( pLay && !pLay->GetLen() )
85 const SwLinePortion *pPor = m_pCurr->GetFirstPortion();
86 while( pPor && bSkip )
88 if( pPor->InTextGrp() )
89 bSkip = false;
90 pPor = pPor->GetNextPortion();
92 pLay = bSkip ? pLay->GetNext() : nullptr;
96 if( bSkip )
98 if( !GetInfo().GetParaPortion()->HasFly() )
100 if( IsLastCenter() )
101 CalcFlyAdjust( m_pCurr );
102 m_pCurr->FinishSpaceAdd();
103 return;
105 else
107 const SwLinePortion *pTmpFly = nullptr;
109 // End at the last Fly
110 const SwLinePortion *pPos = m_pCurr->GetFirstPortion();
111 while( pPos )
113 // Look for the last Fly which has text coming after it:
114 if( pPos->IsFlyPortion() )
115 pTmpFly = pPos; // Found a Fly
116 else if ( pTmpFly && pPos->InTextGrp() )
118 pFly = pTmpFly; // A Fly with follow-up text!
119 pTmpFly = nullptr;
121 pPos = pPos->GetNextPortion();
123 // End if we didn't find one
124 if( !pFly )
126 if( IsLastCenter() )
127 CalcFlyAdjust( m_pCurr );
128 m_pCurr->FinishSpaceAdd();
129 return;
134 const TextFrameIndex nOldIdx = GetInfo().GetIdx();
135 GetInfo().SetIdx( m_nStart );
136 CalcNewBlock( m_pCurr, pFly );
137 GetInfo().SetIdx( nOldIdx );
138 GetInfo().GetParaPortion()->GetRepaint().SetOffset(0);
141 static bool lcl_CheckKashidaPositions(SwScriptInfo& rSI, SwTextSizeInfo& rInf, SwTextIter& rItr,
142 sal_Int32& rKashidas, TextFrameIndex& nGluePortion,
143 bool& rRemovedAllKashida)
145 rRemovedAllKashida = true;
147 // i60594 validate Kashida justification
148 TextFrameIndex nIdx = rItr.GetStart();
149 TextFrameIndex nEnd = rItr.GetEnd();
151 // Get the initial kashida position set, for invalidation
152 std::vector<TextFrameIndex> aOldKashidaPositions;
153 rSI.GetKashidaPositions(nIdx, rItr.GetLength(), aOldKashidaPositions);
155 std::vector<TextFrameIndex> aNewKashidaPositions;
156 std::vector<bool> aValidPositions;
158 // Reparse the text, and reapply the kashida insertion rules
159 std::function<LanguageType(sal_Int32, sal_Int32, bool)> const pGetLangOfChar(
160 [&rInf](sal_Int32 const nBegin, sal_uInt16 const nScript, bool const bNoChar)
161 { return rInf.GetTextFrame()->GetLangOfChar(TextFrameIndex{ nBegin }, nScript, bNoChar); });
162 SwScanner aScanner(pGetLangOfChar, rInf.GetText(), nullptr, ModelToViewHelper(),
163 i18n::WordType::DICTIONARY_WORD, sal_Int32(nIdx), sal_Int32(nEnd));
165 while (aScanner.NextWord())
167 const OUString& rWord = aScanner.GetWord();
169 // Fetch the set of valid positions from VCL, where possible
170 if (SwScriptInfo::IsKashidaScriptText(rInf.GetText(), TextFrameIndex{ aScanner.GetBegin() },
171 TextFrameIndex{ aScanner.GetLen() }))
173 aValidPositions.clear();
175 rItr.SeekAndChgAttrIter(TextFrameIndex{ aScanner.GetBegin() }, rInf.GetRefDev());
177 vcl::text::ComplexTextLayoutFlags nOldLayout = rInf.GetRefDev()->GetLayoutMode();
178 rInf.GetRefDev()->SetLayoutMode(nOldLayout | vcl::text::ComplexTextLayoutFlags::BiDiRtl);
180 rInf.GetRefDev()->GetWordKashidaPositions(rWord, &aValidPositions);
182 rInf.GetRefDev()->SetLayoutMode(nOldLayout);
184 auto stKashidaPos = i18nutil::GetWordKashidaPosition(rWord, aValidPositions);
185 if (stKashidaPos.has_value())
187 TextFrameIndex nNewKashidaPos{ aScanner.GetBegin() + stKashidaPos->nIndex };
189 // tdf#164098: The above algorithm can return out-of-range kashida positions. This
190 // can happen if, for example, a single word is split across multiple lines, and
191 // the best kashida candidate position is on the first line.
192 if (nNewKashidaPos >= nIdx && nNewKashidaPos < nEnd)
194 aNewKashidaPositions.push_back(nNewKashidaPos);
200 if (aOldKashidaPositions != aNewKashidaPositions)
202 // Kashida positions have changed; restart CalcNewBlock
203 rSI.ReplaceKashidaPositions(nIdx, nEnd, aNewKashidaPositions);
204 rRemovedAllKashida = aNewKashidaPositions.empty();
205 return false;
208 // Note on calling KashidaJustify():
209 // Kashida positions may be marked as invalid. Therefore KashidaJustify may return the clean
210 // total number of kashida positions, or the number of kashida positions after some positions
211 // have been dropped.
212 // Here we want the clean total, which is OK: We have called ClearKashidaInvalid() before.
213 rKashidas = rSI.KashidaJustify(nullptr, nullptr, rItr.GetStart(), rItr.GetLength());
215 if (rKashidas <= 0) // nothing to do
216 return true;
218 // kashida positions found in SwScriptInfo are not necessarily valid in every font
219 // if two characters are replaced by a ligature glyph, there will be no place for a kashida
220 assert(aNewKashidaPositions.size() >= o3tl::make_unsigned(rKashidas));
222 std::vector<sal_Int32> aKashidaPos;
223 std::transform(std::cbegin(aNewKashidaPositions), std::cend(aNewKashidaPositions),
224 std::back_inserter(aKashidaPos),
225 [](TextFrameIndex nPos) { return static_cast<sal_Int32>(nPos); });
227 std::vector<sal_Int32> aKashidaPosDropped;
228 std::vector<TextFrameIndex> aCastKashidaPosDropped;
230 sal_Int32 nKashidaIdx = 0;
231 while ( rKashidas && nIdx < nEnd )
233 rItr.SeekAndChgAttrIter(nIdx, rInf.GetRefDev());
234 TextFrameIndex nNext = rItr.GetNextAttr();
236 // is there also a script change before?
237 // if there is, nNext should point to the script change
238 TextFrameIndex const nNextScript = rSI.NextScriptChg( nIdx );
239 if( nNextScript < nNext )
240 nNext = nNextScript;
242 if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd)
243 nNext = nEnd;
245 // Use an expanded context to validate kashida insertions between spans
246 TextFrameIndex nWholeNext = nNextScript;
247 if (nWholeNext == TextFrameIndex(COMPLETE_STRING) || nWholeNext > nEnd)
249 nWholeNext = nEnd;
252 sal_Int32 nKashidasInAttr = rSI.KashidaJustify(nullptr, nullptr, nIdx, nNext - nIdx);
253 if (nKashidasInAttr > 0)
255 // Kashida glyph looks suspicious, skip Kashida justification
256 if (rInf.GetRefDev()->GetMinKashida() <= 0)
258 return false;
261 sal_Int32 nKashidasDropped = 0;
262 if (!SwScriptInfo::IsKashidaScriptText(rInf.GetText(), nIdx, nNext - nIdx))
264 nKashidasDropped = nKashidasInAttr;
265 rKashidas -= nKashidasDropped;
267 else
269 vcl::text::ComplexTextLayoutFlags nOldLayout = rInf.GetRefDev()->GetLayoutMode();
270 rInf.GetRefDev()->SetLayoutMode(nOldLayout
271 | vcl::text::ComplexTextLayoutFlags::BiDiRtl);
272 nKashidasDropped = rInf.GetRefDev()->ValidateKashidas(
273 rInf.GetText(), /*nIdx=*/sal_Int32{ nIdx },
274 /*nLen=*/sal_Int32{ nWholeNext - nIdx },
275 /*nPartIdx=*/sal_Int32{ nIdx }, /*nPartLen=*/sal_Int32{ nNext - nIdx },
276 std::span(aKashidaPos).subspan(nKashidaIdx, nKashidasInAttr),
277 &aKashidaPosDropped);
278 rInf.GetRefDev()->SetLayoutMode(nOldLayout);
279 if ( nKashidasDropped )
281 aCastKashidaPosDropped.clear();
282 std::transform(std::cbegin(aKashidaPosDropped), std::cend(aKashidaPosDropped),
283 std::back_inserter(aCastKashidaPosDropped),
284 [](sal_Int32 nPos) { return TextFrameIndex{ nPos }; });
285 rSI.MarkKashidasInvalid(nKashidasDropped, aCastKashidaPosDropped.data());
286 rKashidas -= nKashidasDropped;
287 nGluePortion -= TextFrameIndex(nKashidasDropped);
290 nKashidaIdx += nKashidasInAttr;
292 nIdx = nNext;
295 // return false if all kashidas have been eliminated
296 return (rKashidas > 0);
299 static bool lcl_CheckKashidaWidth ( SwScriptInfo& rSI, SwTextSizeInfo& rInf, SwTextIter& rItr, sal_Int32& rKashidas,
300 TextFrameIndex& nGluePortion, const tools::Long nGluePortionWidth, tools::Long& nSpaceAdd )
302 // check kashida width
303 // if width is smaller than minimal kashida width allowed by fonts in the current line
304 // drop one kashida after the other until kashida width is OK
305 while (rKashidas)
307 bool bAddSpaceChanged = false;
308 TextFrameIndex nIdx = rItr.GetStart();
309 TextFrameIndex nEnd = rItr.GetEnd();
310 while ( nIdx < nEnd )
312 rItr.SeekAndChgAttrIter(nIdx, rInf.GetRefDev());
313 TextFrameIndex nNext = rItr.GetNextAttr();
315 // is there also a script change before?
316 // if there is, nNext should point to the script change
317 TextFrameIndex const nNextScript = rSI.NextScriptChg( nIdx );
318 if( nNextScript < nNext )
319 nNext = nNextScript;
321 if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd)
322 nNext = nEnd;
323 sal_Int32 nKashidasInAttr = rSI.KashidaJustify(nullptr, nullptr, nIdx, nNext - nIdx);
325 tools::Long nFontMinKashida = rInf.GetRefDev()->GetMinKashida();
326 if (nFontMinKashida && nKashidasInAttr > 0
327 && SwScriptInfo::IsKashidaScriptText(rInf.GetText(), nIdx, nNext - nIdx))
329 sal_Int32 nKashidasDropped = 0;
330 while ( rKashidas && nGluePortion && nKashidasInAttr > 0 &&
331 nSpaceAdd / SPACING_PRECISION_FACTOR < nFontMinKashida )
333 --nGluePortion;
334 --rKashidas;
335 --nKashidasInAttr;
336 ++nKashidasDropped;
337 if( !rKashidas || !nGluePortion ) // nothing left, return false to
338 return false; // do regular blank justification
340 nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion);
341 bAddSpaceChanged = true;
343 if( nKashidasDropped )
344 rSI.MarkKashidasInvalid( nKashidasDropped, nIdx, nNext - nIdx );
346 if ( bAddSpaceChanged )
347 break; // start all over again
348 nIdx = nNext;
350 if ( !bAddSpaceChanged )
351 break; // everything was OK
353 return true;
356 // CalcNewBlock() must only be called _after_ CalcLine()!
357 // We always span between two RandPortions or FixPortions (Tabs and Flys).
358 // We count the Glues and call ExpandBlock.
359 void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent,
360 const SwLinePortion *pStopAt, SwTwips nReal, bool bSkipKashida )
362 OSL_ENSURE( GetInfo().IsMulti() || SvxAdjust::Block == GetAdjust(),
363 "CalcNewBlock: Why?" );
364 OSL_ENSURE( pCurrent->Height(), "SwTextAdjuster::CalcBlockAdjust: missing CalcLine()" );
366 pCurrent->InitSpaceAdd();
367 TextFrameIndex nGluePortion(0);
368 TextFrameIndex nCharCnt(0);
369 sal_uInt16 nSpaceIdx = 0;
371 // i60591: hennerdrews
372 SwScriptInfo& rSI = GetInfo().GetParaPortion()->GetScriptInfo();
373 SwTextSizeInfo aInf ( GetTextFrame() );
374 SwTextIter aItr ( GetTextFrame(), &aInf );
376 if ( rSI.CountKashida() )
378 while (aItr.GetCurr() != pCurrent && aItr.GetNext())
379 aItr.Next();
381 if( bSkipKashida )
383 rSI.SetNoKashidaLine ( aItr.GetStart(), aItr.GetLength());
385 else
387 rSI.ClearKashidaInvalid ( aItr.GetStart(), aItr.GetLength() );
388 rSI.ClearNoKashidaLine( aItr.GetStart(), aItr.GetLength() );
392 // Do not forget: CalcRightMargin() sets pCurrent->Width() to the line width!
393 if (!bSkipKashida)
394 CalcRightMargin( pCurrent, nReal );
396 // #i49277#
397 const bool bDoNotJustifyLinesWithManualBreak =
398 GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::DO_NOT_JUSTIFY_LINES_WITH_MANUAL_BREAK);
399 bool bDoNotJustifyTab = false;
401 SwLinePortion *pPos = pCurrent->GetNextPortion();
402 // calculate real text width for shrinking
403 tools::Long nBreakWidth = 0;
405 while( pPos )
407 nBreakWidth += pPos->Width();
409 if ( ( bDoNotJustifyLinesWithManualBreak || bDoNotJustifyTab ) &&
410 pPos->IsBreakPortion() && !IsLastBlock() )
412 pCurrent->FinishSpaceAdd();
413 break;
416 switch ( pPos->GetWhichPor() )
418 case PortionType::TabCenter :
419 case PortionType::TabRight :
420 case PortionType::TabDecimal :
421 bDoNotJustifyTab = true;
422 break;
423 case PortionType::TabLeft :
424 case PortionType::Break:
425 bDoNotJustifyTab = false;
426 break;
427 default: break;
430 if ( pPos->InTextGrp() )
431 nGluePortion = nGluePortion + static_cast<SwTextPortion*>(pPos)->GetSpaceCnt( GetInfo(), nCharCnt );
432 else if( pPos->IsMultiPortion() )
434 SwMultiPortion* pMulti = static_cast<SwMultiPortion*>(pPos);
435 // a multiportion with a tabulator inside breaks the text adjustment
436 // a ruby portion will not be stretched by text adjustment
437 // a double line portion takes additional space for each blank
438 // in the wider line
439 if( pMulti->HasTabulator() )
441 if ( nSpaceIdx == pCurrent->GetLLSpaceAddCount() )
442 pCurrent->SetLLSpaceAdd( 0, nSpaceIdx );
444 nSpaceIdx++;
445 nGluePortion = TextFrameIndex(0);
446 nCharCnt = TextFrameIndex(0);
448 else if( pMulti->IsDouble() )
449 nGluePortion = nGluePortion + static_cast<SwDoubleLinePortion*>(pMulti)->GetSpaceCnt();
450 else if ( pMulti->IsBidi() )
451 nGluePortion = nGluePortion + static_cast<SwBidiPortion*>(pMulti)->GetSpaceCnt( GetInfo() ); // i60594
454 if( pPos->InGlueGrp() )
456 if( pPos->InFixMargGrp() )
458 if ( nSpaceIdx == pCurrent->GetLLSpaceAddCount() )
459 pCurrent->SetLLSpaceAdd( 0, nSpaceIdx );
461 const tools::Long nGluePortionWidth = static_cast<SwGluePortion*>(pPos)->GetPrtGlue() *
462 SPACING_PRECISION_FACTOR;
464 sal_Int32 nKashidas = 0;
465 if( nGluePortion && rSI.CountKashida() && !bSkipKashida )
467 // kashida positions found in SwScriptInfo are not necessarily valid in every font
468 // if two characters are replaced by a ligature glyph, there will be no place for a kashida
469 bool bRemovedAllKashida = false;
470 if (!lcl_CheckKashidaPositions(rSI, aInf, aItr, nKashidas, nGluePortion,
471 bRemovedAllKashida))
473 // all kashida positions are invalid
474 // do regular blank justification
475 pCurrent->FinishSpaceAdd();
476 GetInfo().SetIdx( m_nStart );
477 CalcNewBlock(pCurrent, pStopAt, nReal, bRemovedAllKashida);
478 return;
482 if( nGluePortion )
484 tools::Long nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion);
485 // shrink, if not shrunk line width exceed the set line width
486 // i.e. if pCurrent->ExtraShrunkWidth() > 0
487 // tdf#163720 but at hyphenated lines, still nBreakWidth contains the correct
488 // not shrunk line width (ExtraShrunkWidth + hyphen length), so use that
489 if ( pCurrent->ExtraShrunkWidth() > nBreakWidth )
490 nBreakWidth = pCurrent->ExtraShrunkWidth();
491 // shrink, if portions exceed the line width
492 tools::Long nSpaceSub = ( nBreakWidth > pCurrent->Width() )
493 ? (nBreakWidth - pCurrent->Width()) * SPACING_PRECISION_FACTOR /
494 sal_Int32(nGluePortion) + LONG_MAX/2
495 : ( nSpaceAdd < 0 )
496 // shrink, if portions exceed the line width available before an image
497 ? -nSpaceAdd + LONG_MAX/2
498 : 0;
500 // i60594
501 if( rSI.CountKashida() && !bSkipKashida )
503 if( !lcl_CheckKashidaWidth( rSI, aInf, aItr, nKashidas, nGluePortion, nGluePortionWidth, nSpaceAdd ))
505 // no kashidas left
506 // do regular blank justification
507 pCurrent->FinishSpaceAdd();
508 GetInfo().SetIdx( m_nStart );
509 CalcNewBlock( pCurrent, pStopAt, nReal, true );
510 return;
514 pCurrent->SetLLSpaceAdd( nSpaceSub ? nSpaceSub : nSpaceAdd, nSpaceIdx );
515 pPos->Width( static_cast<SwGluePortion*>(pPos)->GetFixWidth() );
517 else if (IsOneBlock() && nCharCnt > TextFrameIndex(1))
519 const tools::Long nSpaceAdd = - nGluePortionWidth / (sal_Int32(nCharCnt) - 1);
520 pCurrent->SetLLSpaceAdd( nSpaceAdd, nSpaceIdx );
521 pPos->Width( static_cast<SwGluePortion*>(pPos)->GetFixWidth() );
524 nSpaceIdx++;
525 nGluePortion = TextFrameIndex(0);
526 nCharCnt = TextFrameIndex(0);
528 else
529 ++nGluePortion;
531 GetInfo().SetIdx( GetInfo().GetIdx() + pPos->GetLen() );
532 if ( pPos == pStopAt )
534 pCurrent->SetLLSpaceAdd( 0, nSpaceIdx );
535 break;
537 pPos = pPos->GetNextPortion();
541 SwTwips SwTextAdjuster::CalcKanaAdj( SwLineLayout* pCurrent )
543 OSL_ENSURE( pCurrent->Height(), "SwTextAdjuster::CalcBlockAdjust: missing CalcLine()" );
544 OSL_ENSURE( !pCurrent->GetpKanaComp(), "pKanaComp already exists!!" );
546 pCurrent->SetKanaComp( std::make_unique<std::deque<sal_uInt16>>() );
548 const sal_uInt16 nNull = 0;
549 size_t nKanaIdx = 0;
550 tools::Long nKanaDiffSum = 0;
551 SwTwips nRepaintOfst = 0;
552 SwTwips nX = 0;
553 bool bNoCompression = false;
555 // Do not forget: CalcRightMargin() sets pCurrent->Width() to the line width!
556 CalcRightMargin( pCurrent );
558 SwLinePortion* pPos = pCurrent->GetNextPortion();
560 while( pPos )
562 if ( pPos->InTextGrp() )
564 // get maximum portion width from info structure, calculated
565 // during text formatting
566 SwTwips nMaxWidthDiff = GetInfo().GetMaxWidthDiff(pPos);
568 // check, if information is stored under other key
569 if ( !nMaxWidthDiff && pPos == pCurrent->GetFirstPortion() )
570 nMaxWidthDiff = GetInfo().GetMaxWidthDiff( pCurrent );
572 // calculate difference between portion width and max. width
573 nKanaDiffSum += nMaxWidthDiff;
575 // we store the beginning of the first compressible portion
576 // for repaint
577 if ( nMaxWidthDiff && !nRepaintOfst )
578 nRepaintOfst = nX + GetLeftMargin();
580 else if( pPos->InGlueGrp() && pPos->InFixMargGrp() )
582 if ( nKanaIdx == pCurrent->GetKanaComp().size() )
583 pCurrent->GetKanaComp().push_back( nNull );
585 tools::Long nRest;
587 if ( pPos->InTabGrp() )
589 nRest = ! bNoCompression &&
590 ( pPos->Width() > MIN_TAB_WIDTH ) ?
591 pPos->Width() - MIN_TAB_WIDTH :
594 // for simplifying the handling of left, right ... tabs,
595 // we do expand portions, which are lying behind
596 // those special tabs
597 bNoCompression = !pPos->IsTabLeftPortion();
599 else
601 nRest = ! bNoCompression ?
602 static_cast<SwGluePortion*>(pPos)->GetPrtGlue() :
605 bNoCompression = false;
608 if( nKanaDiffSum )
610 sal_uLong nCompress = ( 10000 * nRest ) / nKanaDiffSum;
612 if ( nCompress >= 10000 )
613 // kanas can be expanded to 100%, and there is still
614 // some space remaining
615 nCompress = 0;
617 else
618 nCompress = 10000 - nCompress;
620 ( pCurrent->GetKanaComp() )[ nKanaIdx ] = o3tl::narrowing<sal_uInt16>(nCompress);
621 nKanaDiffSum = 0;
624 nKanaIdx++;
627 nX += pPos->Width();
628 pPos = pPos->GetNextPortion();
631 // set portion width
632 nKanaIdx = 0;
633 sal_uInt16 nCompress = ( pCurrent->GetKanaComp() )[ nKanaIdx ];
634 pPos = pCurrent->GetNextPortion();
635 tools::Long nDecompress = 0;
637 while( pPos )
639 if ( pPos->InTextGrp() )
641 const SwTwips nMinWidth = pPos->Width();
643 // get maximum portion width from info structure, calculated
644 // during text formatting
645 SwTwips nMaxWidthDiff = GetInfo().GetMaxWidthDiff( pPos );
647 // check, if information is stored under other key
648 if ( !nMaxWidthDiff && pPos == pCurrent->GetFirstPortion() )
649 nMaxWidthDiff = GetInfo().GetMaxWidthDiff( pCurrent );
650 pPos->Width( nMinWidth +
651 ( ( 10000 - nCompress ) * nMaxWidthDiff ) / 10000 );
652 nDecompress += pPos->Width() - nMinWidth;
654 else if( pPos->InGlueGrp() && pPos->InFixMargGrp() )
656 pPos->Width(pPos->Width() - nDecompress);
658 if ( pPos->InTabGrp() )
659 // set fix width to width
660 static_cast<SwTabPortion*>(pPos)->SetFixWidth( pPos->Width() );
662 if ( ++nKanaIdx < pCurrent->GetKanaComp().size() )
663 nCompress = ( pCurrent->GetKanaComp() )[ nKanaIdx ];
665 nDecompress = 0;
667 pPos = pPos->GetNextPortion();
670 return nRepaintOfst;
673 SwMarginPortion *SwTextAdjuster::CalcRightMargin( SwLineLayout *pCurrent,
674 SwTwips nReal )
676 tools::Long nRealWidth;
677 const SwTwips nRealHeight = GetLineHeight();
678 const SwTwips nLineHeight = pCurrent->Height();
680 SwTwips nPrtWidth = pCurrent->PrtWidth();
681 SwLinePortion *pLast = pCurrent->FindLastPortion();
683 if( GetInfo().IsMulti() )
684 nRealWidth = nReal;
685 else
687 nRealWidth = GetLineWidth();
688 // For each FlyFrame extending into the right margin, we create a FlyPortion.
689 const tools::Long nLeftMar = GetLeftMargin();
690 SwRect aCurrRect( nLeftMar + nPrtWidth, Y() + nRealHeight - nLineHeight,
691 nRealWidth - nPrtWidth, nLineHeight );
693 SwFlyPortion *pFly = CalcFlyPortion( nRealWidth, aCurrRect );
694 while( pFly && tools::Long( nPrtWidth )< nRealWidth )
696 pLast->Append( pFly );
697 pLast = pFly;
698 if( pFly->GetFix() > nPrtWidth )
699 pFly->Width( ( pFly->GetFix() - nPrtWidth) + pFly->Width() + 1);
700 nPrtWidth += pFly->Width() + 1;
701 aCurrRect.Left( nLeftMar + nPrtWidth );
702 pFly = CalcFlyPortion( nRealWidth, aCurrRect );
704 delete pFly;
707 SwMarginPortion *pRight = new SwMarginPortion;
708 pLast->Append( pRight );
710 if( tools::Long( nPrtWidth )< nRealWidth )
711 pRight->PrtWidth(nRealWidth - nPrtWidth);
713 // pCurrent->Width() is set to the real size, because we attach the
714 // MarginPortions.
715 // This trick gives miraculous results:
716 // If pCurrent->Width() == nRealWidth, then the adjustment gets overruled
717 // implicitly. GetLeftMarginAdjust() and IsJustified() think they have a
718 // line filled with chars.
720 pCurrent->PrtWidth(nRealWidth);
721 return pRight;
724 void SwTextAdjuster::CalcFlyAdjust( SwLineLayout *pCurrent )
726 // 1) We insert a left margin:
727 SwMarginPortion *pLeft = pCurrent->CalcLeftMargin();
728 SwGluePortion *pGlue = pLeft; // the last GluePortion
730 // 2) We attach a right margin:
731 // CalcRightMargin also calculates a possible overlap with FlyFrames.
732 CalcRightMargin( pCurrent );
734 SwLinePortion *pPos = pLeft->GetNextPortion();
735 TextFrameIndex nLen(0);
737 // If we only have one line, the text portion is consecutive and we center, then ...
738 bool bComplete = TextFrameIndex(0) == m_nStart;
739 const bool bTabCompat = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT);
740 bool bMultiTab = false;
742 while( pPos )
744 if ( pPos->IsMultiPortion() && static_cast<SwMultiPortion*>(pPos)->HasTabulator() )
745 bMultiTab = true;
746 else if( pPos->InFixMargGrp() &&
747 ( bTabCompat ? ! pPos->InTabGrp() : ! bMultiTab ) )
749 // in tab compat mode we do not want to change tab portions
750 // in non tab compat mode we do not want to change margins if we
751 // found a multi portion with tabs
752 if( SvxAdjust::Right == GetAdjust() )
753 static_cast<SwGluePortion*>(pPos)->MoveAllGlue( pGlue );
754 else
756 // We set the first text portion to right-aligned and the last one
757 // to left-aligned.
758 // The first text portion gets the whole Glue, but only if we have
759 // more than one line.
760 if (bComplete && TextFrameIndex(GetInfo().GetText().getLength()) == nLen)
761 static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue );
762 else
764 if ( ! bTabCompat )
766 if( pLeft == pGlue )
768 // If we only have a left and right margin, the
769 // margins share the Glue.
770 if( nLen + pPos->GetLen() >= pCurrent->GetLen() )
771 static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue );
772 else
773 static_cast<SwGluePortion*>(pPos)->MoveAllGlue( pGlue );
775 else
777 // The last text portion retains its Glue.
778 if( !pPos->IsMarginPortion() )
779 static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue );
782 else
783 static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue );
787 pGlue = static_cast<SwGluePortion*>(pPos);
788 bComplete = false;
790 nLen = nLen + pPos->GetLen();
791 pPos = pPos->GetNextPortion();
794 if( ! bTabCompat && ! bMultiTab && SvxAdjust::Right == GetAdjust() )
795 // portions are moved to the right if possible
796 pLeft->AdjustRight( pCurrent );
799 void SwTextAdjuster::CalcAdjLine( SwLineLayout *pCurrent )
801 OSL_ENSURE( pCurrent->IsFormatAdj(), "CalcAdjLine: Why?" );
803 pCurrent->SetFormatAdj(false);
805 SwParaPortion* pPara = GetInfo().GetParaPortion();
807 switch( GetAdjust() )
809 case SvxAdjust::Right:
810 case SvxAdjust::Center:
812 CalcFlyAdjust( pCurrent );
813 pPara->GetRepaint().SetOffset( 0 );
814 break;
816 case SvxAdjust::Block:
818 FormatBlock();
819 break;
821 default : return;
825 // This is a quite complicated calculation: nCurrWidth is the width _before_
826 // adding the word, that still fits onto the line! For this reason the FlyPortion's
827 // width is still correct if we get a deadlock-situation of:
828 // bFirstWord && !WORDFITS
829 SwFlyPortion *SwTextAdjuster::CalcFlyPortion( const tools::Long nRealWidth,
830 const SwRect &rCurrRect )
832 SwTextFly aTextFly( GetTextFrame() );
834 const SwTwips nCurrWidth = m_pCurr->PrtWidth();
835 SwFlyPortion *pFlyPortion = nullptr;
837 SwRect aLineVert( rCurrRect );
838 if ( GetTextFrame()->IsRightToLeft() )
839 GetTextFrame()->SwitchLTRtoRTL( aLineVert );
840 if ( GetTextFrame()->IsVertical() )
841 GetTextFrame()->SwitchHorizontalToVertical( aLineVert );
843 // aFlyRect is document-global!
844 SwRect aFlyRect( aTextFly.GetFrame( aLineVert ) );
846 if ( GetTextFrame()->IsRightToLeft() )
847 GetTextFrame()->SwitchRTLtoLTR( aFlyRect );
848 if ( GetTextFrame()->IsVertical() )
849 GetTextFrame()->SwitchVerticalToHorizontal( aFlyRect );
851 // If a Frame overlapps we open a Portion
852 if( aFlyRect.HasArea() )
854 // aLocal is frame-local
855 SwRect aLocal( aFlyRect );
856 aLocal.Pos( aLocal.Left() - GetLeftMargin(), aLocal.Top() );
857 if( nCurrWidth > aLocal.Left() )
858 aLocal.Left( nCurrWidth );
860 // If the rect is wider than the line, we adjust it to the right size
861 const tools::Long nLocalWidth = aLocal.Left() + aLocal.Width();
862 if( nRealWidth < nLocalWidth )
863 aLocal.Width( nRealWidth - aLocal.Left() );
864 GetInfo().GetParaPortion()->SetFly();
865 pFlyPortion = new SwFlyPortion( aLocal );
866 pFlyPortion->Height(rCurrRect.Height());
867 // The Width could be smaller than the FixWidth, thus:
868 pFlyPortion->AdjFixWidth();
870 return pFlyPortion;
873 // CalcDropAdjust is called at the end by Format() if needed
874 void SwTextAdjuster::CalcDropAdjust()
876 OSL_ENSURE( 1<GetDropLines() && SvxAdjust::Left!=GetAdjust() && SvxAdjust::Block!=GetAdjust(),
877 "CalcDropAdjust: No reason for DropAdjustment." );
879 const sal_Int32 nLineNumber = GetLineNr();
881 // 1) Skip dummies
882 Top();
884 if( !m_pCurr->IsDummy() || NextLine() )
886 // Adjust first
887 GetAdjusted();
889 SwLinePortion *pPor = m_pCurr->GetFirstPortion();
891 // 2) Make sure we include the ropPortion
892 // 3) pLeft is the GluePor preceding the DropPor
893 if( pPor->InGlueGrp() && pPor->GetNextPortion()
894 && pPor->GetNextPortion()->IsDropPortion() )
896 const SwLinePortion *pDropPor = pPor->GetNextPortion();
897 SwGluePortion *pLeft = static_cast<SwGluePortion*>( pPor );
899 // 4) pRight: Find the GluePor coming after the DropPor
900 pPor = pPor->GetNextPortion();
901 while( pPor && !pPor->InFixMargGrp() )
902 pPor = pPor->GetNextPortion();
904 SwGluePortion *pRight = ( pPor && pPor->InGlueGrp() ) ?
905 static_cast<SwGluePortion*>(pPor) : nullptr;
906 if( pRight && pRight != pLeft )
908 // 5) Calculate nMinLeft. Who is the most to left?
909 const auto nDropLineStart =
910 GetLineStart() + pLeft->Width() + pDropPor->Width();
911 auto nMinLeft = nDropLineStart;
912 for( sal_Int32 i = 1; i < GetDropLines(); ++i )
914 if( NextLine() )
916 // Adjust first
917 GetAdjusted();
919 pPor = m_pCurr->GetFirstPortion();
920 const SwMarginPortion *pMar = pPor->IsMarginPortion() ?
921 static_cast<SwMarginPortion*>(pPor) : nullptr;
922 if( !pMar )
923 nMinLeft = 0;
924 else
926 const auto nLineStart =
927 GetLineStart() + pMar->Width();
928 if( nMinLeft > nLineStart )
929 nMinLeft = nLineStart;
934 // 6) Distribute the Glue anew between pLeft and pRight
935 if( nMinLeft < nDropLineStart )
937 // The Glue is always passed from pLeft to pRight, so that
938 // the text moves to the left.
939 const auto nGlue = nDropLineStart - nMinLeft;
940 if( !nMinLeft )
941 pLeft->MoveAllGlue( pRight );
942 else
943 pLeft->MoveGlue( pRight, nGlue );
949 if( nLineNumber != GetLineNr() )
951 Top();
952 while( nLineNumber != GetLineNr() && Next() )
957 void SwTextAdjuster::CalcDropRepaint()
959 Top();
960 SwRepaint &rRepaint = GetInfo().GetParaPortion()->GetRepaint();
961 if( rRepaint.Top() > Y() )
962 rRepaint.Top( Y() );
963 for( sal_Int32 i = 1; i < GetDropLines(); ++i )
964 NextLine();
965 const SwTwips nBottom = Y() + GetLineHeight() - 1;
966 if( rRepaint.Bottom() < nBottom )
967 rRepaint.Bottom( nBottom );
970 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */