android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / source / core / text / portxt.cxx
blob99595c1a71f7e0353593457fe1638172322bcaf1
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 // 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;
54 if ( pStr )
56 // passing a string means we are inside a field
57 nPos = TextFrameIndex(0);
58 nEnd = TextFrameIndex(pStr->getLength());
60 else
62 nPos = rInf.GetIdx();
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
74 if ( pSI )
75 nScript = pSI->ScriptType( nPos );
76 else
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 )
90 LanguageType aLang =
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() )
105 --nCnt;
107 return nCnt;
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
119 if (nKashRes != -1)
120 return TextFrameIndex(nKashRes);
124 // Thai Justification: Each character cell gets some extra space
125 if ( nEnd > nPos && COMPLEX == nScript )
127 LanguageType aLang =
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() ) )
141 --nCnt;
143 return nCnt;
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 )
157 return nCnt;
159 TextFrameIndex nTextEnd = std::min(nEnd, TextFrameIndex(pStr->getLength()));
160 for ( ; nPos < nTextEnd; ++nPos )
162 if (CH_BLANK == (*pStr)[ sal_Int32(nPos) ])
163 ++nCnt;
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())
180 return nCnt;
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 );
188 OUString aStr;
189 pPor->GetExpText( rInf, aStr );
190 const_cast<SwTextSizeInfo &>(rInf).SetOnWin( bOldOnWin );
192 nNextScript = static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType( aStr, 0 ));
194 else
195 nNextScript = static_cast<sal_uInt8>(
196 g_pBreakIt->GetBreakIter()->getScriptType(rInf.GetText(), sal_Int32(nPos)));
198 if( ASIAN == nNextScript )
200 LanguageType aLang =
201 rInf.GetTextFrame()->GetLangOfChar(nPos, nNextScript);
203 if (!MsLangId::isKorean(aLang))
204 ++nCnt;
208 return nCnt;
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!
216 return pNew;
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
223 // We truncate ...
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() )
232 rInf.SetLen( nLen );
233 SetLen( nLen );
234 CalcTextSize( rInf );
236 // changing these values requires also changing them in
237 // guess.cxx
238 sal_uInt16 nItalic = 0;
239 if( ITALIC_NONE != rInf.GetFont()->GetItalic() && !rInf.NotEOL() )
241 nItalic = Height() / 12;
243 Width( Width() + nItalic );
245 else
247 Width( rGuess.BreakWidth() );
248 SetLen( nLen );
251 // special case: first character does not fit to line
252 else if ( rGuess.CutPos() == rInf.GetLineStart() )
254 SetLen( TextFrameIndex(1) );
255 Width( nLineWidth );
257 else
259 SetLen( TextFrameIndex(0) );
260 Width( 0 );
264 void SwTextPortion::BreakUnderflow( SwTextFormatInfo &rInf )
266 Truncate();
267 Height( 0 );
268 Width( 0 );
269 SetLen( TextFrameIndex(0) );
270 SetAscent( 0 );
271 rInf.SetUnderflow( this );
274 static bool lcl_HasContent( const SwFieldPortion& rField, SwTextFormatInfo const &rInf )
276 OUString aText;
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
288 bool bFull = false;
289 const bool bHyph = rInf.ChgHyph( true );
290 if( rInf.IsHyphenate() )
292 SwTextGuess aGuess;
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) );
301 return bFull;
304 SwTextGuess aGuess;
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:
318 // ==> line break
320 // case A: line not yet full
321 if ( !bFull )
323 Width( aGuess.BreakWidth() );
324 ExtraBlankWidth(aGuess.ExtraBlankWidth());
325 // Caution!
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);
333 if( nKern < 0 )
334 nKern = 0;
336 if( nKern )
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() );
347 // breakPos >= index
348 else if (aGuess.BreakPos() >= rInf.GetIdx() && aGuess.BreakPos() != TextFrameIndex(COMPLETE_STRING))
350 // case B1
351 if( aGuess.HyphWord().is() && aGuess.BreakPos() > rInf.GetLineStart()
352 && ( aGuess.BreakPos() > rInf.GetIdx() ||
353 ( rInf.GetLast() && ! rInf.GetLast()->IsFlyPortion() ) ) )
355 CreateHyphen( rInf, aGuess );
356 if ( rInf.GetFly() )
357 rInf.GetRoot()->SetMidHyph( true );
358 else
359 rInf.GetRoot()->SetEndHyph( true );
361 // case C1
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() ) ||
372 ( rInf.GetLast() &&
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 );
381 // case B2
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() ||
388 rInf.GetFly() ||
389 rInf.IsFirstMulti() ||
390 ( rInf.GetLast() &&
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() );
400 else
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() );
413 pNew->Width(0);
414 pNew->ExtraBlankWidth( aGuess.ExtraBlankWidth() );
415 Insert( pNew );
418 else // case C2, last exit
419 BreakCut( rInf, aGuess );
421 // breakPos < index or no breakpos at all
422 else
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 );
434 else
435 // case C2, last exit
436 BreakCut(rInf, aGuess);
439 return bFull;
442 bool SwTextPortion::Format( SwTextFormatInfo &rInf )
444 // GetLineWidth() takes care of DocumentSettingId::TAB_OVER_MARGIN.
445 if( rInf.GetLineWidth() < 0 || (!GetLen() && !InExpGrp()) )
447 Height( 0 );
448 Width( 0 );
449 SetLen( TextFrameIndex(0) );
450 SetAscent( 0 );
451 SetNextPortion( nullptr ); // ????
452 return true;
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.:
464 // "from {Santa}"
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() ) ) ||
474 !GetLen() ||
475 rInf.GetIdx() >= TextFrameIndex(rInf.GetText().getLength()) ||
476 TextFrameIndex(1) >= rInf.GetIdx() ||
477 ' ' != rInf.GetChar(rInf.GetIdx() - TextFrameIndex(1)) ||
478 rInf.GetLast()->IsHolePortion() )
479 return;
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 ) )
485 nHoleLen++;
487 // First set ourselves and the insert, because there could be
488 // a SwLineLayout
489 sal_uInt16 nBlankSize;
490 if( nHoleLen == GetLen() )
491 nBlankSize = Width();
492 else
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 );
500 Insert( pHole );
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() );
523 return aSize;
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()));
544 else if( GetLen() )
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 );
565 else
566 rInf.DrawText( *this, rInf.GetLen() );
570 bool SwTextPortion::GetExpText( const SwTextSizeInfo &, OUString & ) const
572 return false;
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() )
592 if (OUString ExpOut;
593 (!IsBlankPortion()
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 );
602 OUString aStr;
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 );
613 nPos = GetLen();
615 rCharCnt = rCharCnt + nPos;
616 return nCnt;
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())
627 return 0;
630 if ( InExpGrp() || PortionType::InputField == GetWhichPor() )
632 if (OUString ExpOut;
633 (!IsBlankPortion()
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 );
642 OUString aStr;
643 GetExpText( rInf, aStr );
644 const_cast<SwTextSizeInfo &>(rInf).SetOnWin( bOldOnWin );
645 if( nSpaceAdd > 0 )
646 nCnt = nCnt + lcl_AddSpace( rInf, &aStr, *this );
647 else
649 nSpaceAdd = -nSpaceAdd;
650 nCnt = TextFrameIndex(aStr.getLength());
654 else if( !IsDropPortion() )
656 if( nSpaceAdd > 0 )
657 nCnt = nCnt + lcl_AddSpace( rInf, nullptr, *this );
658 else
660 nSpaceAdd = -nSpaceAdd;
661 nCnt = GetLen();
662 SwLinePortion* pPor = GetNextPortion();
664 // we do not want an extra space in front of margin portions
665 if ( nCnt )
667 while ( pPor && !pPor->Width() && ! pPor->IsHolePortion() )
668 pPor = pPor->GetNextPortion();
670 if ( !pPor || pPor->InFixMargGrp() || pPor->IsHolePortion() )
671 --nCnt;
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
696 if ( Width() )
698 rInf.DrawViewOpt( *this, PortionType::InputField );
699 SwTextSlot aPaintText( &rInf, this, true, true, OUString() );
700 SwTextPortion::Paint( rInf );
702 else
704 // highlight empty input field, elsewhere they are completely invisible for the user
705 SwRect aIntersect;
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()
712 && rInf.OnWin()
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());
721 pOut->Pop();
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 )
732 ++nIdx;
733 --nLen;
735 if (rInf.GetChar(rInf.GetIdx() + GetLen() - TextFrameIndex(1)) == CH_TXT_ATR_INPUTFIELDEND)
737 --nLen;
739 rText = rInf.GetText().copy( nIdx, std::min( nLen, rInf.GetText().getLength() - nIdx ) );
741 return true;
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 )
756 : m_nBlankWidth( 0 )
758 SetLen( TextFrameIndex(1) );
759 Height( rPor.Height() );
760 Width(0);
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());
780 return aSize;
783 void SwHolePortion::Paint( const SwTextPaintInfo &rInf ) const
785 if( !rInf.GetOut() )
786 return;
788 bool bPDFExport = rInf.GetVsh()->GetViewOptions()->IsPDFExport();
790 // #i16816# export stuff only needed for tagged pdf support
791 if (bPDFExport && !SwTaggedPDFHelper::IsExportTaggedPDF( *rInf.GetOut()) )
792 return;
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() );
809 if (bPDFExport)
811 rInf.DrawText(" ", *this, TextFrameIndex(0), TextFrameIndex(1));
813 else
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());
820 oFontSave.reset();
821 pHoleFont.reset();
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);
838 nOffset += GetLen();
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 & )
854 Width(0);
855 return false;
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.
884 // See:
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( ) );
890 return false;
893 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */