summaryrefslogtreecommitdiffstats
path: root/sw/source/core/text/portxt.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sw/source/core/text/portxt.cxx')
-rw-r--r--sw/source/core/text/portxt.cxx925
1 files changed, 925 insertions, 0 deletions
diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx
new file mode 100644
index 0000000000..85691ef21e
--- /dev/null
+++ b/sw/source/core/text/portxt.cxx
@@ -0,0 +1,925 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <i18nlangtag/mslangid.hxx>
+#include <breakit.hxx>
+#include <hintids.hxx>
+#include <EnhancedPDFExportHelper.hxx>
+#include <SwPortionHandler.hxx>
+#include "porlay.hxx"
+#include "inftxt.hxx"
+#include "guess.hxx"
+#include "porfld.hxx"
+#include <pagefrm.hxx>
+#include <tgrditem.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <IDocumentMarkAccess.hxx>
+
+#include <IMark.hxx>
+#include <pam.hxx>
+#include <doc.hxx>
+#include <xmloff/odffields.hxx>
+#include <viewopt.hxx>
+
+using namespace ::sw::mark;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::i18n::ScriptType;
+
+// Returns for how many characters an extra space has to be added
+// (for justified alignment).
+static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo &rInf,
+ const OUString* pStr, const SwLinePortion& rPor)
+{
+ TextFrameIndex nPos, nEnd;
+ const SwScriptInfo* pSI = nullptr;
+
+ if ( pStr )
+ {
+ // passing a string means we are inside a field
+ nPos = TextFrameIndex(0);
+ nEnd = TextFrameIndex(pStr->getLength());
+ }
+ else
+ {
+ nPos = rInf.GetIdx();
+ nEnd = rInf.GetIdx() + rPor.GetLen();
+ pStr = &rInf.GetText();
+ pSI = &const_cast<SwParaPortion*>(rInf.GetParaPortion())->GetScriptInfo();
+ }
+
+ TextFrameIndex nCnt(0);
+ sal_uInt8 nScript = 0;
+
+ // If portion consists of Asian characters and language is not
+ // Korean, we add extra space to each character.
+ // first we get the script type
+ if ( pSI )
+ nScript = pSI->ScriptType( nPos );
+ else
+ nScript = static_cast<sal_uInt8>(
+ g_pBreakIt->GetBreakIter()->getScriptType(*pStr, sal_Int32(nPos)));
+
+ // Note: rInf.GetIdx() can differ from nPos,
+ // e.g., when rPor is a field portion. nPos refers to the string passed
+ // to the function, rInf.GetIdx() refers to the original string.
+
+ // We try to find out which justification mode is required. This is done by
+ // evaluating the script type and the language attribute set for this portion
+
+ // Asian Justification: Each character get some extra space
+ if ( nEnd > nPos && ASIAN == nScript )
+ {
+ LanguageType aLang =
+ rInf.GetTextFrame()->GetLangOfChar(rInf.GetIdx(), nScript);
+
+ if (!MsLangId::isKorean(aLang))
+ {
+ const SwLinePortion* pPor = rPor.GetNextPortion();
+ if ( pPor && ( pPor->IsKernPortion() ||
+ pPor->IsControlCharPortion() ||
+ pPor->IsPostItsPortion() ) )
+ pPor = pPor->GetNextPortion();
+
+ nCnt += SwScriptInfo::CountCJKCharacters( *pStr, nPos, nEnd, aLang );
+
+ if ( !pPor || pPor->IsHolePortion() || pPor->InFixMargGrp() ||
+ pPor->IsBreakPortion() )
+ --nCnt;
+
+ return nCnt;
+ }
+ }
+
+ // Kashida Justification: Insert Kashidas
+ if ( nEnd > nPos && pSI && COMPLEX == nScript )
+ {
+ if ( SwScriptInfo::IsArabicText( *pStr, nPos, nEnd - nPos ) && pSI->CountKashida() )
+ {
+ const sal_Int32 nKashRes = pSI->KashidaJustify(nullptr, nullptr, nPos, nEnd - nPos);
+ // i60591: need to check result of KashidaJustify
+ // determine if kashida justification is applicable
+ if (nKashRes != -1)
+ return TextFrameIndex(nKashRes);
+ }
+ }
+
+ // Thai Justification: Each character cell gets some extra space
+ if ( nEnd > nPos && COMPLEX == nScript )
+ {
+ LanguageType aLang =
+ rInf.GetTextFrame()->GetLangOfChar(rInf.GetIdx(), nScript);
+
+ if ( LANGUAGE_THAI == aLang )
+ {
+ nCnt = SwScriptInfo::ThaiJustify(*pStr, nullptr, nPos, nEnd - nPos);
+
+ const SwLinePortion* pPor = rPor.GetNextPortion();
+ if ( pPor && ( pPor->IsKernPortion() ||
+ pPor->IsControlCharPortion() ||
+ pPor->IsPostItsPortion() ) )
+ pPor = pPor->GetNextPortion();
+
+ if ( nCnt && ( ! pPor || pPor->IsHolePortion() || pPor->InFixMargGrp() ) )
+ --nCnt;
+
+ return nCnt;
+ }
+ }
+
+ // Here starts the good old "Look for blanks and add space to them" part.
+ // Note: We do not want to add space to an isolated latin blank in front
+ // of some complex characters in RTL environment
+ const bool bDoNotAddSpace =
+ LATIN == nScript && (nEnd == nPos + TextFrameIndex(1)) && pSI &&
+ ( i18n::ScriptType::COMPLEX ==
+ pSI->ScriptType(nPos + TextFrameIndex(1))) &&
+ rInf.GetTextFrame() && rInf.GetTextFrame()->IsRightToLeft();
+
+ if ( bDoNotAddSpace )
+ return nCnt;
+
+ TextFrameIndex nTextEnd = std::min(nEnd, TextFrameIndex(pStr->getLength()));
+ for ( ; nPos < nTextEnd; ++nPos )
+ {
+ if (CH_BLANK == (*pStr)[ sal_Int32(nPos) ])
+ ++nCnt;
+ }
+
+ // We still have to examine the next character:
+ // If the next character is ASIAN and not KOREAN we have
+ // to add an extra space
+ // nPos refers to the original string, even if a field string has
+ // been passed to this function
+ nPos = rInf.GetIdx() + rPor.GetLen();
+ if (nPos < TextFrameIndex(rInf.GetText().getLength()))
+ {
+ sal_uInt8 nNextScript = 0;
+ const SwLinePortion* pPor = rPor.GetNextPortion();
+ if ( pPor && pPor->IsKernPortion() )
+ pPor = pPor->GetNextPortion();
+
+ if (!pPor || pPor->InFixMargGrp())
+ return nCnt;
+
+ // next character is inside a field?
+ if ( CH_TXTATR_BREAKWORD == rInf.GetChar( nPos ) && pPor->InExpGrp() )
+ {
+ bool bOldOnWin = rInf.OnWin();
+ const_cast<SwTextSizeInfo &>(rInf).SetOnWin( false );
+
+ OUString aStr;
+ pPor->GetExpText( rInf, aStr );
+ const_cast<SwTextSizeInfo &>(rInf).SetOnWin( bOldOnWin );
+
+ nNextScript = static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType( aStr, 0 ));
+ }
+ else
+ nNextScript = static_cast<sal_uInt8>(
+ g_pBreakIt->GetBreakIter()->getScriptType(rInf.GetText(), sal_Int32(nPos)));
+
+ if( ASIAN == nNextScript )
+ {
+ LanguageType aLang =
+ rInf.GetTextFrame()->GetLangOfChar(nPos, nNextScript);
+
+ if (!MsLangId::isKorean(aLang))
+ ++nCnt;
+ }
+ }
+
+ return nCnt;
+}
+
+SwTextPortion * SwTextPortion::CopyLinePortion(const SwLinePortion &rPortion)
+{
+ SwTextPortion *const pNew(new SwTextPortion);
+ static_cast<SwLinePortion&>(*pNew) = rPortion;
+ pNew->SetWhichPor( PortionType::Text ); // overwrite that!
+ return pNew;
+}
+
+void SwTextPortion::BreakCut( SwTextFormatInfo &rInf, const SwTextGuess &rGuess )
+{
+ // The word/char is larger than the line
+ // Special case 1: The word is larger than the line
+ // We truncate ...
+ const sal_uInt16 nLineWidth = o3tl::narrowing<sal_uInt16>(rInf.Width() - rInf.X());
+ TextFrameIndex nLen = rGuess.CutPos() - rInf.GetIdx();
+ if (nLen > TextFrameIndex(0))
+ {
+ // special case: guess does not always provide the correct
+ // width, only in common cases.
+ if ( !rGuess.BreakWidth() )
+ {
+ rInf.SetLen( nLen );
+ SetLen( nLen );
+ CalcTextSize( rInf );
+
+ // changing these values requires also changing them in
+ // guess.cxx
+ sal_uInt16 nItalic = 0;
+ if( ITALIC_NONE != rInf.GetFont()->GetItalic() && !rInf.NotEOL() )
+ {
+ nItalic = Height() / 12;
+ }
+ Width( Width() + nItalic );
+ }
+ else
+ {
+ Width( rGuess.BreakWidth() );
+ SetLen( nLen );
+ }
+ }
+ // special case: first character does not fit to line
+ else if ( rGuess.CutPos() == rInf.GetLineStart() )
+ {
+ SetLen( TextFrameIndex(1) );
+ Width( nLineWidth );
+ }
+ else
+ {
+ SetLen( TextFrameIndex(0) );
+ Width( 0 );
+ }
+}
+
+void SwTextPortion::BreakUnderflow( SwTextFormatInfo &rInf )
+{
+ Truncate();
+ Height( 0 );
+ Width( 0 );
+ SetLen( TextFrameIndex(0) );
+ SetAscent( 0 );
+ rInf.SetUnderflow( this );
+}
+
+static bool lcl_HasContent( const SwFieldPortion& rField, SwTextFormatInfo const &rInf )
+{
+ OUString aText;
+ return rField.GetExpText( rInf, aText ) && !aText.isEmpty();
+}
+
+bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
+{
+ // 5744: If only the hyphen does not fit anymore, we still need to wrap
+ // the word, or else return true!
+ if( rInf.IsUnderflow() && rInf.GetSoftHyphPos() )
+ {
+ // soft hyphen portion has triggered an underflow event because
+ // of an alternative spelling position
+ bool bFull = false;
+ const bool bHyph = rInf.ChgHyph( true );
+ if( rInf.IsHyphenate() )
+ {
+ SwTextGuess aGuess;
+ // check for alternative spelling left from the soft hyphen
+ // this should usually be true but
+ aGuess.AlternativeSpelling(rInf, rInf.GetSoftHyphPos() - TextFrameIndex(1));
+ bFull = CreateHyphen( rInf, aGuess );
+ OSL_ENSURE( bFull, "Problem with hyphenation!!!" );
+ }
+ rInf.ChgHyph( bHyph );
+ rInf.SetSoftHyphPos( TextFrameIndex(0) );
+ return bFull;
+ }
+
+ SwTextGuess aGuess;
+ bool bFull = !aGuess.Guess( *this, rInf, Height() );
+
+ // tdf#158776 for the last full text portion, call Guess() again to allow more text in the
+ // adjusted line by shrinking spaces using the know space count from the first Guess() call
+ const SvxAdjust& rAdjust = rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust().GetAdjust();
+ if ( bFull && rAdjust == SvxAdjust::Block &&
+ aGuess.BreakPos() != TextFrameIndex(COMPLETE_STRING) &&
+ rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
+ DocumentSettingId::JUSTIFY_LINES_WITH_SHRINKING) &&
+ // tdf#158436 avoid shrinking at underflow, e.g. no-break space after a
+ // very short word resulted endless loop
+ !rInf.IsUnderflow() )
+ {
+ sal_Int32 nSpacesInLine(0);
+ for (sal_Int32 i = sal_Int32(rInf.GetLineStart()); i < sal_Int32(aGuess.BreakPos()); ++i)
+ {
+ sal_Unicode cChar = rInf.GetText()[i];
+ if ( cChar == CH_BLANK )
+ ++nSpacesInLine;
+ }
+
+ // call with an extra space: shrinking can result a new word in the line
+ // and a new space before that, which is also a shrank space
+ // (except if the line was already broken at a soft hyphen, i.e. inside a word)
+ if ( aGuess.BreakPos() < TextFrameIndex(rInf.GetText().getLength()) &&
+ rInf.GetText()[sal_Int32(aGuess.BreakPos())] != CHAR_SOFTHYPHEN )
+ {
+ ++nSpacesInLine;
+ }
+
+ bFull = !aGuess.Guess( *this, rInf, Height(), nSpacesInLine );
+ }
+
+ // these are the possible cases:
+ // A Portion fits to current line
+ // B Portion does not fit to current line but a possible line break
+ // within the portion has been found by the break iterator, 2 subcases
+ // B1 break is hyphen
+ // B2 break is word end
+ // C Portion does not fit to current line and no possible line break
+ // has been found by break iterator, 2 subcases:
+ // C1 break iterator found a possible line break in portion before us
+ // ==> this break is used (underflow)
+ // C2 break iterator does not found a possible line break at all:
+ // ==> line break
+
+ // case A: line not yet full
+ if ( !bFull )
+ {
+ Width( aGuess.BreakWidth() );
+ ExtraBlankWidth(aGuess.ExtraBlankWidth());
+ // Caution!
+ if( !InExpGrp() || InFieldGrp() )
+ SetLen( rInf.GetLen() );
+
+ short nKern = rInf.GetFont()->CheckKerning();
+ if( nKern > 0 && rInf.Width() < rInf.X() + Width() + nKern )
+ {
+ nKern = static_cast<short>(rInf.Width() - rInf.X() - Width() - 1);
+ if( nKern < 0 )
+ nKern = 0;
+ }
+ if( nKern )
+ new SwKernPortion( *this, nKern );
+ }
+ // special case: hanging portion
+ else if( aGuess.GetHangingPortion() )
+ {
+ Width( aGuess.BreakWidth() );
+ SetLen( aGuess.BreakPos() - rInf.GetIdx() );
+ aGuess.GetHangingPortion()->SetAscent( GetAscent() );
+ Insert( aGuess.ReleaseHangingPortion() );
+ }
+ // breakPos >= index
+ else if (aGuess.BreakPos() >= rInf.GetIdx() && aGuess.BreakPos() != TextFrameIndex(COMPLETE_STRING))
+ {
+ // case B1
+ if( aGuess.HyphWord().is() && aGuess.BreakPos() > rInf.GetLineStart()
+ && ( aGuess.BreakPos() > rInf.GetIdx() ||
+ ( rInf.GetLast() && ! rInf.GetLast()->IsFlyPortion() ) ) )
+ {
+ CreateHyphen( rInf, aGuess );
+ if ( rInf.GetFly() )
+ rInf.GetRoot()->SetMidHyph( true );
+ else
+ rInf.GetRoot()->SetEndHyph( true );
+ }
+ // case C1
+ // - Footnote portions with fake line start (i.e., not at beginning of line)
+ // should keep together with the text portion. (Note: no keep together
+ // with only footnote portions.
+ // - TabPortions not at beginning of line should keep together with the
+ // text portion, if they are not followed by a blank
+ // (work around different definition of tab stop character - breaking or
+ // non breaking character - in compatibility mode)
+ else if ( ( IsFootnotePortion() && rInf.IsFakeLineStart() &&
+
+ rInf.IsOtherThanFootnoteInside() ) ||
+ ( rInf.GetLast() &&
+ rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT) &&
+ rInf.GetLast()->InTabGrp() &&
+ rInf.GetLineStart() + rInf.GetLast()->GetLen() < rInf.GetIdx() &&
+ aGuess.BreakPos() == rInf.GetIdx() &&
+ CH_BLANK != rInf.GetChar( rInf.GetIdx() ) &&
+ CH_FULL_BLANK != rInf.GetChar( rInf.GetIdx() ) &&
+ CH_SIX_PER_EM != rInf.GetChar( rInf.GetIdx() ) ) )
+ BreakUnderflow( rInf );
+ // case B2
+ else if( rInf.GetIdx() > rInf.GetLineStart() ||
+ aGuess.BreakPos() > rInf.GetIdx() ||
+ // this is weird: during formatting the follow of a field
+ // the values rInf.GetIdx and rInf.GetLineStart are replaced
+ // IsFakeLineStart indicates GetIdx > GetLineStart
+ rInf.IsFakeLineStart() ||
+ rInf.GetFly() ||
+ rInf.IsFirstMulti() ||
+ ( rInf.GetLast() &&
+ ( rInf.GetLast()->IsFlyPortion() ||
+ ( rInf.GetLast()->InFieldGrp() &&
+ ! rInf.GetLast()->InNumberGrp() &&
+ ! rInf.GetLast()->IsErgoSumPortion() &&
+ lcl_HasContent(*static_cast<SwFieldPortion*>(rInf.GetLast()),rInf ) ) ) ) )
+ {
+ Width( aGuess.BreakWidth() );
+
+ SetLen( aGuess.BreakPos() - rInf.GetIdx() );
+
+ OSL_ENSURE( aGuess.BreakStart() >= aGuess.FieldDiff(),
+ "Trouble with expanded field portions during line break" );
+ TextFrameIndex const nRealStart = aGuess.BreakStart() - aGuess.FieldDiff();
+ if( aGuess.BreakPos() < nRealStart && !InExpGrp() )
+ {
+ SwHolePortion *pNew = new SwHolePortion( *this );
+ pNew->SetLen( nRealStart - aGuess.BreakPos() );
+ pNew->Width(0);
+ pNew->ExtraBlankWidth( aGuess.ExtraBlankWidth() );
+ Insert( pNew );
+
+ // UAX #14 Unicode Line Breaking Algorithm Non-tailorable Line breaking rule LB6:
+ // https://www.unicode.org/reports/tr14/#LB6 Do not break before hard line breaks
+ if (auto ch = rInf.GetChar(aGuess.BreakStart()); !ch || ch == CH_BREAK)
+ bFull = false; // Keep following SwBreakPortion / para break in the same line
+ }
+ }
+ else // case C2, last exit
+ BreakCut( rInf, aGuess );
+ }
+ // breakPos < index or no breakpos at all
+ else
+ {
+ bool bFirstPor = rInf.GetLineStart() == rInf.GetIdx();
+ if (aGuess.BreakPos() != TextFrameIndex(COMPLETE_STRING) &&
+ aGuess.BreakPos() != rInf.GetLineStart() &&
+ ( !bFirstPor || rInf.GetFly() || rInf.GetLast()->IsFlyPortion() ||
+ rInf.IsFirstMulti() ) &&
+ ( !rInf.GetLast()->IsBlankPortion() ||
+ SwBlankPortion::MayUnderflow(rInf, rInf.GetIdx() - TextFrameIndex(1), true)))
+ { // case C1 (former BreakUnderflow())
+ BreakUnderflow( rInf );
+ }
+ else
+ // case C2, last exit
+ BreakCut(rInf, aGuess);
+ }
+
+ return bFull;
+}
+
+bool SwTextPortion::Format( SwTextFormatInfo &rInf )
+{
+ // GetLineWidth() takes care of DocumentSettingId::TAB_OVER_MARGIN.
+ if( rInf.GetLineWidth() < 0 || (!GetLen() && !InExpGrp()) )
+ {
+ Height( 0 );
+ Width( 0 );
+ SetLen( TextFrameIndex(0) );
+ SetAscent( 0 );
+ SetNextPortion( nullptr ); // ????
+ return true;
+ }
+
+ OSL_ENSURE( rInf.RealWidth() || (rInf.X() == rInf.Width()),
+ "SwTextPortion::Format: missing real width" );
+ OSL_ENSURE( Height(), "SwTextPortion::Format: missing height" );
+
+ return Format_( rInf );
+}
+
+// Format end of line
+// 5083: We can have awkward cases e.g.:
+// "from {Santa}"
+// Santa wraps, "from " turns into "from" and " " in a justified
+// paragraph, in which the glue gets expanded instead of merged
+// with the MarginPortion.
+
+// rInf.nIdx points to the next word, nIdx-1 is the portion's last char
+void SwTextPortion::FormatEOL( SwTextFormatInfo &rInf )
+{
+ if( ( GetNextPortion() &&
+ ( !GetNextPortion()->IsKernPortion() || GetNextPortion()->GetNextPortion() ) ) ||
+ !GetLen() ||
+ rInf.GetIdx() >= TextFrameIndex(rInf.GetText().getLength()) ||
+ TextFrameIndex(1) >= rInf.GetIdx() ||
+ ' ' != rInf.GetChar(rInf.GetIdx() - TextFrameIndex(1)) ||
+ rInf.GetLast()->IsHolePortion() )
+ return;
+
+ // calculate number of blanks
+ TextFrameIndex nX(rInf.GetIdx() - TextFrameIndex(1));
+ TextFrameIndex nHoleLen(1);
+ while( nX && nHoleLen < GetLen() && CH_BLANK == rInf.GetChar( --nX ) )
+ nHoleLen++;
+
+ // First set ourselves and the insert, because there could be
+ // a SwLineLayout
+ sal_uInt16 nBlankSize;
+ if( nHoleLen == GetLen() )
+ nBlankSize = Width();
+ else
+ nBlankSize = sal_Int32(nHoleLen) * rInf.GetTextSize(OUString(' ')).Width();
+ Width( Width() - nBlankSize );
+ rInf.X( rInf.X() - nBlankSize );
+ SetLen( GetLen() - nHoleLen );
+ SwLinePortion *pHole = new SwHolePortion( *this );
+ static_cast<SwHolePortion *>( pHole )->SetBlankWidth( nBlankSize );
+ static_cast<SwHolePortion *>( pHole )->SetLen( nHoleLen );
+ Insert( pHole );
+
+}
+
+TextFrameIndex SwTextPortion::GetModelPositionForViewPoint(const sal_uInt16 nOfst) const
+{
+ OSL_ENSURE( false, "SwTextPortion::GetModelPositionForViewPoint: don't use this method!" );
+ return SwLinePortion::GetModelPositionForViewPoint( nOfst );
+}
+
+// The GetTextSize() assumes that the own length is correct
+SwPosSize SwTextPortion::GetTextSize( const SwTextSizeInfo &rInf ) const
+{
+ SwPosSize aSize = rInf.GetTextSize();
+ if( !GetJoinBorderWithPrev() )
+ aSize.Width(aSize.Width() + rInf.GetFont()->GetLeftBorderSpace() );
+ if( !GetJoinBorderWithNext() )
+ aSize.Width(aSize.Width() + rInf.GetFont()->GetRightBorderSpace() );
+
+ aSize.Height(aSize.Height() +
+ rInf.GetFont()->GetTopBorderSpace() +
+ rInf.GetFont()->GetBottomBorderSpace() );
+
+ return aSize;
+}
+
+void SwTextPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if (rInf.OnWin() && TextFrameIndex(1) == rInf.GetLen()
+ && CH_TXT_ATR_FIELDEND == rInf.GetText()[sal_Int32(rInf.GetIdx())])
+ {
+ assert(false); // this is some debugging only code
+ rInf.DrawBackBrush( *this );
+ const OUString aText(CH_TXT_ATR_SUBST_FIELDEND);
+ rInf.DrawText(aText, *this, TextFrameIndex(0), TextFrameIndex(aText.getLength()));
+ }
+ else if (rInf.OnWin() && TextFrameIndex(1) == rInf.GetLen()
+ && CH_TXT_ATR_FIELDSTART == rInf.GetText()[sal_Int32(rInf.GetIdx())])
+ {
+ assert(false); // this is some debugging only code
+ rInf.DrawBackBrush( *this );
+ const OUString aText(CH_TXT_ATR_SUBST_FIELDSTART);
+ rInf.DrawText(aText, *this, TextFrameIndex(0), TextFrameIndex(aText.getLength()));
+ }
+ else if( GetLen() )
+ {
+ rInf.DrawBackBrush( *this );
+ rInf.DrawBorder( *this );
+
+ rInf.DrawCSDFHighlighting(*this);
+
+ // do we have to repaint a post it portion?
+ if( rInf.OnWin() && mpNextPortion && !mpNextPortion->Width() )
+ mpNextPortion->PrePaint( rInf, this );
+
+ auto const* pWrongList = rInf.GetpWrongList();
+ auto const* pGrammarCheckList = rInf.GetGrammarCheckList();
+ auto const* pSmarttags = rInf.GetSmartTags();
+
+ const bool bWrong = nullptr != pWrongList;
+ const bool bGrammarCheck = nullptr != pGrammarCheckList;
+ const bool bSmartTags = nullptr != pSmarttags;
+
+ if ( bWrong || bSmartTags || bGrammarCheck )
+ rInf.DrawMarkedText( *this, rInf.GetLen(), bWrong, bSmartTags, bGrammarCheck );
+ else
+ rInf.DrawText( *this, rInf.GetLen() );
+ }
+}
+
+bool SwTextPortion::GetExpText( const SwTextSizeInfo &, OUString & ) const
+{
+ return false;
+}
+
+// Responsible for the justified paragraph. They calculate the blank
+// count and the resulting added space.
+TextFrameIndex SwTextPortion::GetSpaceCnt(const SwTextSizeInfo &rInf,
+ TextFrameIndex& rCharCnt) const
+{
+ TextFrameIndex nCnt(0);
+ TextFrameIndex nPos(0);
+
+ if ( rInf.SnapToGrid() )
+ {
+ SwTextGridItem const*const pGrid(GetGridItem(rInf.GetTextFrame()->FindPageFrame()));
+ if (pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && pGrid->IsSnapToChars())
+ return TextFrameIndex(0);
+ }
+
+ if ( InExpGrp() || PortionType::InputField == GetWhichPor() )
+ {
+ if (OUString ExpOut;
+ (!IsBlankPortion()
+ || (GetExpText(rInf, ExpOut) && OUStringChar(CH_BLANK) == ExpOut))
+ && !InNumberGrp() && !IsCombinedPortion())
+ {
+ // OnWin() likes to return a blank instead of an empty string from
+ // time to time. We cannot use that here at all, however.
+ bool bOldOnWin = rInf.OnWin();
+ const_cast<SwTextSizeInfo &>(rInf).SetOnWin( false );
+
+ OUString aStr;
+ GetExpText( rInf, aStr );
+ const_cast<SwTextSizeInfo &>(rInf).SetOnWin( bOldOnWin );
+
+ nCnt = nCnt + lcl_AddSpace( rInf, &aStr, *this );
+ nPos = TextFrameIndex(aStr.getLength());
+ }
+ }
+ else if( !IsDropPortion() )
+ {
+ nCnt = nCnt + lcl_AddSpace( rInf, nullptr, *this );
+ nPos = GetLen();
+ }
+ rCharCnt = rCharCnt + nPos;
+ return nCnt;
+}
+
+SwTwips SwTextPortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) const
+{
+ TextFrameIndex nCnt(0);
+
+ if ( rInf.SnapToGrid() )
+ {
+ SwTextGridItem const*const pGrid(GetGridItem(rInf.GetTextFrame()->FindPageFrame()));
+ if (pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && pGrid->IsSnapToChars())
+ return 0;
+ }
+
+ if ( InExpGrp() || PortionType::InputField == GetWhichPor() )
+ {
+ if (OUString ExpOut;
+ (!IsBlankPortion()
+ || (GetExpText(rInf, ExpOut) && OUStringChar(CH_BLANK) == ExpOut))
+ && !InNumberGrp() && !IsCombinedPortion())
+ {
+ // OnWin() likes to return a blank instead of an empty string from
+ // time to time. We cannot use that here at all, however.
+ bool bOldOnWin = rInf.OnWin();
+ const_cast<SwTextSizeInfo &>(rInf).SetOnWin( false );
+
+ OUString aStr;
+ GetExpText( rInf, aStr );
+ const_cast<SwTextSizeInfo &>(rInf).SetOnWin( bOldOnWin );
+ if( nSpaceAdd > 0 )
+ nCnt = nCnt + lcl_AddSpace( rInf, &aStr, *this );
+ else
+ {
+ nSpaceAdd = -nSpaceAdd;
+ nCnt = TextFrameIndex(aStr.getLength());
+ }
+ }
+ }
+ else if( !IsDropPortion() )
+ {
+ if( nSpaceAdd > 0 )
+ nCnt = nCnt + lcl_AddSpace( rInf, nullptr, *this );
+ else
+ {
+ nSpaceAdd = -nSpaceAdd;
+ nCnt = GetLen();
+ SwLinePortion* pPor = GetNextPortion();
+
+ // we do not want an extra space in front of margin portions
+ if ( nCnt )
+ {
+ while ( pPor && !pPor->Width() && ! pPor->IsHolePortion() )
+ pPor = pPor->GetNextPortion();
+
+ if ( !pPor || pPor->InFixMargGrp() || pPor->IsHolePortion() )
+ --nCnt;
+ }
+ }
+ }
+
+ return sal_Int32(nCnt) * (nSpaceAdd > LONG_MAX/2 ? LONG_MAX/2 - nSpaceAdd : nSpaceAdd)
+ / SPACING_PRECISION_FACTOR;
+}
+
+void SwTextPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Text( GetLen(), GetWhichPor() );
+}
+
+SwTextInputFieldPortion::SwTextInputFieldPortion()
+{
+ SetWhichPor( PortionType::InputField );
+}
+
+bool SwTextInputFieldPortion::Format(SwTextFormatInfo &rTextFormatInfo)
+{
+ return SwTextPortion::Format(rTextFormatInfo);
+}
+
+void SwTextInputFieldPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if ( Width() )
+ {
+ rInf.DrawViewOpt( *this, PortionType::InputField );
+ SwTextSlot aPaintText( &rInf, this, true, true, OUString() );
+ SwTextPortion::Paint( rInf );
+ }
+ else
+ {
+ // highlight empty input field, elsewhere they are completely invisible for the user
+ SwRect aIntersect;
+ rInf.CalcRect(*this, &aIntersect);
+ const sal_uInt16 aAreaWidth = rInf.GetTextSize(OUString(' ')).Width();
+ aIntersect.Left(aIntersect.Left() - aAreaWidth/2);
+ aIntersect.Width(aAreaWidth);
+
+ if (aIntersect.HasArea()
+ && rInf.OnWin()
+ && rInf.GetOpt().IsFieldShadings()
+ && !rInf.GetOpt().IsPagePreview())
+ {
+ OutputDevice* pOut = const_cast<OutputDevice*>(rInf.GetOut());
+ pOut->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR);
+ pOut->SetFillColor(rInf.GetOpt().GetFieldShadingsColor());
+ pOut->SetLineColor();
+ pOut->DrawRect(aIntersect.SVRect());
+ pOut->Pop();
+ }
+ }
+}
+
+bool SwTextInputFieldPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const
+{
+ sal_Int32 nIdx(rInf.GetIdx());
+ sal_Int32 nLen(GetLen());
+ if ( rInf.GetChar( rInf.GetIdx() ) == CH_TXT_ATR_INPUTFIELDSTART )
+ {
+ ++nIdx;
+ --nLen;
+ }
+ if (rInf.GetChar(rInf.GetIdx() + GetLen() - TextFrameIndex(1)) == CH_TXT_ATR_INPUTFIELDEND)
+ {
+ --nLen;
+ }
+ rText = rInf.GetText().copy( nIdx, std::min( nLen, rInf.GetText().getLength() - nIdx ) );
+
+ return true;
+}
+
+SwPosSize SwTextInputFieldPortion::GetTextSize( const SwTextSizeInfo &rInf ) const
+{
+ SwTextSlot aFormatText( &rInf, this, true, false );
+ if (rInf.GetLen() == TextFrameIndex(0))
+ {
+ return SwPosSize( 0, 0 );
+ }
+
+ return rInf.GetTextSize();
+}
+
+SwHolePortion::SwHolePortion( const SwTextPortion &rPor )
+ : m_nBlankWidth( 0 )
+{
+ SetLen( TextFrameIndex(1) );
+ Height( rPor.Height() );
+ Width(0);
+ SetAscent( rPor.GetAscent() );
+ SetWhichPor( PortionType::Hole );
+}
+
+SwLinePortion *SwHolePortion::Compress() { return this; }
+
+// The GetTextSize() assumes that the own length is correct
+SwPosSize SwHolePortion::GetTextSize(const SwTextSizeInfo& rInf) const
+{
+ SwPosSize aSize = rInf.GetTextSize();
+ if (!GetJoinBorderWithPrev())
+ aSize.Width(aSize.Width() + rInf.GetFont()->GetLeftBorderSpace());
+ if (!GetJoinBorderWithNext())
+ aSize.Width(aSize.Width() + rInf.GetFont()->GetRightBorderSpace());
+
+ aSize.Height(aSize.Height() +
+ rInf.GetFont()->GetTopBorderSpace() +
+ rInf.GetFont()->GetBottomBorderSpace());
+
+ return aSize;
+}
+
+void SwHolePortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if( !rInf.GetOut() )
+ return;
+
+ bool bPDFExport = rInf.GetVsh()->GetViewOptions()->IsPDFExport();
+
+ // #i16816# export stuff only needed for tagged pdf support
+ if (bPDFExport && !SwTaggedPDFHelper::IsExportTaggedPDF( *rInf.GetOut()) )
+ return;
+
+ // #i68503# the hole must have no decoration for a consistent visual appearance
+ const SwFont* pOrigFont = rInf.GetFont();
+ std::unique_ptr<SwFont> pHoleFont;
+ std::optional<SwFontSave> oFontSave;
+ if( pOrigFont->GetUnderline() != LINESTYLE_NONE
+ || pOrigFont->GetOverline() != LINESTYLE_NONE
+ || pOrigFont->GetStrikeout() != STRIKEOUT_NONE )
+ {
+ pHoleFont.reset(new SwFont( *pOrigFont ));
+ pHoleFont->SetUnderline( LINESTYLE_NONE );
+ pHoleFont->SetOverline( LINESTYLE_NONE );
+ pHoleFont->SetStrikeout( STRIKEOUT_NONE );
+ oFontSave.emplace( rInf, pHoleFont.get() );
+ }
+
+ if (bPDFExport)
+ {
+ rInf.DrawText(" ", *this, TextFrameIndex(0), TextFrameIndex(1));
+ }
+ else
+ {
+ // tdf#43244: Paint spaces even at end of line,
+ // but only if this paint is not called for pdf export, to keep that pdf export intact
+ rInf.DrawText(*this, rInf.GetLen());
+ }
+
+ oFontSave.reset();
+ pHoleFont.reset();
+}
+
+bool SwHolePortion::Format( SwTextFormatInfo &rInf )
+{
+ return rInf.IsFull() || rInf.X() >= rInf.Width();
+}
+
+void SwHolePortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Text( GetLen(), GetWhichPor() );
+}
+
+void SwHolePortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, TextFrameIndex& nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwHolePortion"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ nOffset += GetLen();
+
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("blank-width"),
+ BAD_CAST(OString::number(m_nBlankWidth).getStr()));
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+void SwFieldMarkPortion::Paint( const SwTextPaintInfo & /*rInf*/) const
+{
+ // These shouldn't be painted!
+ //SwTextPortion::Paint(rInf);
+}
+
+bool SwFieldMarkPortion::Format( SwTextFormatInfo & )
+{
+ Width(0);
+ return false;
+}
+
+void SwFieldFormCheckboxPortion::Paint( const SwTextPaintInfo& rInf ) const
+{
+ SwPosition const aPosition(rInf.GetTextFrame()->MapViewToModelPos(rInf.GetIdx()));
+
+ IFieldmark const*const pBM = rInf.GetTextFrame()->GetDoc().getIDocumentMarkAccess()->getFieldmarkAt(aPosition);
+
+ OSL_ENSURE(pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX,
+ "Where is my form field bookmark???");
+
+ if (pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX)
+ {
+ const ICheckboxFieldmark* pCheckboxFm = dynamic_cast<ICheckboxFieldmark const*>(pBM);
+ bool bChecked = pCheckboxFm && pCheckboxFm->IsChecked();
+ rInf.DrawCheckBox(*this, bChecked);
+ }
+}
+
+bool SwFieldFormCheckboxPortion::Format( SwTextFormatInfo & rInf )
+{
+ SwPosition const aPosition(rInf.GetTextFrame()->MapViewToModelPos(rInf.GetIdx()));
+ IFieldmark const*const pBM = rInf.GetTextFrame()->GetDoc().getIDocumentMarkAccess()->getFieldmarkAt(aPosition);
+ OSL_ENSURE(pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX, "Where is my form field bookmark???");
+ if (pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX)
+ {
+ // the width of the checkbox portion is the same as its height since it's a square
+ // and that size depends on the font size.
+ // See:
+ // http://document-foundation-mail-archive.969070.n3.nabble.com/Wrong-copy-paste-in-SwFieldFormCheckboxPortion-Format-td4269112.html
+ Width( rInf.GetTextHeight( ) );
+ Height( rInf.GetTextHeight( ) );
+ SetAscent( rInf.GetAscent( ) );
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */