sc: factor out some more code
[LibreOffice.git] / sw / source / core / text / portxt.cxx
blobff8f05e2819776a4f73c157d6c11d0760c225e40
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 <com/sun/star/i18n/ScriptType.hpp>
21 #include <com/sun/star/i18n/XBreakIterator.hpp>
22 #include <i18nlangtag/mslangid.hxx>
23 #include <breakit.hxx>
24 #include <hintids.hxx>
25 #include <EnhancedPDFExportHelper.hxx>
26 #include <SwPortionHandler.hxx>
27 #include "porlay.hxx"
28 #include "inftxt.hxx"
29 #include "guess.hxx"
30 #include "porfld.hxx"
31 #include <pagefrm.hxx>
32 #include <tgrditem.hxx>
33 #include <IDocumentSettingAccess.hxx>
34 #include <IDocumentMarkAccess.hxx>
36 #include <IMark.hxx>
37 #include <pam.hxx>
38 #include <doc.hxx>
39 #include <xmloff/odffields.hxx>
40 #include <viewopt.hxx>
42 using namespace ::sw::mark;
43 using namespace ::com::sun::star;
44 using namespace ::com::sun::star::i18n::ScriptType;
46 static TextFrameIndex lcl_AddSpace_Latin(const SwTextSizeInfo& rInf, const OUString* pStr,
47 const SwLinePortion& rPor, TextFrameIndex nPos,
48 TextFrameIndex nEnd, const SwScriptInfo* pSI,
49 sal_uInt8 nScript);
51 // Returns for how many characters an extra space has to be added
52 // (for justified alignment).
53 static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo &rInf,
54 const OUString* pStr, const SwLinePortion& rPor)
56 TextFrameIndex nPos, nEnd;
57 const SwScriptInfo* pSI = nullptr;
59 if ( pStr )
61 // passing a string means we are inside a field
62 nPos = TextFrameIndex(0);
63 nEnd = TextFrameIndex(pStr->getLength());
65 else
67 nPos = rInf.GetIdx();
68 nEnd = rInf.GetIdx() + rPor.GetLen();
69 pStr = &rInf.GetText();
70 pSI = &const_cast<SwParaPortion*>(rInf.GetParaPortion())->GetScriptInfo();
73 TextFrameIndex nCnt(0);
74 sal_uInt8 nScript = 0;
76 // If portion consists of Asian characters and language is not
77 // Korean, we add extra space to each character.
78 // first we get the script type
79 if ( pSI )
80 nScript = pSI->ScriptType( nPos );
81 else
82 nScript = static_cast<sal_uInt8>(
83 g_pBreakIt->GetBreakIter()->getScriptType(*pStr, sal_Int32(nPos)));
85 // Note: rInf.GetIdx() can differ from nPos,
86 // e.g., when rPor is a field portion. nPos refers to the string passed
87 // to the function, rInf.GetIdx() refers to the original string.
89 // We try to find out which justification mode is required. This is done by
90 // evaluating the script type and the language attribute set for this portion
92 // Asian Justification: Each character get some extra space
93 if ( nEnd > nPos && ASIAN == nScript )
95 LanguageType aLang =
96 rInf.GetTextFrame()->GetLangOfChar(rInf.GetIdx(), nScript);
98 if (!MsLangId::isKorean(aLang))
100 const SwLinePortion* pPor = rPor.GetNextPortion();
101 if ( pPor && ( pPor->IsKernPortion() ||
102 pPor->IsControlCharPortion() ||
103 pPor->IsPostItsPortion() ) )
104 pPor = pPor->GetNextPortion();
106 nCnt += SwScriptInfo::CountCJKCharacters( *pStr, nPos, nEnd, aLang );
108 if ( !pPor || pPor->IsHolePortion() || pPor->InFixMargGrp() ||
109 pPor->IsBreakPortion() )
110 --nCnt;
112 return nCnt;
116 // Kashida Justification: Insert Kashidas
117 if ( nEnd > nPos && pSI && COMPLEX == nScript )
119 if (SwScriptInfo::IsKashidaScriptText(*pStr, nPos, nEnd - nPos) && pSI->CountKashida())
121 const sal_Int32 nKashRes = pSI->KashidaJustify(nullptr, nullptr, nPos, nEnd - nPos);
122 // i60591: need to check result of KashidaJustify
123 // determine if kashida justification is applicable
124 if (nKashRes != -1)
126 // tdf#163105: For kashida justification, also expand whitespace.
127 auto nCntLatin = lcl_AddSpace_Latin(rInf, pStr, rPor, nPos, nEnd, pSI, nScript);
128 return nCntLatin + TextFrameIndex{ nKashRes };
133 // Thai Justification: Each character cell gets some extra space
134 if ( nEnd > nPos && COMPLEX == nScript )
136 LanguageType aLang =
137 rInf.GetTextFrame()->GetLangOfChar(rInf.GetIdx(), nScript);
139 if ( LANGUAGE_THAI == aLang )
141 nCnt = SwScriptInfo::ThaiJustify(*pStr, nullptr, nPos, nEnd - nPos);
143 const SwLinePortion* pPor = rPor.GetNextPortion();
144 if ( pPor && ( pPor->IsKernPortion() ||
145 pPor->IsControlCharPortion() ||
146 pPor->IsPostItsPortion() ) )
147 pPor = pPor->GetNextPortion();
149 if ( nCnt && ( ! pPor || pPor->IsHolePortion() || pPor->InFixMargGrp() ) )
150 --nCnt;
152 return nCnt;
156 return lcl_AddSpace_Latin(rInf, pStr, rPor, nPos, nEnd, pSI, nScript);
159 static TextFrameIndex lcl_AddSpace_Latin(const SwTextSizeInfo& rInf, const OUString* pStr,
160 const SwLinePortion& rPor, TextFrameIndex nPos,
161 TextFrameIndex nEnd, const SwScriptInfo* pSI,
162 sal_uInt8 nScript)
164 TextFrameIndex nCnt(0);
166 // Here starts the good old "Look for blanks and add space to them" part.
167 // Note: We do not want to add space to an isolated latin blank in front
168 // of some complex characters in RTL environment
169 const bool bDoNotAddSpace =
170 LATIN == nScript && (nEnd == nPos + TextFrameIndex(1)) && pSI &&
171 ( i18n::ScriptType::COMPLEX ==
172 pSI->ScriptType(nPos + TextFrameIndex(1))) &&
173 rInf.GetTextFrame() && rInf.GetTextFrame()->IsRightToLeft();
175 if ( bDoNotAddSpace )
176 return nCnt;
178 TextFrameIndex nTextEnd = std::min(nEnd, TextFrameIndex(pStr->getLength()));
179 for ( ; nPos < nTextEnd; ++nPos )
181 if (CH_BLANK == (*pStr)[ sal_Int32(nPos) ])
182 ++nCnt;
185 // We still have to examine the next character:
186 // If the next character is ASIAN and not KOREAN we have
187 // to add an extra space
188 // nPos refers to the original string, even if a field string has
189 // been passed to this function
190 nPos = rInf.GetIdx() + rPor.GetLen();
191 if (nPos < TextFrameIndex(rInf.GetText().getLength()))
193 sal_uInt8 nNextScript = 0;
194 const SwLinePortion* pPor = rPor.GetNextPortion();
195 if ( pPor && pPor->IsKernPortion() )
196 pPor = pPor->GetNextPortion();
198 if (!pPor || pPor->InFixMargGrp())
199 return nCnt;
201 // next character is inside a field?
202 if ( CH_TXTATR_BREAKWORD == rInf.GetChar( nPos ) && pPor->InExpGrp() )
204 bool bOldOnWin = rInf.OnWin();
205 const_cast<SwTextSizeInfo &>(rInf).SetOnWin( false );
207 OUString aStr;
208 pPor->GetExpText( rInf, aStr );
209 const_cast<SwTextSizeInfo &>(rInf).SetOnWin( bOldOnWin );
211 nNextScript = static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType( aStr, 0 ));
213 else
214 nNextScript = static_cast<sal_uInt8>(
215 g_pBreakIt->GetBreakIter()->getScriptType(rInf.GetText(), sal_Int32(nPos)));
217 if( ASIAN == nNextScript )
219 LanguageType aLang =
220 rInf.GetTextFrame()->GetLangOfChar(nPos, nNextScript);
222 if (!MsLangId::isKorean(aLang))
223 ++nCnt;
227 return nCnt;
230 SwTextPortion * SwTextPortion::CopyLinePortion(const SwLinePortion &rPortion)
232 SwTextPortion *const pNew(new SwTextPortion);
233 static_cast<SwLinePortion&>(*pNew) = rPortion;
234 pNew->SetWhichPor( PortionType::Text ); // overwrite that!
235 return pNew;
238 void SwTextPortion::BreakCut( SwTextFormatInfo &rInf, const SwTextGuess &rGuess )
240 // The word/char is larger than the line
241 // Special case 1: The word is larger than the line
242 // We truncate ...
243 const SwTwips nLineWidth = rInf.Width() - rInf.X();
244 TextFrameIndex nLen = rGuess.CutPos() - rInf.GetIdx();
245 if (nLen > TextFrameIndex(0))
247 // special case: guess does not always provide the correct
248 // width, only in common cases.
249 if ( !rGuess.BreakWidth() )
251 rInf.SetLen( nLen );
252 SetLen( nLen );
253 CalcTextSize( rInf );
255 // changing these values requires also changing them in
256 // guess.cxx
257 SwTwips nItalic = 0;
258 if( ITALIC_NONE != rInf.GetFont()->GetItalic() && !rInf.NotEOL() )
260 nItalic = Height() / 12;
262 Width( Width() + nItalic );
264 else
266 Width( rGuess.BreakWidth() );
267 SetLen( nLen );
270 // special case: first character does not fit to line
271 else if ( rGuess.CutPos() == rInf.GetLineStart() )
273 SetLen( TextFrameIndex(1) );
274 Width( nLineWidth );
276 else
278 SetLen( TextFrameIndex(0) );
279 Width( 0 );
280 ExtraShrunkWidth( 0 );
284 void SwTextPortion::BreakUnderflow( SwTextFormatInfo &rInf )
286 Truncate();
287 Height( 0 );
288 Width( 0 );
289 ExtraShrunkWidth( 0 );
290 SetLen( TextFrameIndex(0) );
291 SetAscent( 0 );
292 rInf.SetUnderflow( this );
295 static bool lcl_HasContent( const SwFieldPortion& rField, SwTextFormatInfo const &rInf )
297 OUString aText;
298 return rField.GetExpText( rInf, aText ) && !aText.isEmpty();
301 bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
303 // 5744: If only the hyphen does not fit anymore, we still need to wrap
304 // the word, or else return true!
305 if( rInf.IsUnderflow() && rInf.GetSoftHyphPos() )
307 // soft hyphen portion has triggered an underflow event because
308 // of an alternative spelling position
309 bool bFull = false;
310 const bool bHyph = rInf.ChgHyph( true );
311 if( rInf.IsHyphenate() )
313 SwTextGuess aGuess;
314 // check for alternative spelling left from the soft hyphen
315 // this should usually be true but
316 aGuess.AlternativeSpelling(rInf, rInf.GetSoftHyphPos() - TextFrameIndex(1));
317 bFull = CreateHyphen( rInf, aGuess );
318 OSL_ENSURE( bFull, "Problem with hyphenation!!!" );
320 rInf.ChgHyph( bHyph );
321 rInf.SetSoftHyphPos( TextFrameIndex(0) );
322 return bFull;
325 ExtraShrunkWidth( 0 );
326 std::unique_ptr<SwTextGuess> pGuess(new SwTextGuess());
327 bool bFull = !pGuess->Guess( *this, rInf, Height() );
329 // tdf#158776 for the last full text portion, call Guess() again to allow more text in the
330 // adjusted line by shrinking spaces using the know space count from the first Guess() call
331 const SvxAdjust aAdjust = rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust().GetAdjust();
332 if ( bFull && aAdjust == SvxAdjust::Block &&
333 pGuess->BreakPos() != TextFrameIndex(COMPLETE_STRING) &&
334 rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
335 DocumentSettingId::JUSTIFY_LINES_WITH_SHRINKING) &&
336 // tdf#158436 avoid shrinking at underflow, e.g. no-break space after a
337 // very short word resulted endless loop
338 !rInf.IsUnderflow() )
340 sal_Int32 nSpacesInLine(0);
341 for (sal_Int32 i = sal_Int32(rInf.GetLineStart()); i < sal_Int32(pGuess->BreakPos()); ++i)
343 sal_Unicode cChar = rInf.GetText()[i];
344 if ( cChar == CH_BLANK )
345 ++nSpacesInLine;
348 // call with an extra space: shrinking can result a new word in the line
349 // and a new space before that, which is also a shrank space
350 // (except if the line was already broken inside a word with hyphenation)
351 // TODO: handle the case, if the line contains extra amount of spaces
352 if (
353 // no automatic hyphenation
354 !pGuess->HyphWord().is() &&
355 // no hyphenation at soft hyphen
356 pGuess->BreakPos() < TextFrameIndex(rInf.GetText().getLength()) &&
357 rInf.GetText()[sal_Int32(pGuess->BreakPos())] != CHAR_SOFTHYPHEN )
359 ++nSpacesInLine;
362 if ( nSpacesInLine > 0 )
364 SwTwips nOldWidth = pGuess->BreakWidth();
365 pGuess.reset(new SwTextGuess());
366 bFull = !pGuess->Guess( *this, rInf, Height(), nSpacesInLine );
368 if ( pGuess->BreakWidth() > nOldWidth )
369 ExtraShrunkWidth( pGuess->BreakWidth() );
373 // these are the possible cases:
374 // A Portion fits to current line
375 // B Portion does not fit to current line but a possible line break
376 // within the portion has been found by the break iterator, 2 subcases
377 // B1 break is hyphen
378 // B2 break is word end
379 // C Portion does not fit to current line and no possible line break
380 // has been found by break iterator, 2 subcases:
381 // C1 break iterator found a possible line break in portion before us
382 // ==> this break is used (underflow)
383 // C2 break iterator does not found a possible line break at all:
384 // ==> line break
386 // case A: line not yet full
387 if ( !bFull )
389 Width( pGuess->BreakWidth() );
390 ExtraBlankWidth(pGuess->ExtraBlankWidth());
391 // Caution!
392 if( !InExpGrp() || InFieldGrp() )
393 SetLen( rInf.GetLen() );
395 short nKern = rInf.GetFont()->CheckKerning();
396 if( nKern > 0 && rInf.Width() < rInf.X() + Width() + nKern )
398 nKern = static_cast<short>(rInf.Width() - rInf.X() - Width() - 1);
399 if( nKern < 0 )
400 nKern = 0;
402 if( nKern )
403 new SwKernPortion( *this, nKern );
405 // special case: hanging portion
406 else if( pGuess->GetHangingPortion() )
408 Width( pGuess->BreakWidth() );
409 SetLen( pGuess->BreakPos() - rInf.GetIdx() );
410 pGuess->GetHangingPortion()->SetAscent( GetAscent() );
411 Insert( pGuess->ReleaseHangingPortion() );
413 // breakPos >= index
414 else if (pGuess->BreakPos() >= rInf.GetIdx() && pGuess->BreakPos() != TextFrameIndex(COMPLETE_STRING))
416 // case B1
417 if( pGuess->HyphWord().is() && pGuess->BreakPos() > rInf.GetLineStart()
418 && ( pGuess->BreakPos() > rInf.GetIdx() ||
419 ( rInf.GetLast() && ! rInf.GetLast()->IsFlyPortion() ) ) )
421 CreateHyphen( rInf, *pGuess );
422 if ( rInf.GetFly() )
423 rInf.GetRoot()->SetMidHyph( true );
424 else
425 rInf.GetRoot()->SetEndHyph( true );
427 // case C1
428 // - Footnote portions with fake line start (i.e., not at beginning of line)
429 // should keep together with the text portion. (Note: no keep together
430 // with only footnote portions.
431 // - TabPortions not at beginning of line should keep together with the
432 // text portion, if they are not followed by a blank
433 // (work around different definition of tab stop character - breaking or
434 // non breaking character - in compatibility mode)
435 else if ( ( IsFootnotePortion() && rInf.IsFakeLineStart() &&
437 rInf.IsOtherThanFootnoteInside() ) ||
438 ( rInf.GetLast() &&
439 rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT) &&
440 rInf.GetLast()->InTabGrp() &&
441 rInf.GetLineStart() + rInf.GetLast()->GetLen() < rInf.GetIdx() &&
442 pGuess->BreakPos() == rInf.GetIdx() &&
443 CH_BLANK != rInf.GetChar( rInf.GetIdx() ) &&
444 CH_FULL_BLANK != rInf.GetChar( rInf.GetIdx() ) &&
445 CH_SIX_PER_EM != rInf.GetChar( rInf.GetIdx() ) ) )
446 BreakUnderflow( rInf );
447 // case B2
448 else if( rInf.GetIdx() > rInf.GetLineStart() ||
449 pGuess->BreakPos() > rInf.GetIdx() ||
450 // this is weird: during formatting the follow of a field
451 // the values rInf.GetIdx and rInf.GetLineStart are replaced
452 // IsFakeLineStart indicates GetIdx > GetLineStart
453 rInf.IsFakeLineStart() ||
454 rInf.GetFly() ||
455 rInf.IsFirstMulti() ||
456 ( rInf.GetLast() &&
457 ( rInf.GetLast()->IsFlyPortion() ||
458 ( rInf.GetLast()->InFieldGrp() &&
459 ! rInf.GetLast()->InNumberGrp() &&
460 ! rInf.GetLast()->IsErgoSumPortion() &&
461 lcl_HasContent(*static_cast<SwFieldPortion*>(rInf.GetLast()),rInf ) ) ) ) )
463 Width( pGuess->BreakWidth() );
465 SetLen( pGuess->BreakPos() - rInf.GetIdx() );
467 // Clamp layout context to the end of the line
468 if(auto stClampedContext = GetLayoutContext(); stClampedContext.has_value()) {
469 stClampedContext->m_nEnd = pGuess->BreakPos().get();
470 SetLayoutContext(stClampedContext);
473 OSL_ENSURE( pGuess->BreakStart() >= pGuess->FieldDiff(),
474 "Trouble with expanded field portions during line break" );
475 TextFrameIndex const nRealStart = pGuess->BreakStart() - pGuess->FieldDiff();
476 if( pGuess->BreakPos() < nRealStart && !InExpGrp() )
478 SwHolePortion *pNew = new SwHolePortion( *this );
479 pNew->SetLen( nRealStart - pGuess->BreakPos() );
480 pNew->Width(0);
481 pNew->ExtraBlankWidth( pGuess->ExtraBlankWidth() );
482 Insert( pNew );
484 // UAX #14 Unicode Line Breaking Algorithm Non-tailorable Line breaking rule LB6:
485 // https://www.unicode.org/reports/tr14/#LB6 Do not break before hard line breaks
486 if (auto ch = rInf.GetChar(pGuess->BreakStart()); !ch || ch == CH_BREAK)
487 bFull = false; // Keep following SwBreakPortion / para break in the same line
490 else // case C2, last exit
491 BreakCut( rInf, *pGuess );
493 // breakPos < index or no breakpos at all
494 else
496 bool bFirstPor = rInf.GetLineStart() == rInf.GetIdx();
497 if (pGuess->BreakPos() != TextFrameIndex(COMPLETE_STRING) &&
498 pGuess->BreakPos() != rInf.GetLineStart() &&
499 ( !bFirstPor || rInf.GetFly() || rInf.GetLast()->IsFlyPortion() ||
500 rInf.IsFirstMulti() ) &&
501 ( !rInf.GetLast()->IsBlankPortion() ||
502 SwBlankPortion::MayUnderflow(rInf, rInf.GetIdx() - TextFrameIndex(1), true)))
503 { // case C1 (former BreakUnderflow())
504 BreakUnderflow( rInf );
506 else
507 // case C2, last exit
508 BreakCut(rInf, *pGuess);
511 return bFull;
514 bool SwTextPortion::Format( SwTextFormatInfo &rInf )
516 // GetLineWidth() takes care of DocumentSettingId::TAB_OVER_MARGIN.
517 if( rInf.GetLineWidth() + rInf.GetExtraSpace() < 0 || (!GetLen() && !InExpGrp()) )
519 Height( 0 );
520 Width( 0 );
521 ExtraShrunkWidth( 0 );
522 SetLen( TextFrameIndex(0) );
523 SetAscent( 0 );
524 SetNextPortion( nullptr ); // ????
525 return true;
528 OSL_ENSURE( rInf.RealWidth() || (rInf.X() == rInf.Width()),
529 "SwTextPortion::Format: missing real width" );
530 OSL_ENSURE( Height(), "SwTextPortion::Format: missing height" );
532 return Format_( rInf );
535 // Format end of line
536 // 5083: We can have awkward cases e.g.:
537 // "from {Santa}"
538 // Santa wraps, "from " turns into "from" and " " in a justified
539 // paragraph, in which the glue gets expanded instead of merged
540 // with the MarginPortion.
542 // rInf.nIdx points to the next word, nIdx-1 is the portion's last char
543 void SwTextPortion::FormatEOL( SwTextFormatInfo &rInf )
545 if( ( GetNextPortion() &&
546 ( !GetNextPortion()->IsKernPortion() || GetNextPortion()->GetNextPortion() ) ) ||
547 !GetLen() ||
548 rInf.GetIdx() >= TextFrameIndex(rInf.GetText().getLength()) ||
549 TextFrameIndex(1) >= rInf.GetIdx() ||
550 ' ' != rInf.GetChar(rInf.GetIdx() - TextFrameIndex(1)) ||
551 rInf.GetLast()->IsHolePortion() )
552 return;
554 // calculate number of blanks
555 TextFrameIndex nX(rInf.GetIdx() - TextFrameIndex(1));
556 TextFrameIndex nHoleLen(1);
557 while( nX && nHoleLen < GetLen() && CH_BLANK == rInf.GetChar( --nX ) )
558 nHoleLen++;
560 // First set ourselves and the insert, because there could be
561 // a SwLineLayout
562 SwTwips nBlankSize;
563 if( nHoleLen == GetLen() )
564 nBlankSize = Width();
565 else
566 nBlankSize = sal_Int32(nHoleLen) * rInf.GetTextSize(OUString(' ')).Width();
567 Width( Width() - nBlankSize );
568 rInf.X( rInf.X() - nBlankSize );
569 SetLen( GetLen() - nHoleLen );
570 SwHolePortion* pHole = new SwHolePortion(*this);
571 pHole->SetBlankWidth(nBlankSize);
572 pHole->SetLen(nHoleLen);
573 Insert( pHole );
577 TextFrameIndex SwTextPortion::GetModelPositionForViewPoint(const SwTwips nOfst) const
579 OSL_ENSURE( false, "SwTextPortion::GetModelPositionForViewPoint: don't use this method!" );
580 return SwLinePortion::GetModelPositionForViewPoint( nOfst );
583 // The GetTextSize() assumes that the own length is correct
584 SwPosSize SwTextPortion::GetTextSize( const SwTextSizeInfo &rInf ) const
586 SwPosSize aSize = rInf.GetTextSize(GetLayoutContext());
587 if( !GetJoinBorderWithPrev() )
588 aSize.Width(aSize.Width() + rInf.GetFont()->GetLeftBorderSpace() );
589 if( !GetJoinBorderWithNext() )
590 aSize.Width(aSize.Width() + rInf.GetFont()->GetRightBorderSpace() );
592 aSize.Height(aSize.Height() +
593 rInf.GetFont()->GetTopBorderSpace() +
594 rInf.GetFont()->GetBottomBorderSpace() );
596 return aSize;
599 void SwTextPortion::Paint( const SwTextPaintInfo &rInf ) const
601 if (rInf.OnWin() && TextFrameIndex(1) == rInf.GetLen()
602 && CH_TXT_ATR_FIELDEND == rInf.GetText()[sal_Int32(rInf.GetIdx())])
604 assert(false); // this is some debugging only code
605 rInf.DrawBackBrush( *this );
606 const OUString aText(CH_TXT_ATR_SUBST_FIELDEND);
607 rInf.DrawText(aText, *this, TextFrameIndex(0), TextFrameIndex(aText.getLength()));
609 else if (rInf.OnWin() && TextFrameIndex(1) == rInf.GetLen()
610 && CH_TXT_ATR_FIELDSTART == rInf.GetText()[sal_Int32(rInf.GetIdx())])
612 assert(false); // this is some debugging only code
613 rInf.DrawBackBrush( *this );
614 const OUString aText(CH_TXT_ATR_SUBST_FIELDSTART);
615 rInf.DrawText(aText, *this, TextFrameIndex(0), TextFrameIndex(aText.getLength()));
617 else if( GetLen() )
619 rInf.DrawBackBrush( *this );
620 rInf.DrawBorder( *this );
622 rInf.DrawCSDFHighlighting(*this);
624 // do we have to repaint a post it portion?
625 if( rInf.OnWin() && mpNextPortion && !mpNextPortion->Width() )
626 mpNextPortion->PrePaint( rInf, this );
628 auto const* pWrongList = rInf.GetpWrongList();
629 auto const* pGrammarCheckList = rInf.GetGrammarCheckList();
630 auto const* pSmarttags = rInf.GetSmartTags();
632 const bool bWrong = nullptr != pWrongList;
633 const bool bGrammarCheck = nullptr != pGrammarCheckList;
634 const bool bSmartTags = nullptr != pSmarttags;
636 if ( bWrong || bSmartTags || bGrammarCheck )
637 rInf.DrawMarkedText( *this, rInf.GetLen(), bWrong, bSmartTags, bGrammarCheck );
638 else
639 rInf.DrawText( *this, rInf.GetLen() );
643 bool SwTextPortion::GetExpText( const SwTextSizeInfo &, OUString & ) const
645 return false;
648 // Responsible for the justified paragraph. They calculate the blank
649 // count and the resulting added space.
650 TextFrameIndex SwTextPortion::GetSpaceCnt(const SwTextSizeInfo &rInf,
651 TextFrameIndex& rCharCnt) const
653 TextFrameIndex nCnt(0);
654 TextFrameIndex nPos(0);
656 if ( rInf.SnapToGrid() )
658 SwTextGridItem const*const pGrid(GetGridItem(rInf.GetTextFrame()->FindPageFrame()));
659 if (pGrid && SwTextGrid::LinesAndChars == pGrid->GetGridType() && pGrid->IsSnapToChars())
660 return TextFrameIndex(0);
663 if ( InExpGrp() || PortionType::InputField == GetWhichPor() )
665 if (OUString ExpOut;
666 (!IsBlankPortion()
667 || (GetExpText(rInf, ExpOut) && OUStringChar(CH_BLANK) == ExpOut))
668 && !InNumberGrp() && !IsCombinedPortion())
670 // OnWin() likes to return a blank instead of an empty string from
671 // time to time. We cannot use that here at all, however.
672 bool bOldOnWin = rInf.OnWin();
673 const_cast<SwTextSizeInfo &>(rInf).SetOnWin( false );
675 OUString aStr;
676 GetExpText( rInf, aStr );
677 const_cast<SwTextSizeInfo &>(rInf).SetOnWin( bOldOnWin );
679 nCnt = nCnt + lcl_AddSpace( rInf, &aStr, *this );
680 nPos = TextFrameIndex(aStr.getLength());
683 else if( !IsDropPortion() )
685 nCnt = nCnt + lcl_AddSpace( rInf, nullptr, *this );
686 nPos = GetLen();
688 rCharCnt = rCharCnt + nPos;
689 return nCnt;
692 SwTwips SwTextPortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) const
694 TextFrameIndex nCnt(0);
696 if ( rInf.SnapToGrid() )
698 SwTextGridItem const*const pGrid(GetGridItem(rInf.GetTextFrame()->FindPageFrame()));
699 if (pGrid && SwTextGrid::LinesAndChars == pGrid->GetGridType() && pGrid->IsSnapToChars())
700 return 0;
703 if ( InExpGrp() || PortionType::InputField == GetWhichPor() )
705 if (OUString ExpOut;
706 (!IsBlankPortion()
707 || (GetExpText(rInf, ExpOut) && OUStringChar(CH_BLANK) == ExpOut))
708 && !InNumberGrp() && !IsCombinedPortion())
710 // OnWin() likes to return a blank instead of an empty string from
711 // time to time. We cannot use that here at all, however.
712 bool bOldOnWin = rInf.OnWin();
713 const_cast<SwTextSizeInfo &>(rInf).SetOnWin( false );
715 OUString aStr;
716 GetExpText( rInf, aStr );
717 const_cast<SwTextSizeInfo &>(rInf).SetOnWin( bOldOnWin );
718 if( nSpaceAdd > 0 )
719 nCnt = nCnt + lcl_AddSpace( rInf, &aStr, *this );
720 else
722 nSpaceAdd = -nSpaceAdd;
723 nCnt = TextFrameIndex(aStr.getLength());
727 else if( !IsDropPortion() )
729 if( nSpaceAdd > 0 )
730 nCnt = nCnt + lcl_AddSpace( rInf, nullptr, *this );
731 else
733 nSpaceAdd = -nSpaceAdd;
734 nCnt = GetLen();
735 SwLinePortion* pPor = GetNextPortion();
737 // we do not want an extra space in front of margin portions
738 if ( nCnt )
740 while ( pPor && !pPor->Width() && ! pPor->IsHolePortion() )
741 pPor = pPor->GetNextPortion();
743 if ( !pPor || pPor->InFixMargGrp() || pPor->IsHolePortion() )
744 --nCnt;
749 return sal_Int32(nCnt) * (nSpaceAdd > LONG_MAX/2 ? LONG_MAX/2 - nSpaceAdd : nSpaceAdd)
750 / SPACING_PRECISION_FACTOR;
753 void SwTextPortion::HandlePortion( SwPortionHandler& rPH ) const
755 rPH.Text( GetLen(), GetWhichPor() );
758 SwTextInputFieldPortion::SwTextInputFieldPortion()
760 SetWhichPor( PortionType::InputField );
763 bool SwTextInputFieldPortion::Format(SwTextFormatInfo &rTextFormatInfo)
765 return SwTextPortion::Format(rTextFormatInfo);
768 void SwTextInputFieldPortion::Paint( const SwTextPaintInfo &rInf ) const
770 if ( Width() )
772 rInf.DrawViewOpt( *this, PortionType::InputField );
773 SwTextSlot aPaintText( &rInf, this, true, true, OUString() );
774 SwTextPortion::Paint( rInf );
776 else
778 // highlight empty input field, elsewhere they are completely invisible for the user
779 SwRect aIntersect;
780 rInf.CalcRect(*this, &aIntersect);
781 const SwTwips aAreaWidth = rInf.GetTextSize(OUString(' ')).Width();
782 aIntersect.Left(aIntersect.Left() - aAreaWidth/2);
783 aIntersect.Width(aAreaWidth);
785 if (aIntersect.HasArea()
786 && rInf.OnWin()
787 && rInf.GetOpt().IsFieldShadings()
788 && !rInf.GetOpt().IsPagePreview())
790 OutputDevice* pOut = const_cast<OutputDevice*>(rInf.GetOut());
791 pOut->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR);
792 pOut->SetFillColor(rInf.GetOpt().GetFieldShadingsColor());
793 pOut->SetLineColor();
794 pOut->DrawRect(aIntersect.SVRect());
795 pOut->Pop();
800 bool SwTextInputFieldPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const
802 sal_Int32 nIdx(rInf.GetIdx());
803 sal_Int32 nLen(GetLen());
804 if ( rInf.GetChar( rInf.GetIdx() ) == CH_TXT_ATR_INPUTFIELDSTART )
806 ++nIdx;
807 --nLen;
809 if (rInf.GetChar(rInf.GetIdx() + GetLen() - TextFrameIndex(1)) == CH_TXT_ATR_INPUTFIELDEND)
811 --nLen;
813 rText = rInf.GetText().copy( nIdx, std::min( nLen, rInf.GetText().getLength() - nIdx ) );
815 return true;
818 SwPosSize SwTextInputFieldPortion::GetTextSize( const SwTextSizeInfo &rInf ) const
820 SwTextSlot aFormatText( &rInf, this, true, false );
821 if (rInf.GetLen() == TextFrameIndex(0))
823 return SwPosSize( 0, 0 );
826 return rInf.GetTextSize();
829 SwHolePortion::SwHolePortion( const SwTextPortion &rPor )
830 : m_nBlankWidth( 0 )
832 SetLen( TextFrameIndex(1) );
833 Height( rPor.Height() );
834 Width(0);
835 SetAscent( rPor.GetAscent() );
836 SetWhichPor( PortionType::Hole );
839 SwLinePortion *SwHolePortion::Compress() { return this; }
841 // The GetTextSize() assumes that the own length is correct
842 SwPosSize SwHolePortion::GetTextSize(const SwTextSizeInfo& rInf) const
844 SwPosSize aSize = rInf.GetTextSize();
845 if (!GetJoinBorderWithPrev())
846 aSize.Width(aSize.Width() + rInf.GetFont()->GetLeftBorderSpace());
847 if (!GetJoinBorderWithNext())
848 aSize.Width(aSize.Width() + rInf.GetFont()->GetRightBorderSpace());
850 aSize.Height(aSize.Height() +
851 rInf.GetFont()->GetTopBorderSpace() +
852 rInf.GetFont()->GetBottomBorderSpace());
854 return aSize;
857 void SwHolePortion::Paint( const SwTextPaintInfo &rInf ) const
859 if( !rInf.GetOut() )
860 return;
862 bool bPDFExport = rInf.GetVsh()->GetViewOptions()->IsPDFExport();
864 // #i16816# export stuff only needed for tagged pdf support
865 if (bPDFExport && !SwTaggedPDFHelper::IsExportTaggedPDF( *rInf.GetOut()) )
866 return;
868 // #i68503# the hole must have no decoration for a consistent visual appearance
869 const SwFont* pOrigFont = rInf.GetFont();
870 std::unique_ptr<SwFont> pHoleFont;
871 std::optional<SwFontSave> oFontSave;
872 if( pOrigFont->GetUnderline() != LINESTYLE_NONE
873 || pOrigFont->GetOverline() != LINESTYLE_NONE
874 || pOrigFont->GetStrikeout() != STRIKEOUT_NONE )
876 pHoleFont.reset(new SwFont( *pOrigFont ));
877 pHoleFont->SetUnderline( LINESTYLE_NONE );
878 pHoleFont->SetOverline( LINESTYLE_NONE );
879 pHoleFont->SetStrikeout( STRIKEOUT_NONE );
880 oFontSave.emplace( rInf, pHoleFont.get() );
883 if (bPDFExport)
885 rInf.DrawText(u" "_ustr, *this, TextFrameIndex(0), TextFrameIndex(1));
887 else
889 // tdf#43244: Paint spaces even at end of line,
890 // but only if this paint is not called for pdf export, to keep that pdf export intact
891 rInf.DrawText(*this, rInf.GetLen());
894 oFontSave.reset();
895 pHoleFont.reset();
898 bool SwHolePortion::Format( SwTextFormatInfo &rInf )
900 return rInf.IsFull() || rInf.X() >= rInf.Width();
903 void SwHolePortion::HandlePortion( SwPortionHandler& rPH ) const
905 rPH.Text( GetLen(), GetWhichPor() );
908 void SwHolePortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, TextFrameIndex& nOffset) const
910 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwHolePortion"));
911 dumpAsXmlAttributes(pWriter, rText, nOffset);
912 nOffset += GetLen();
914 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("blank-width"),
915 BAD_CAST(OString::number(m_nBlankWidth).getStr()));
917 (void)xmlTextWriterEndElement(pWriter);
920 void SwFieldMarkPortion::Paint( const SwTextPaintInfo & /*rInf*/) const
922 // These shouldn't be painted!
923 //SwTextPortion::Paint(rInf);
926 bool SwFieldMarkPortion::Format( SwTextFormatInfo & )
928 Width(0);
929 return false;
932 void SwFieldFormCheckboxPortion::Paint( const SwTextPaintInfo& rInf ) const
934 SwPosition const aPosition(rInf.GetTextFrame()->MapViewToModelPos(rInf.GetIdx()));
936 Fieldmark const*const pBM = rInf.GetTextFrame()->GetDoc().getIDocumentMarkAccess()->getFieldmarkAt(aPosition);
938 OSL_ENSURE(pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX,
939 "Where is my form field bookmark???");
941 if (pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX)
943 const CheckboxFieldmark* pCheckboxFm = dynamic_cast<CheckboxFieldmark const*>(pBM);
944 bool bChecked = pCheckboxFm && pCheckboxFm->IsChecked();
945 rInf.DrawCheckBox(*this, bChecked);
949 bool SwFieldFormCheckboxPortion::Format( SwTextFormatInfo & rInf )
951 SwPosition const aPosition(rInf.GetTextFrame()->MapViewToModelPos(rInf.GetIdx()));
952 Fieldmark const*const pBM = rInf.GetTextFrame()->GetDoc().getIDocumentMarkAccess()->getFieldmarkAt(aPosition);
953 OSL_ENSURE(pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX, "Where is my form field bookmark???");
954 if (pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX)
956 // the width of the checkbox portion is the same as its height since it's a square
957 // and that size depends on the font size.
958 // See:
959 // http://document-foundation-mail-archive.969070.n3.nabble.com/Wrong-copy-paste-in-SwFieldFormCheckboxPortion-Format-td4269112.html
960 Width( rInf.GetTextHeight( ) );
961 Height( rInf.GetTextHeight( ) );
962 SetAscent( rInf.GetAscent( ) );
964 return false;
967 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */