1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <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>
31 #include "porglue.hxx"
34 #include "pormulti.hxx"
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
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() )
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.
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() )
90 pPor
= pPor
->GetNextPortion();
92 pLay
= bSkip
? pLay
->GetNext() : nullptr;
98 if( !GetInfo().GetParaPortion()->HasFly() )
101 CalcFlyAdjust( m_pCurr
);
102 m_pCurr
->FinishSpaceAdd();
107 const SwLinePortion
*pTmpFly
= nullptr;
109 // End at the last Fly
110 const SwLinePortion
*pPos
= m_pCurr
->GetFirstPortion();
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!
121 pPos
= pPos
->GetNextPortion();
123 // End if we didn't find one
127 CalcFlyAdjust( m_pCurr
);
128 m_pCurr
->FinishSpaceAdd();
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();
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
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
)
242 if (nNext
== TextFrameIndex(COMPLETE_STRING
) || 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
)
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)
261 sal_Int32 nKashidasDropped
= 0;
262 if (!SwScriptInfo::IsKashidaScriptText(rInf
.GetText(), nIdx
, nNext
- nIdx
))
264 nKashidasDropped
= nKashidasInAttr
;
265 rKashidas
-= nKashidasDropped
;
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
;
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
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
)
321 if (nNext
== TextFrameIndex(COMPLETE_STRING
) || 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
)
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
350 if ( !bAddSpaceChanged
)
351 break; // everything was OK
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())
383 rSI
.SetNoKashidaLine ( aItr
.GetStart(), aItr
.GetLength());
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!
394 CalcRightMargin( pCurrent
, nReal
);
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;
407 nBreakWidth
+= pPos
->Width();
409 if ( ( bDoNotJustifyLinesWithManualBreak
|| bDoNotJustifyTab
) &&
410 pPos
->IsBreakPortion() && !IsLastBlock() )
412 pCurrent
->FinishSpaceAdd();
416 switch ( pPos
->GetWhichPor() )
418 case PortionType::TabCenter
:
419 case PortionType::TabRight
:
420 case PortionType::TabDecimal
:
421 bDoNotJustifyTab
= true;
423 case PortionType::TabLeft
:
424 case PortionType::Break
:
425 bDoNotJustifyTab
= false;
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
439 if( pMulti
->HasTabulator() )
441 if ( nSpaceIdx
== pCurrent
->GetLLSpaceAddCount() )
442 pCurrent
->SetLLSpaceAdd( 0, 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
,
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
);
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
496 // shrink, if portions exceed the line width available before an image
497 ? -nSpaceAdd
+ LONG_MAX
/2
501 if( rSI
.CountKashida() && !bSkipKashida
)
503 if( !lcl_CheckKashidaWidth( rSI
, aInf
, aItr
, nKashidas
, nGluePortion
, nGluePortionWidth
, nSpaceAdd
))
506 // do regular blank justification
507 pCurrent
->FinishSpaceAdd();
508 GetInfo().SetIdx( m_nStart
);
509 CalcNewBlock( pCurrent
, pStopAt
, nReal
, true );
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() );
525 nGluePortion
= TextFrameIndex(0);
526 nCharCnt
= TextFrameIndex(0);
531 GetInfo().SetIdx( GetInfo().GetIdx() + pPos
->GetLen() );
532 if ( pPos
== pStopAt
)
534 pCurrent
->SetLLSpaceAdd( 0, nSpaceIdx
);
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;
550 tools::Long nKanaDiffSum
= 0;
551 SwTwips nRepaintOfst
= 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();
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
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
);
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();
601 nRest
= ! bNoCompression
?
602 static_cast<SwGluePortion
*>(pPos
)->GetPrtGlue() :
605 bNoCompression
= false;
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
618 nCompress
= 10000 - nCompress
;
620 ( pCurrent
->GetKanaComp() )[ nKanaIdx
] = o3tl::narrowing
<sal_uInt16
>(nCompress
);
628 pPos
= pPos
->GetNextPortion();
633 sal_uInt16 nCompress
= ( pCurrent
->GetKanaComp() )[ nKanaIdx
];
634 pPos
= pCurrent
->GetNextPortion();
635 tools::Long nDecompress
= 0;
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
];
667 pPos
= pPos
->GetNextPortion();
673 SwMarginPortion
*SwTextAdjuster::CalcRightMargin( SwLineLayout
*pCurrent
,
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() )
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
);
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
);
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
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
);
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;
744 if ( pPos
->IsMultiPortion() && static_cast<SwMultiPortion
*>(pPos
)->HasTabulator() )
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
);
756 // We set the first text portion to right-aligned and the last one
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
);
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
);
773 static_cast<SwGluePortion
*>(pPos
)->MoveAllGlue( pGlue
);
777 // The last text portion retains its Glue.
778 if( !pPos
->IsMarginPortion() )
779 static_cast<SwGluePortion
*>(pPos
)->MoveHalfGlue( pGlue
);
783 static_cast<SwGluePortion
*>(pPos
)->MoveHalfGlue( pGlue
);
787 pGlue
= static_cast<SwGluePortion
*>(pPos
);
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 );
816 case SvxAdjust::Block
:
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();
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();
884 if( !m_pCurr
->IsDummy() || NextLine() )
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
)
919 pPor
= m_pCurr
->GetFirstPortion();
920 const SwMarginPortion
*pMar
= pPor
->IsMarginPortion() ?
921 static_cast<SwMarginPortion
*>(pPor
) : nullptr;
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
;
941 pLeft
->MoveAllGlue( pRight
);
943 pLeft
->MoveGlue( pRight
, nGlue
);
949 if( nLineNumber
!= GetLineNr() )
952 while( nLineNumber
!= GetLineNr() && Next() )
957 void SwTextAdjuster::CalcDropRepaint()
960 SwRepaint
&rRepaint
= GetInfo().GetParaPortion()->GetRepaint();
961 if( rRepaint
.Top() > Y() )
963 for( sal_Int32 i
= 1; i
< GetDropLines(); ++i
)
965 const SwTwips nBottom
= Y() + GetLineHeight() - 1;
966 if( rRepaint
.Bottom() < nBottom
)
967 rRepaint
.Bottom( nBottom
);
970 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */