/* -*- 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;
static TextFrameIndex lcl_AddSpace_Latin(const SwTextSizeInfo& rInf, const OUString* pStr,
const SwLinePortion& rPor, TextFrameIndex nPos,
TextFrameIndex nEnd, const SwScriptInfo* pSI,
sal_uInt8 nScript);
// 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::IsKashidaScriptText(*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)
{
// tdf#163105: For kashida justification, also expand whitespace.
auto nCntLatin = lcl_AddSpace_Latin(rInf, pStr, rPor, nPos, nEnd, pSI, nScript);
return nCntLatin + 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;
}
}
return lcl_AddSpace_Latin(rInf, pStr, rPor, nPos, nEnd, pSI, nScript);
}
static TextFrameIndex lcl_AddSpace_Latin(const SwTextSizeInfo& rInf, const OUString* pStr,
const SwLinePortion& rPor, TextFrameIndex nPos,
TextFrameIndex nEnd, const SwScriptInfo* pSI,
sal_uInt8 nScript)
{
TextFrameIndex nCnt(0);
// 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 SwTwips nLineWidth = 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
SwTwips 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 );
ExtraShrunkWidth( 0 );
}
}
void SwTextPortion::BreakUnderflow( SwTextFormatInfo &rInf )
{
Truncate();
Height( 0 );
Width( 0 );
ExtraShrunkWidth( 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;
}
ExtraShrunkWidth( 0 );
std::unique_ptr<SwTextGuess> pGuess(new SwTextGuess());
bool bFull = !pGuess->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 aAdjust = rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust().GetAdjust();
if ( bFull && aAdjust == SvxAdjust::Block &&
pGuess->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(pGuess->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 inside a word with hyphenation)
// TODO: handle the case, if the line contains extra amount of spaces
if (
// no automatic hyphenation
!pGuess->HyphWord().is() &&
// no hyphenation at soft hyphen
pGuess->BreakPos() < TextFrameIndex(rInf.GetText().getLength()) &&
rInf.GetText()[sal_Int32(pGuess->BreakPos())] != CHAR_SOFTHYPHEN )
{
++nSpacesInLine;
}
if ( nSpacesInLine > 0 )
{
SwTwips nOldWidth = pGuess->BreakWidth();
pGuess.reset(new SwTextGuess());
bFull = !pGuess->Guess( *this, rInf, Height(), nSpacesInLine );
if ( pGuess->BreakWidth() > nOldWidth )
ExtraShrunkWidth( pGuess->BreakWidth() );
}
}
// 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( pGuess->BreakWidth() );
ExtraBlankWidth(pGuess->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( pGuess->GetHangingPortion() )
{
Width( pGuess->BreakWidth() );
SetLen( pGuess->BreakPos() - rInf.GetIdx() );
pGuess->GetHangingPortion()->SetAscent( GetAscent() );
Insert( pGuess->ReleaseHangingPortion() );
}
// breakPos >= index
else if (pGuess->BreakPos() >= rInf.GetIdx() && pGuess->BreakPos() != TextFrameIndex(COMPLETE_STRING))
{
// case B1
if( pGuess->HyphWord().is() && pGuess->BreakPos() > rInf.GetLineStart()
&& ( pGuess->BreakPos() > rInf.GetIdx() ||
( rInf.GetLast() && ! rInf.GetLast()->IsFlyPortion() ) ) )
{
CreateHyphen( rInf, *pGuess );
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() &&
pGuess->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() ||
pGuess->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( pGuess->BreakWidth() );
SetLen( pGuess->BreakPos() - rInf.GetIdx() );
// Clamp layout context to the end of the line
if(auto stClampedContext = GetLayoutContext(); stClampedContext.has_value()) {
stClampedContext->m_nEnd = pGuess->BreakPos().get();
SetLayoutContext(stClampedContext);
}
OSL_ENSURE( pGuess->BreakStart() >= pGuess->FieldDiff(),
"Trouble with expanded field portions during line break" );
TextFrameIndex const nRealStart = pGuess->BreakStart() - pGuess->FieldDiff();
if( pGuess->BreakPos() < nRealStart && !InExpGrp() )
{
SwHolePortion *pNew = new SwHolePortion( *this );
pNew->SetLen( nRealStart - pGuess->BreakPos() );
pNew->Width(0);
pNew->ExtraBlankWidth( pGuess->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(pGuess->BreakStart()); !ch || ch == CH_BREAK)
bFull = false; // Keep following SwBreakPortion / para break in the same line
}
}
else // case C2, last exit
BreakCut( rInf, *pGuess );
}
// breakPos < index or no breakpos at all
else
{
bool bFirstPor = rInf.GetLineStart() == rInf.GetIdx();
if (pGuess->BreakPos() != TextFrameIndex(COMPLETE_STRING) &&
pGuess->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, *pGuess);
}
return bFull;
}
bool SwTextPortion::Format( SwTextFormatInfo &rInf )
{
// GetLineWidth() takes care of DocumentSettingId::TAB_OVER_MARGIN.
if( rInf.GetLineWidth() + rInf.GetExtraSpace() < 0 || (!GetLen() && !InExpGrp()) )
{
Height( 0 );
Width( 0 );
ExtraShrunkWidth( 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
SwTwips 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 );
SwHolePortion* pHole = new SwHolePortion(*this);
pHole->SetBlankWidth(nBlankSize);
pHole->SetLen(nHoleLen);
Insert( pHole );
}
TextFrameIndex SwTextPortion::GetModelPositionForViewPoint(const SwTwips 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(GetLayoutContext());
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 SwTwips 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(u" "_ustr, *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()));
Fieldmark 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 CheckboxFieldmark* pCheckboxFm = dynamic_cast<CheckboxFieldmark const*>(pBM);
bool bChecked = pCheckboxFm && pCheckboxFm->IsChecked();
rInf.DrawCheckBox(*this, bChecked);
}
}
bool SwFieldFormCheckboxPortion::Format( SwTextFormatInfo & rInf )
{
SwPosition const aPosition(rInf.GetTextFrame()->MapViewToModelPos(rInf.GetIdx()));
Fieldmark 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: */
↑ V560 A part of conditional expression is always true: GetNextPortion()->GetNextPortion().