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 <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>
31 #include <pagefrm.hxx>
32 #include <tgrditem.hxx>
33 #include <IDocumentSettingAccess.hxx>
34 #include <IDocumentMarkAccess.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
,
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;
61 // passing a string means we are inside a field
62 nPos
= TextFrameIndex(0);
63 nEnd
= TextFrameIndex(pStr
->getLength());
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
80 nScript
= pSI
->ScriptType( nPos
);
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
)
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() )
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
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
)
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() ) )
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
,
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
)
178 TextFrameIndex nTextEnd
= std::min(nEnd
, TextFrameIndex(pStr
->getLength()));
179 for ( ; nPos
< nTextEnd
; ++nPos
)
181 if (CH_BLANK
== (*pStr
)[ sal_Int32(nPos
) ])
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())
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 );
208 pPor
->GetExpText( rInf
, aStr
);
209 const_cast<SwTextSizeInfo
&>(rInf
).SetOnWin( bOldOnWin
);
211 nNextScript
= static_cast<sal_uInt8
>(g_pBreakIt
->GetBreakIter()->getScriptType( aStr
, 0 ));
214 nNextScript
= static_cast<sal_uInt8
>(
215 g_pBreakIt
->GetBreakIter()->getScriptType(rInf
.GetText(), sal_Int32(nPos
)));
217 if( ASIAN
== nNextScript
)
220 rInf
.GetTextFrame()->GetLangOfChar(nPos
, nNextScript
);
222 if (!MsLangId::isKorean(aLang
))
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!
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
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() )
253 CalcTextSize( rInf
);
255 // changing these values requires also changing them in
258 if( ITALIC_NONE
!= rInf
.GetFont()->GetItalic() && !rInf
.NotEOL() )
260 nItalic
= Height() / 12;
262 Width( Width() + nItalic
);
266 Width( rGuess
.BreakWidth() );
270 // special case: first character does not fit to line
271 else if ( rGuess
.CutPos() == rInf
.GetLineStart() )
273 SetLen( TextFrameIndex(1) );
278 SetLen( TextFrameIndex(0) );
280 ExtraShrunkWidth( 0 );
284 void SwTextPortion::BreakUnderflow( SwTextFormatInfo
&rInf
)
289 ExtraShrunkWidth( 0 );
290 SetLen( TextFrameIndex(0) );
292 rInf
.SetUnderflow( this );
295 static bool lcl_HasContent( const SwFieldPortion
& rField
, SwTextFormatInfo
const &rInf
)
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
310 const bool bHyph
= rInf
.ChgHyph( true );
311 if( rInf
.IsHyphenate() )
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) );
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
)
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
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
)
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:
386 // case A: line not yet full
389 Width( pGuess
->BreakWidth() );
390 ExtraBlankWidth(pGuess
->ExtraBlankWidth());
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);
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() );
414 else if (pGuess
->BreakPos() >= rInf
.GetIdx() && pGuess
->BreakPos() != TextFrameIndex(COMPLETE_STRING
))
417 if( pGuess
->HyphWord().is() && pGuess
->BreakPos() > rInf
.GetLineStart()
418 && ( pGuess
->BreakPos() > rInf
.GetIdx() ||
419 ( rInf
.GetLast() && ! rInf
.GetLast()->IsFlyPortion() ) ) )
421 CreateHyphen( rInf
, *pGuess
);
423 rInf
.GetRoot()->SetMidHyph( true );
425 rInf
.GetRoot()->SetEndHyph( true );
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() ) ||
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
);
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() ||
455 rInf
.IsFirstMulti() ||
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() );
481 pNew
->ExtraBlankWidth( pGuess
->ExtraBlankWidth() );
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
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
);
507 // case C2, last exit
508 BreakCut(rInf
, *pGuess
);
514 bool SwTextPortion::Format( SwTextFormatInfo
&rInf
)
516 // GetLineWidth() takes care of DocumentSettingId::TAB_OVER_MARGIN.
517 if( rInf
.GetLineWidth() + rInf
.GetExtraSpace() < 0 || (!GetLen() && !InExpGrp()) )
521 ExtraShrunkWidth( 0 );
522 SetLen( TextFrameIndex(0) );
524 SetNextPortion( nullptr ); // ????
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.:
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() ) ) ||
548 rInf
.GetIdx() >= TextFrameIndex(rInf
.GetText().getLength()) ||
549 TextFrameIndex(1) >= rInf
.GetIdx() ||
550 ' ' != rInf
.GetChar(rInf
.GetIdx() - TextFrameIndex(1)) ||
551 rInf
.GetLast()->IsHolePortion() )
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
) )
560 // First set ourselves and the insert, because there could be
563 if( nHoleLen
== GetLen() )
564 nBlankSize
= Width();
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
);
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() );
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()));
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
);
639 rInf
.DrawText( *this, rInf
.GetLen() );
643 bool SwTextPortion::GetExpText( const SwTextSizeInfo
&, OUString
& ) const
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() )
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 );
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 );
688 rCharCnt
= rCharCnt
+ nPos
;
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())
703 if ( InExpGrp() || PortionType::InputField
== GetWhichPor() )
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 );
716 GetExpText( rInf
, aStr
);
717 const_cast<SwTextSizeInfo
&>(rInf
).SetOnWin( bOldOnWin
);
719 nCnt
= nCnt
+ lcl_AddSpace( rInf
, &aStr
, *this );
722 nSpaceAdd
= -nSpaceAdd
;
723 nCnt
= TextFrameIndex(aStr
.getLength());
727 else if( !IsDropPortion() )
730 nCnt
= nCnt
+ lcl_AddSpace( rInf
, nullptr, *this );
733 nSpaceAdd
= -nSpaceAdd
;
735 SwLinePortion
* pPor
= GetNextPortion();
737 // we do not want an extra space in front of margin portions
740 while ( pPor
&& !pPor
->Width() && ! pPor
->IsHolePortion() )
741 pPor
= pPor
->GetNextPortion();
743 if ( !pPor
|| pPor
->InFixMargGrp() || pPor
->IsHolePortion() )
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
772 rInf
.DrawViewOpt( *this, PortionType::InputField
);
773 SwTextSlot
aPaintText( &rInf
, this, true, true, OUString() );
774 SwTextPortion::Paint( rInf
);
778 // highlight empty input field, elsewhere they are completely invisible for the user
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()
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());
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
)
809 if (rInf
.GetChar(rInf
.GetIdx() + GetLen() - TextFrameIndex(1)) == CH_TXT_ATR_INPUTFIELDEND
)
813 rText
= rInf
.GetText().copy( nIdx
, std::min( nLen
, rInf
.GetText().getLength() - nIdx
) );
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
)
832 SetLen( TextFrameIndex(1) );
833 Height( rPor
.Height() );
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());
857 void SwHolePortion::Paint( const SwTextPaintInfo
&rInf
) const
862 bool bPDFExport
= rInf
.GetVsh()->GetViewOptions()->IsPDFExport();
864 // #i16816# export stuff only needed for tagged pdf support
865 if (bPDFExport
&& !SwTaggedPDFHelper::IsExportTaggedPDF( *rInf
.GetOut()) )
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() );
885 rInf
.DrawText(u
" "_ustr
, *this, TextFrameIndex(0), TextFrameIndex(1));
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());
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
);
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
& )
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.
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( ) );
967 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */