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 // Returns for how many characters an extra space has to be added
47 // (for justified alignment).
48 static TextFrameIndex
lcl_AddSpace(const SwTextSizeInfo
&rInf
,
49 const OUString
* pStr
, const SwLinePortion
& rPor
)
51 TextFrameIndex nPos
, nEnd
;
52 const SwScriptInfo
* pSI
= nullptr;
56 // passing a string means we are inside a field
57 nPos
= TextFrameIndex(0);
58 nEnd
= TextFrameIndex(pStr
->getLength());
63 nEnd
= rInf
.GetIdx() + rPor
.GetLen();
64 pStr
= &rInf
.GetText();
65 pSI
= &const_cast<SwParaPortion
*>(rInf
.GetParaPortion())->GetScriptInfo();
68 TextFrameIndex
nCnt(0);
69 sal_uInt8 nScript
= 0;
71 // If portion consists of Asian characters and language is not
72 // Korean, we add extra space to each character.
73 // first we get the script type
75 nScript
= pSI
->ScriptType( nPos
);
77 nScript
= static_cast<sal_uInt8
>(
78 g_pBreakIt
->GetBreakIter()->getScriptType(*pStr
, sal_Int32(nPos
)));
80 // Note: rInf.GetIdx() can differ from nPos,
81 // e.g., when rPor is a field portion. nPos refers to the string passed
82 // to the function, rInf.GetIdx() refers to the original string.
84 // We try to find out which justification mode is required. This is done by
85 // evaluating the script type and the language attribute set for this portion
87 // Asian Justification: Each character get some extra space
88 if ( nEnd
> nPos
&& ASIAN
== nScript
)
91 rInf
.GetTextFrame()->GetLangOfChar(rInf
.GetIdx(), nScript
);
93 if (!MsLangId::isKorean(aLang
))
95 const SwLinePortion
* pPor
= rPor
.GetNextPortion();
96 if ( pPor
&& ( pPor
->IsKernPortion() ||
97 pPor
->IsControlCharPortion() ||
98 pPor
->IsPostItsPortion() ) )
99 pPor
= pPor
->GetNextPortion();
101 nCnt
+= SwScriptInfo::CountCJKCharacters( *pStr
, nPos
, nEnd
, aLang
);
103 if ( !pPor
|| pPor
->IsHolePortion() || pPor
->InFixMargGrp() ||
104 pPor
->IsBreakPortion() )
111 // Kashida Justification: Insert Kashidas
112 if ( nEnd
> nPos
&& pSI
&& COMPLEX
== nScript
)
114 if ( SwScriptInfo::IsArabicText( *pStr
, nPos
, nEnd
- nPos
) && pSI
->CountKashida() )
116 const sal_Int32 nKashRes
= pSI
->KashidaJustify(nullptr, nullptr, nPos
, nEnd
- nPos
);
117 // i60591: need to check result of KashidaJustify
118 // determine if kashida justification is applicable
120 return TextFrameIndex(nKashRes
);
124 // Thai Justification: Each character cell gets some extra space
125 if ( nEnd
> nPos
&& COMPLEX
== nScript
)
128 rInf
.GetTextFrame()->GetLangOfChar(rInf
.GetIdx(), nScript
);
130 if ( LANGUAGE_THAI
== aLang
)
132 nCnt
= SwScriptInfo::ThaiJustify(*pStr
, nullptr, nPos
, nEnd
- nPos
);
134 const SwLinePortion
* pPor
= rPor
.GetNextPortion();
135 if ( pPor
&& ( pPor
->IsKernPortion() ||
136 pPor
->IsControlCharPortion() ||
137 pPor
->IsPostItsPortion() ) )
138 pPor
= pPor
->GetNextPortion();
140 if ( nCnt
&& ( ! pPor
|| pPor
->IsHolePortion() || pPor
->InFixMargGrp() ) )
147 // Here starts the good old "Look for blanks and add space to them" part.
148 // Note: We do not want to add space to an isolated latin blank in front
149 // of some complex characters in RTL environment
150 const bool bDoNotAddSpace
=
151 LATIN
== nScript
&& (nEnd
== nPos
+ TextFrameIndex(1)) && pSI
&&
152 ( i18n::ScriptType::COMPLEX
==
153 pSI
->ScriptType(nPos
+ TextFrameIndex(1))) &&
154 rInf
.GetTextFrame() && rInf
.GetTextFrame()->IsRightToLeft();
156 if ( bDoNotAddSpace
)
159 TextFrameIndex nTextEnd
= std::min(nEnd
, TextFrameIndex(pStr
->getLength()));
160 for ( ; nPos
< nTextEnd
; ++nPos
)
162 if (CH_BLANK
== (*pStr
)[ sal_Int32(nPos
) ])
166 // We still have to examine the next character:
167 // If the next character is ASIAN and not KOREAN we have
168 // to add an extra space
169 // nPos refers to the original string, even if a field string has
170 // been passed to this function
171 nPos
= rInf
.GetIdx() + rPor
.GetLen();
172 if (nPos
< TextFrameIndex(rInf
.GetText().getLength()))
174 sal_uInt8 nNextScript
= 0;
175 const SwLinePortion
* pPor
= rPor
.GetNextPortion();
176 if ( pPor
&& pPor
->IsKernPortion() )
177 pPor
= pPor
->GetNextPortion();
179 if (!pPor
|| pPor
->InFixMargGrp())
182 // next character is inside a field?
183 if ( CH_TXTATR_BREAKWORD
== rInf
.GetChar( nPos
) && pPor
->InExpGrp() )
185 bool bOldOnWin
= rInf
.OnWin();
186 const_cast<SwTextSizeInfo
&>(rInf
).SetOnWin( false );
189 pPor
->GetExpText( rInf
, aStr
);
190 const_cast<SwTextSizeInfo
&>(rInf
).SetOnWin( bOldOnWin
);
192 nNextScript
= static_cast<sal_uInt8
>(g_pBreakIt
->GetBreakIter()->getScriptType( aStr
, 0 ));
195 nNextScript
= static_cast<sal_uInt8
>(
196 g_pBreakIt
->GetBreakIter()->getScriptType(rInf
.GetText(), sal_Int32(nPos
)));
198 if( ASIAN
== nNextScript
)
201 rInf
.GetTextFrame()->GetLangOfChar(nPos
, nNextScript
);
203 if (!MsLangId::isKorean(aLang
))
211 SwTextPortion
* SwTextPortion::CopyLinePortion(const SwLinePortion
&rPortion
)
213 SwTextPortion
*const pNew(new SwTextPortion
);
214 static_cast<SwLinePortion
&>(*pNew
) = rPortion
;
215 pNew
->SetWhichPor( PortionType::Text
); // overwrite that!
219 void SwTextPortion::BreakCut( SwTextFormatInfo
&rInf
, const SwTextGuess
&rGuess
)
221 // The word/char is larger than the line
222 // Special case 1: The word is larger than the line
224 const sal_uInt16 nLineWidth
= o3tl::narrowing
<sal_uInt16
>(rInf
.Width() - rInf
.X());
225 TextFrameIndex nLen
= rGuess
.CutPos() - rInf
.GetIdx();
226 if (nLen
> TextFrameIndex(0))
228 // special case: guess does not always provide the correct
229 // width, only in common cases.
230 if ( !rGuess
.BreakWidth() )
234 CalcTextSize( rInf
);
236 // changing these values requires also changing them in
238 sal_uInt16 nItalic
= 0;
239 if( ITALIC_NONE
!= rInf
.GetFont()->GetItalic() && !rInf
.NotEOL() )
241 nItalic
= Height() / 12;
243 Width( Width() + nItalic
);
247 Width( rGuess
.BreakWidth() );
251 // special case: first character does not fit to line
252 else if ( rGuess
.CutPos() == rInf
.GetLineStart() )
254 SetLen( TextFrameIndex(1) );
259 SetLen( TextFrameIndex(0) );
264 void SwTextPortion::BreakUnderflow( SwTextFormatInfo
&rInf
)
269 SetLen( TextFrameIndex(0) );
271 rInf
.SetUnderflow( this );
274 static bool lcl_HasContent( const SwFieldPortion
& rField
, SwTextFormatInfo
const &rInf
)
277 return rField
.GetExpText( rInf
, aText
) && !aText
.isEmpty();
280 bool SwTextPortion::Format_( SwTextFormatInfo
&rInf
)
282 // 5744: If only the hyphen does not fit anymore, we still need to wrap
283 // the word, or else return true!
284 if( rInf
.IsUnderflow() && rInf
.GetSoftHyphPos() )
286 // soft hyphen portion has triggered an underflow event because
287 // of an alternative spelling position
289 const bool bHyph
= rInf
.ChgHyph( true );
290 if( rInf
.IsHyphenate() )
293 // check for alternative spelling left from the soft hyphen
294 // this should usually be true but
295 aGuess
.AlternativeSpelling(rInf
, rInf
.GetSoftHyphPos() - TextFrameIndex(1));
296 bFull
= CreateHyphen( rInf
, aGuess
);
297 OSL_ENSURE( bFull
, "Problem with hyphenation!!!" );
299 rInf
.ChgHyph( bHyph
);
300 rInf
.SetSoftHyphPos( TextFrameIndex(0) );
305 const bool bFull
= !aGuess
.Guess( *this, rInf
, Height() );
307 // these are the possible cases:
308 // A Portion fits to current line
309 // B Portion does not fit to current line but a possible line break
310 // within the portion has been found by the break iterator, 2 subcases
311 // B1 break is hyphen
312 // B2 break is word end
313 // C Portion does not fit to current line and no possible line break
314 // has been found by break iterator, 2 subcases:
315 // C1 break iterator found a possible line break in portion before us
316 // ==> this break is used (underflow)
317 // C2 break iterator does not found a possible line break at all:
320 // case A: line not yet full
323 Width( aGuess
.BreakWidth() );
324 ExtraBlankWidth(aGuess
.ExtraBlankWidth());
326 if( !InExpGrp() || InFieldGrp() )
327 SetLen( rInf
.GetLen() );
329 short nKern
= rInf
.GetFont()->CheckKerning();
330 if( nKern
> 0 && rInf
.Width() < rInf
.X() + Width() + nKern
)
332 nKern
= static_cast<short>(rInf
.Width() - rInf
.X() - Width() - 1);
337 new SwKernPortion( *this, nKern
);
339 // special case: hanging portion
340 else if( bFull
&& aGuess
.GetHangingPortion() )
342 Width( aGuess
.BreakWidth() );
343 SetLen( aGuess
.BreakPos() - rInf
.GetIdx() );
344 aGuess
.GetHangingPortion()->SetAscent( GetAscent() );
345 Insert( aGuess
.ReleaseHangingPortion() );
348 else if (aGuess
.BreakPos() >= rInf
.GetIdx() && aGuess
.BreakPos() != TextFrameIndex(COMPLETE_STRING
))
351 if( aGuess
.HyphWord().is() && aGuess
.BreakPos() > rInf
.GetLineStart()
352 && ( aGuess
.BreakPos() > rInf
.GetIdx() ||
353 ( rInf
.GetLast() && ! rInf
.GetLast()->IsFlyPortion() ) ) )
355 CreateHyphen( rInf
, aGuess
);
357 rInf
.GetRoot()->SetMidHyph( true );
359 rInf
.GetRoot()->SetEndHyph( true );
362 // - Footnote portions with fake line start (i.e., not at beginning of line)
363 // should keep together with the text portion. (Note: no keep together
364 // with only footnote portions.
365 // - TabPortions not at beginning of line should keep together with the
366 // text portion, if they are not followed by a blank
367 // (work around different definition of tab stop character - breaking or
368 // non breaking character - in compatibility mode)
369 else if ( ( IsFootnotePortion() && rInf
.IsFakeLineStart() &&
371 rInf
.IsOtherThanFootnoteInside() ) ||
373 rInf
.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT
) &&
374 rInf
.GetLast()->InTabGrp() &&
375 rInf
.GetLineStart() + rInf
.GetLast()->GetLen() < rInf
.GetIdx() &&
376 aGuess
.BreakPos() == rInf
.GetIdx() &&
377 CH_BLANK
!= rInf
.GetChar( rInf
.GetIdx() ) &&
378 CH_FULL_BLANK
!= rInf
.GetChar( rInf
.GetIdx() ) &&
379 CH_SIX_PER_EM
!= rInf
.GetChar( rInf
.GetIdx() ) ) )
380 BreakUnderflow( rInf
);
382 else if( rInf
.GetIdx() > rInf
.GetLineStart() ||
383 aGuess
.BreakPos() > rInf
.GetIdx() ||
384 // this is weird: during formatting the follow of a field
385 // the values rInf.GetIdx and rInf.GetLineStart are replaced
386 // IsFakeLineStart indicates GetIdx > GetLineStart
387 rInf
.IsFakeLineStart() ||
389 rInf
.IsFirstMulti() ||
391 ( rInf
.GetLast()->IsFlyPortion() ||
392 ( rInf
.GetLast()->InFieldGrp() &&
393 ! rInf
.GetLast()->InNumberGrp() &&
394 ! rInf
.GetLast()->IsErgoSumPortion() &&
395 lcl_HasContent(*static_cast<SwFieldPortion
*>(rInf
.GetLast()),rInf
) ) ) ) )
397 // GetLineWidth() takes care of DocumentSettingId::TAB_OVER_MARGIN.
398 if (aGuess
.BreakWidth() <= rInf
.GetLineWidth())
399 Width( aGuess
.BreakWidth() );
401 // this actually should not happen
402 Width( sal_uInt16(rInf
.Width() - rInf
.X()) );
404 SetLen( aGuess
.BreakPos() - rInf
.GetIdx() );
406 OSL_ENSURE( aGuess
.BreakStart() >= aGuess
.FieldDiff(),
407 "Trouble with expanded field portions during line break" );
408 TextFrameIndex
const nRealStart
= aGuess
.BreakStart() - aGuess
.FieldDiff();
409 if( aGuess
.BreakPos() < nRealStart
&& !InExpGrp() )
411 SwHolePortion
*pNew
= new SwHolePortion( *this );
412 pNew
->SetLen( nRealStart
- aGuess
.BreakPos() );
414 pNew
->ExtraBlankWidth( aGuess
.ExtraBlankWidth() );
418 else // case C2, last exit
419 BreakCut( rInf
, aGuess
);
421 // breakPos < index or no breakpos at all
424 bool bFirstPor
= rInf
.GetLineStart() == rInf
.GetIdx();
425 if (aGuess
.BreakPos() != TextFrameIndex(COMPLETE_STRING
) &&
426 aGuess
.BreakPos() != rInf
.GetLineStart() &&
427 ( !bFirstPor
|| rInf
.GetFly() || rInf
.GetLast()->IsFlyPortion() ||
428 rInf
.IsFirstMulti() ) &&
429 ( !rInf
.GetLast()->IsBlankPortion() ||
430 SwBlankPortion::MayUnderflow(rInf
, rInf
.GetIdx() - TextFrameIndex(1), true)))
431 { // case C1 (former BreakUnderflow())
432 BreakUnderflow( rInf
);
435 // case C2, last exit
436 BreakCut(rInf
, aGuess
);
442 bool SwTextPortion::Format( SwTextFormatInfo
&rInf
)
444 // GetLineWidth() takes care of DocumentSettingId::TAB_OVER_MARGIN.
445 if( rInf
.GetLineWidth() < 0 || (!GetLen() && !InExpGrp()) )
449 SetLen( TextFrameIndex(0) );
451 SetNextPortion( nullptr ); // ????
455 OSL_ENSURE( rInf
.RealWidth() || (rInf
.X() == rInf
.Width()),
456 "SwTextPortion::Format: missing real width" );
457 OSL_ENSURE( Height(), "SwTextPortion::Format: missing height" );
459 return Format_( rInf
);
462 // Format end of line
463 // 5083: We can have awkward cases e.g.:
465 // Santa wraps, "from " turns into "from" and " " in a justified
466 // paragraph, in which the glue gets expanded instead of merged
467 // with the MarginPortion.
469 // rInf.nIdx points to the next word, nIdx-1 is the portion's last char
470 void SwTextPortion::FormatEOL( SwTextFormatInfo
&rInf
)
472 if( ( GetNextPortion() &&
473 ( !GetNextPortion()->IsKernPortion() || GetNextPortion()->GetNextPortion() ) ) ||
475 rInf
.GetIdx() >= TextFrameIndex(rInf
.GetText().getLength()) ||
476 TextFrameIndex(1) >= rInf
.GetIdx() ||
477 ' ' != rInf
.GetChar(rInf
.GetIdx() - TextFrameIndex(1)) ||
478 rInf
.GetLast()->IsHolePortion() )
481 // calculate number of blanks
482 TextFrameIndex
nX(rInf
.GetIdx() - TextFrameIndex(1));
483 TextFrameIndex
nHoleLen(1);
484 while( nX
&& nHoleLen
< GetLen() && CH_BLANK
== rInf
.GetChar( --nX
) )
487 // First set ourselves and the insert, because there could be
489 sal_uInt16 nBlankSize
;
490 if( nHoleLen
== GetLen() )
491 nBlankSize
= Width();
493 nBlankSize
= sal_Int32(nHoleLen
) * rInf
.GetTextSize(OUString(' ')).Width();
494 Width( Width() - nBlankSize
);
495 rInf
.X( rInf
.X() - nBlankSize
);
496 SetLen( GetLen() - nHoleLen
);
497 SwLinePortion
*pHole
= new SwHolePortion( *this );
498 static_cast<SwHolePortion
*>( pHole
)->SetBlankWidth( nBlankSize
);
499 static_cast<SwHolePortion
*>( pHole
)->SetLen( nHoleLen
);
504 TextFrameIndex
SwTextPortion::GetModelPositionForViewPoint(const sal_uInt16 nOfst
) const
506 OSL_ENSURE( false, "SwTextPortion::GetModelPositionForViewPoint: don't use this method!" );
507 return SwLinePortion::GetModelPositionForViewPoint( nOfst
);
510 // The GetTextSize() assumes that the own length is correct
511 SwPosSize
SwTextPortion::GetTextSize( const SwTextSizeInfo
&rInf
) const
513 SwPosSize aSize
= rInf
.GetTextSize();
514 if( !GetJoinBorderWithPrev() )
515 aSize
.Width(aSize
.Width() + rInf
.GetFont()->GetLeftBorderSpace() );
516 if( !GetJoinBorderWithNext() )
517 aSize
.Width(aSize
.Width() + rInf
.GetFont()->GetRightBorderSpace() );
519 aSize
.Height(aSize
.Height() +
520 rInf
.GetFont()->GetTopBorderSpace() +
521 rInf
.GetFont()->GetBottomBorderSpace() );
526 void SwTextPortion::Paint( const SwTextPaintInfo
&rInf
) const
528 if (rInf
.OnWin() && TextFrameIndex(1) == rInf
.GetLen()
529 && CH_TXT_ATR_FIELDEND
== rInf
.GetText()[sal_Int32(rInf
.GetIdx())])
531 assert(false); // this is some debugging only code
532 rInf
.DrawBackBrush( *this );
533 const OUString
aText(CH_TXT_ATR_SUBST_FIELDEND
);
534 rInf
.DrawText(aText
, *this, TextFrameIndex(0), TextFrameIndex(aText
.getLength()));
536 else if (rInf
.OnWin() && TextFrameIndex(1) == rInf
.GetLen()
537 && CH_TXT_ATR_FIELDSTART
== rInf
.GetText()[sal_Int32(rInf
.GetIdx())])
539 assert(false); // this is some debugging only code
540 rInf
.DrawBackBrush( *this );
541 const OUString
aText(CH_TXT_ATR_SUBST_FIELDSTART
);
542 rInf
.DrawText(aText
, *this, TextFrameIndex(0), TextFrameIndex(aText
.getLength()));
546 rInf
.DrawBackBrush( *this );
547 rInf
.DrawBorder( *this );
549 rInf
.DrawCSDFHighlighting(*this);
551 // do we have to repaint a post it portion?
552 if( rInf
.OnWin() && mpNextPortion
&& !mpNextPortion
->Width() )
553 mpNextPortion
->PrePaint( rInf
, this );
555 auto const* pWrongList
= rInf
.GetpWrongList();
556 auto const* pGrammarCheckList
= rInf
.GetGrammarCheckList();
557 auto const* pSmarttags
= rInf
.GetSmartTags();
559 const bool bWrong
= nullptr != pWrongList
;
560 const bool bGrammarCheck
= nullptr != pGrammarCheckList
;
561 const bool bSmartTags
= nullptr != pSmarttags
;
563 if ( bWrong
|| bSmartTags
|| bGrammarCheck
)
564 rInf
.DrawMarkedText( *this, rInf
.GetLen(), bWrong
, bSmartTags
, bGrammarCheck
);
566 rInf
.DrawText( *this, rInf
.GetLen() );
570 bool SwTextPortion::GetExpText( const SwTextSizeInfo
&, OUString
& ) const
575 // Responsible for the justified paragraph. They calculate the blank
576 // count and the resulting added space.
577 TextFrameIndex
SwTextPortion::GetSpaceCnt(const SwTextSizeInfo
&rInf
,
578 TextFrameIndex
& rCharCnt
) const
580 TextFrameIndex
nCnt(0);
581 TextFrameIndex
nPos(0);
583 if ( rInf
.SnapToGrid() )
585 SwTextGridItem
const*const pGrid(GetGridItem(rInf
.GetTextFrame()->FindPageFrame()));
586 if (pGrid
&& GRID_LINES_CHARS
== pGrid
->GetGridType() && pGrid
->IsSnapToChars())
587 return TextFrameIndex(0);
590 if ( InExpGrp() || PortionType::InputField
== GetWhichPor() )
594 || (GetExpText(rInf
, ExpOut
) && OUStringChar(CH_BLANK
) == ExpOut
))
595 && !InNumberGrp() && !IsCombinedPortion())
597 // OnWin() likes to return a blank instead of an empty string from
598 // time to time. We cannot use that here at all, however.
599 bool bOldOnWin
= rInf
.OnWin();
600 const_cast<SwTextSizeInfo
&>(rInf
).SetOnWin( false );
603 GetExpText( rInf
, aStr
);
604 const_cast<SwTextSizeInfo
&>(rInf
).SetOnWin( bOldOnWin
);
606 nCnt
= nCnt
+ lcl_AddSpace( rInf
, &aStr
, *this );
607 nPos
= TextFrameIndex(aStr
.getLength());
610 else if( !IsDropPortion() )
612 nCnt
= nCnt
+ lcl_AddSpace( rInf
, nullptr, *this );
615 rCharCnt
= rCharCnt
+ nPos
;
619 tools::Long
SwTextPortion::CalcSpacing( tools::Long nSpaceAdd
, const SwTextSizeInfo
&rInf
) const
621 TextFrameIndex
nCnt(0);
623 if ( rInf
.SnapToGrid() )
625 SwTextGridItem
const*const pGrid(GetGridItem(rInf
.GetTextFrame()->FindPageFrame()));
626 if (pGrid
&& GRID_LINES_CHARS
== pGrid
->GetGridType() && pGrid
->IsSnapToChars())
630 if ( InExpGrp() || PortionType::InputField
== GetWhichPor() )
634 || (GetExpText(rInf
, ExpOut
) && OUStringChar(CH_BLANK
) == ExpOut
))
635 && !InNumberGrp() && !IsCombinedPortion())
637 // OnWin() likes to return a blank instead of an empty string from
638 // time to time. We cannot use that here at all, however.
639 bool bOldOnWin
= rInf
.OnWin();
640 const_cast<SwTextSizeInfo
&>(rInf
).SetOnWin( false );
643 GetExpText( rInf
, aStr
);
644 const_cast<SwTextSizeInfo
&>(rInf
).SetOnWin( bOldOnWin
);
646 nCnt
= nCnt
+ lcl_AddSpace( rInf
, &aStr
, *this );
649 nSpaceAdd
= -nSpaceAdd
;
650 nCnt
= TextFrameIndex(aStr
.getLength());
654 else if( !IsDropPortion() )
657 nCnt
= nCnt
+ lcl_AddSpace( rInf
, nullptr, *this );
660 nSpaceAdd
= -nSpaceAdd
;
662 SwLinePortion
* pPor
= GetNextPortion();
664 // we do not want an extra space in front of margin portions
667 while ( pPor
&& !pPor
->Width() && ! pPor
->IsHolePortion() )
668 pPor
= pPor
->GetNextPortion();
670 if ( !pPor
|| pPor
->InFixMargGrp() || pPor
->IsHolePortion() )
676 return sal_Int32(nCnt
) * nSpaceAdd
/ SPACING_PRECISION_FACTOR
;
679 void SwTextPortion::HandlePortion( SwPortionHandler
& rPH
) const
681 rPH
.Text( GetLen(), GetWhichPor() );
684 SwTextInputFieldPortion::SwTextInputFieldPortion()
686 SetWhichPor( PortionType::InputField
);
689 bool SwTextInputFieldPortion::Format(SwTextFormatInfo
&rTextFormatInfo
)
691 return SwTextPortion::Format(rTextFormatInfo
);
694 void SwTextInputFieldPortion::Paint( const SwTextPaintInfo
&rInf
) const
698 rInf
.DrawViewOpt( *this, PortionType::InputField
);
699 SwTextSlot
aPaintText( &rInf
, this, true, true, OUString() );
700 SwTextPortion::Paint( rInf
);
704 // highlight empty input field, elsewhere they are completely invisible for the user
706 rInf
.CalcRect(*this, &aIntersect
);
707 const sal_uInt16 aAreaWidth
= rInf
.GetTextSize(OUString(' ')).Width();
708 aIntersect
.Left(aIntersect
.Left() - aAreaWidth
/2);
709 aIntersect
.Width(aAreaWidth
);
711 if (aIntersect
.HasArea()
713 && rInf
.GetOpt().IsFieldShadings()
714 && !rInf
.GetOpt().IsPagePreview())
716 OutputDevice
* pOut
= const_cast<OutputDevice
*>(rInf
.GetOut());
717 pOut
->Push(vcl::PushFlags::LINECOLOR
| vcl::PushFlags::FILLCOLOR
);
718 pOut
->SetFillColor(rInf
.GetOpt().GetFieldShadingsColor());
719 pOut
->SetLineColor();
720 pOut
->DrawRect(aIntersect
.SVRect());
726 bool SwTextInputFieldPortion::GetExpText( const SwTextSizeInfo
&rInf
, OUString
&rText
) const
728 sal_Int32
nIdx(rInf
.GetIdx());
729 sal_Int32
nLen(GetLen());
730 if ( rInf
.GetChar( rInf
.GetIdx() ) == CH_TXT_ATR_INPUTFIELDSTART
)
735 if (rInf
.GetChar(rInf
.GetIdx() + GetLen() - TextFrameIndex(1)) == CH_TXT_ATR_INPUTFIELDEND
)
739 rText
= rInf
.GetText().copy( nIdx
, std::min( nLen
, rInf
.GetText().getLength() - nIdx
) );
744 SwPosSize
SwTextInputFieldPortion::GetTextSize( const SwTextSizeInfo
&rInf
) const
746 SwTextSlot
aFormatText( &rInf
, this, true, false );
747 if (rInf
.GetLen() == TextFrameIndex(0))
749 return SwPosSize( 0, 0 );
752 return rInf
.GetTextSize();
755 SwHolePortion::SwHolePortion( const SwTextPortion
&rPor
)
758 SetLen( TextFrameIndex(1) );
759 Height( rPor
.Height() );
761 SetAscent( rPor
.GetAscent() );
762 SetWhichPor( PortionType::Hole
);
765 SwLinePortion
*SwHolePortion::Compress() { return this; }
767 // The GetTextSize() assumes that the own length is correct
768 SwPosSize
SwHolePortion::GetTextSize(const SwTextSizeInfo
& rInf
) const
770 SwPosSize aSize
= rInf
.GetTextSize();
771 if (!GetJoinBorderWithPrev())
772 aSize
.Width(aSize
.Width() + rInf
.GetFont()->GetLeftBorderSpace());
773 if (!GetJoinBorderWithNext())
774 aSize
.Width(aSize
.Width() + rInf
.GetFont()->GetRightBorderSpace());
776 aSize
.Height(aSize
.Height() +
777 rInf
.GetFont()->GetTopBorderSpace() +
778 rInf
.GetFont()->GetBottomBorderSpace());
783 void SwHolePortion::Paint( const SwTextPaintInfo
&rInf
) const
788 bool bPDFExport
= rInf
.GetVsh()->GetViewOptions()->IsPDFExport();
790 // #i16816# export stuff only needed for tagged pdf support
791 if (bPDFExport
&& !SwTaggedPDFHelper::IsExportTaggedPDF( *rInf
.GetOut()) )
794 // #i68503# the hole must have no decoration for a consistent visual appearance
795 const SwFont
* pOrigFont
= rInf
.GetFont();
796 std::unique_ptr
<SwFont
> pHoleFont
;
797 std::optional
<SwFontSave
> oFontSave
;
798 if( pOrigFont
->GetUnderline() != LINESTYLE_NONE
799 || pOrigFont
->GetOverline() != LINESTYLE_NONE
800 || pOrigFont
->GetStrikeout() != STRIKEOUT_NONE
)
802 pHoleFont
.reset(new SwFont( *pOrigFont
));
803 pHoleFont
->SetUnderline( LINESTYLE_NONE
);
804 pHoleFont
->SetOverline( LINESTYLE_NONE
);
805 pHoleFont
->SetStrikeout( STRIKEOUT_NONE
);
806 oFontSave
.emplace( rInf
, pHoleFont
.get() );
811 rInf
.DrawText(" ", *this, TextFrameIndex(0), TextFrameIndex(1));
815 // tdf#43244: Paint spaces even at end of line,
816 // but only if this paint is not called for pdf export, to keep that pdf export intact
817 rInf
.DrawText(*this, rInf
.GetLen());
824 bool SwHolePortion::Format( SwTextFormatInfo
&rInf
)
826 return rInf
.IsFull() || rInf
.X() >= rInf
.Width();
829 void SwHolePortion::HandlePortion( SwPortionHandler
& rPH
) const
831 rPH
.Text( GetLen(), GetWhichPor() );
834 void SwHolePortion::dumpAsXml(xmlTextWriterPtr pWriter
, const OUString
& rText
, TextFrameIndex
& nOffset
) const
836 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("SwHolePortion"));
837 dumpAsXmlAttributes(pWriter
, rText
, nOffset
);
840 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("blank-width"),
841 BAD_CAST(OString::number(m_nBlankWidth
).getStr()));
843 (void)xmlTextWriterEndElement(pWriter
);
846 void SwFieldMarkPortion::Paint( const SwTextPaintInfo
& /*rInf*/) const
848 // These shouldn't be painted!
849 //SwTextPortion::Paint(rInf);
852 bool SwFieldMarkPortion::Format( SwTextFormatInfo
& )
858 void SwFieldFormCheckboxPortion::Paint( const SwTextPaintInfo
& rInf
) const
860 SwPosition
const aPosition(rInf
.GetTextFrame()->MapViewToModelPos(rInf
.GetIdx()));
862 IFieldmark
const*const pBM
= rInf
.GetTextFrame()->GetDoc().getIDocumentMarkAccess()->getFieldmarkAt(aPosition
);
864 OSL_ENSURE(pBM
&& pBM
->GetFieldname( ) == ODF_FORMCHECKBOX
,
865 "Where is my form field bookmark???");
867 if (pBM
&& pBM
->GetFieldname( ) == ODF_FORMCHECKBOX
)
869 const ICheckboxFieldmark
* pCheckboxFm
= dynamic_cast<ICheckboxFieldmark
const*>(pBM
);
870 bool bChecked
= pCheckboxFm
&& pCheckboxFm
->IsChecked();
871 rInf
.DrawCheckBox(*this, bChecked
);
875 bool SwFieldFormCheckboxPortion::Format( SwTextFormatInfo
& rInf
)
877 SwPosition
const aPosition(rInf
.GetTextFrame()->MapViewToModelPos(rInf
.GetIdx()));
878 IFieldmark
const*const pBM
= rInf
.GetTextFrame()->GetDoc().getIDocumentMarkAccess()->getFieldmarkAt(aPosition
);
879 OSL_ENSURE(pBM
&& pBM
->GetFieldname( ) == ODF_FORMCHECKBOX
, "Where is my form field bookmark???");
880 if (pBM
&& pBM
->GetFieldname( ) == ODF_FORMCHECKBOX
)
882 // the width of the checkbox portion is the same as its height since it's a square
883 // and that size depends on the font size.
885 // http://document-foundation-mail-archive.969070.n3.nabble.com/Wrong-copy-paste-in-SwFieldFormCheckboxPortion-Format-td4269112.html
886 Width( rInf
.GetTextHeight( ) );
887 Height( rInf
.GetTextHeight( ) );
888 SetAscent( rInf
.GetAscent( ) );
893 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */