/* -*- 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 .
*/
SwFieldSlot::~SwFieldSlot()
{ if( bOn )
{
pInf->SetCachedVclData(m_pOldCachedVclData);
pInf->SetText( *pOldText ); // ofz#64109 at last for ruby-text when we restore the original text to // continue laying out the 'body' text of the ruby, then a tab or other // 'hook char' in the text drawn above it shouldn't affect the 'body' // While there are other cases, such as tdf#148360, where the tab in an // inline expanded field, that should affect the body. if (pInf->IsRuby())
pInf->SetHookChar(nOrigHookChar);
pInf->SetIdx( nIdx );
pInf->SetLen( nLen );
pInf->SetFakeLineStart( false );
}
}
// #i89825# change the script type also to CTL // if there is no strong LTR char in the LTR run (numbers) if (nCurrDir != UBIDI_RTL &&
(UBIDI_LTR != nFieldDir || i18n::ScriptType::COMPLEX == nScript))
{
nCurrDir = UBIDI_RTL; for( sal_Int32 nCharIdx = 0; nCharIdx < nEnd; ++nCharIdx )
{
UCharDirection nCharDir = u_charDirection ( aText[ nCharIdx ]); if ( nCharDir == U_LEFT_TO_RIGHT ||
nCharDir == U_LEFT_TO_RIGHT_EMBEDDING ||
nCharDir == U_LEFT_TO_RIGHT_OVERRIDE )
{
nCurrDir = UBIDI_LTR; break;
}
}
}
if (nCurrDir == UBIDI_RTL)
{
nTmp = SwFontScript::CTL; // If we decided that this range was RTL after all and the // previous range was complex but clipped to the start of this // range, then extend it to be complex over the additional RTL range if (nScript == i18n::ScriptType::COMPLEX)
m_nNextScriptChg = nNextDirChg;
}
}
// #i98418# // keep determined script type for footnote portions as preferred script type. // For footnote portions a font can not be created directly - see footnote // portion format method. if ( IsFootnotePortion() )
{ static_cast<SwFootnotePortion*>(this)->SetPreferredScriptType( nTmp );
} elseif ( nTmp != nActual )
{ if( !m_pFont )
m_pFont.reset( new SwFont( *rInf.GetFont() ) );
m_pFont->SetActual( nTmp );
}
// Field portion has to be split in several parts if // 1. There are script/direction changes inside the field // 2. There are portion breaks (tab, break) inside the field: const TextFrameIndex nOldFullLen = rInf.GetLen();
TextFrameIndex nFullLen = rInf.ScanPortionEnd(rInf.GetIdx(), rInf.GetIdx() + nOldFullLen) - rInf.GetIdx(); if ( m_nNextScriptChg < nFullLen )
{
nFullLen = m_nNextScriptChg;
rInf.SetHookChar( 0 );
}
rInf.SetLen( nFullLen );
// Length must be 0: the length is set for bFull after format // and passed along in nRest. Or else the old length would be // retained and be used for nRest!
SetLen(TextFrameIndex(0));
TextFrameIndex const nFollow(IsFollow() ? TextFrameIndex(0) : m_nFieldLen);
// As odd is may seem: the query for GetLen() must return false due // to the ExpandPortions _after_ aDiffText (see SoftHyphs), caused // by SetFull. if( !nFullLen )
{ // Don't Init(), as we need height and ascent
Width(0);
bFull = rInf.Width() <= rInf.GetPos().X();
} else
{
TextFrameIndex const nOldLineStart = rInf.GetLineStart(); if( IsFollow() )
rInf.SetLineStart(TextFrameIndex(0));
rInf.SetNotEOL( nFullLen == nOldFullLen && nTextRest > nFollow );
// the height depending on the fields font is set, // this is required for SwTextGuess::Guess
Height( rInf.GetTextHeight() + rInf.GetFont()->GetTopBorderSpace() +
rInf.GetFont()->GetBottomBorderSpace() ); // If a kerning portion is inserted after our field portion, // the ascent and height must be known
SetAscent( rInf.GetAscent() + rInf.GetFont()->GetTopBorderSpace() );
bFull = SwTextPortion::Format( rInf );
rInf.SetNotEOL( false );
rInf.SetLineStart( nOldLineStart );
}
TextFrameIndex const nTmpLen = GetLen();
bEOL = !nTmpLen && nFollow && bFull;
nRest = nOldFullLen - nTmpLen;
// The char is held in the first position // Unconditionally after format!
SetLen( m_bNoLength ? TextFrameIndex(0) : nFollow );
if( nRest )
{ // aExpand has not yet been shortened; the new Ofst is a // result of nRest
TextFrameIndex nNextOfst = TextFrameIndex(m_aExpand.getLength()) - nRest;
if ( IsQuoVadisPortion() )
nNextOfst = nNextOfst + TextFrameIndex(static_cast<SwQuoVadisPortion*>(this)->GetContText().getLength());
// These characters should not be contained in the follow // field portion. They are handled via the HookChar mechanism. const sal_Unicode nNew = !aNew.isEmpty() ? aNew[0] : 0; auto IsHook = [](const sal_Unicode cNew, boolconst isSpace = false) -> bool
{ switch (cNew)
{ case' ': // tdf#159101 this one is not in ScanPortionEnd // but is required for justified text return isSpace; case CH_BREAK: case CH_TAB: case CHAR_HARDHYPHEN: // non-breaking hyphen case CHAR_SOFTHYPHEN: case CHAR_HARDBLANK: case CHAR_ZWSP: case CHAR_WJ: case CH_TXTATR_BREAKWORD: case CH_TXTATR_INWORD:
{ returntrue;
} default: returnfalse;
}
}; if (IsHook(nNew, true))
{ if (nNew == CH_BREAK)
{
bFull = true;
}
aNew = aNew.copy(1);
++nNextOfst;
}
// Even if there is no more text left for a follow field, // we have to build a follow field portion (without font), // otherwise the HookChar mechanism would not work.
SwFieldPortion *pField = Clone( aNew ); if( !aNew.isEmpty() && !pField->GetFont() )
{
pField->SetFont( std::make_unique<SwFont>( *rInf.GetFont() ) );
} if (IsFollow() || Compress())
{ // empty this will be deleted in SwLineLayout::CalcLine() // anyway so make sure pField doesn't have a stale flag
pField->SetFollow( true );
} if (pField->Compress() && !std::all_of(std::u16string_view(aNew).begin(),
std::u16string_view(aNew).end(), IsHook))
{ // empty pField will be deleted in SwLineLayout::CalcLine() // anyway so make sure this one doesn't have a stale flag
SetHasFollow( true );
}
// tdf#166044: QuoVadis portions with a continuation notice rely on // m_bHasFollow to indicate which portion is responsible for printing // the destination page number. Initially, this is the otherwise-empty // pField. Sometimes SwLineLayout::CalcLine() will cut this extra // field, but that isn't always possible. Set m_bHasFollow on this // portion to avoid printing the page number more than once. if (IsQuoVadisPortion())
{
SetHasFollow(true);
}
// For a newly created field, nNextOffset contains the Offset // of its start of the original string // If a FollowField is created when formatting, this FollowField's // Offset is being held in nNextOffset
m_nNextOffset = m_nNextOffset + nNextOfst;
pField->SetNextOffset( m_nNextOffset );
rInf.SetRest( pField );
}
}
// OSL_ENSURE(GetLen() <= TextFrameIndex(1), "SwFieldPortion::Paint: rest-portion pollution?"); if (Width() && !m_bContentControl)
{ // A very liberal use of the background
rInf.DrawViewOpt( *this, PortionType::Field );
SwExpandPortion::Paint( rInf );
}
}
/** * We can create multiple NumFields * Tricky, if one enters enough previous-text in the dialog box * to cause the line to overflow * We need to keep the Fly's evasion tactics in mind
*/ bool SwNumberPortion::Format( SwTextFormatInfo &rInf )
{
SetHide( false ); constbool bFull = SwFieldPortion::Format( rInf );
SetLen(TextFrameIndex(0)); // a numbering portion can be contained in a rotated portion!!!
m_nFixWidth = rInf.IsMulti() ? Height() : Width();
rInf.SetNumDone( !rInf.GetRest() ); if( rInf.IsNumDone() )
{ // SetAscent( rInf.GetAscent() );
OSL_ENSURE( Height() && mnAscent, "NumberPortions without Height | Ascent" );
tools::Long nDiff( 0 );
if ( !mbLabelAlignmentPosAndSpaceModeActive )
{ if ((!rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) && // #i32902#
!IsFootnoteNumPortion()) || // tdf#159382
(IsFootnoteNumPortion() &&
rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::NO_GAP_AFTER_NOTE_NUMBER)))
{
nDiff = rInf.Left()
+ rInf.GetTextFrame()
->GetTextNodeForParaProps()
->GetSwAttrSet()
.GetFirstLineIndent()
.ResolveTextFirstLineOffset({})
- rInf.First() + rInf.ForcedLeftMargin();
} else
{
nDiff = rInf.Left() - rInf.First() + rInf.ForcedLeftMargin();
}
} // The text part of the numbering should always at least // start at the left margin if( nDiff < 0 )
nDiff = 0; elseif ( nDiff > rInf.X() )
nDiff -= rInf.X(); else
nDiff = 0;
// Numbering evades the Fly, no nDiff in the second round // Tricky special case: FlyFrame is in an Area we're just about to // acquire // The NumberPortion is marked as hidden constbool bFly = rInf.GetFly() ||
( rInf.GetLast() && rInf.GetLast()->IsFlyPortion() ); if( nDiff > rInf.Width() )
{
nDiff = rInf.Width(); if ( bFly )
SetHide( true );
}
// A numbering portion can be inside a SwRotatedPortion. Then the // Height has to be changed if ( rInf.IsMulti() )
{ if ( Height() < nDiff )
Height( nDiff );
} elseif( Width() < nDiff )
Width( nDiff );
} return bFull;
}
/** * A FormatEOL indicates that the subsequent text did not fit onto * the line anymore. In order for the Numbering to follow through, * we hide this NumberPortion
*/ void SwNumberPortion::FormatEOL( SwTextFormatInfo& )
{
// This caused trouble with flys anchored as characters. // If one of these is numbered but does not fit to the line, // it calls this function, causing a loop because both the number // portion and the fly portion go to the next line // SetHide( true );
}
/** * A hidden NumberPortion is not displayed, unless there are TextPortions in * this line or there's just one line at all
*/ void SwNumberPortion::Paint( const SwTextPaintInfo &rInf ) const
{ if ( IsHide() && rInf.GetParaPortion() && rInf.GetParaPortion()->GetNext() )
{
SwLinePortion *pTmp = GetNextPortion(); while ( pTmp && !pTmp->InTextGrp() )
pTmp = pTmp->GetNextPortion(); if ( !pTmp ) return;
}
// calculate the width of the number portion, including follows const SwTwips nOldWidth = Width();
SwTwips nSumWidth = 0;
SwTwips nOffset = 0;
// The master portion takes care for painting the background of the // follow field portions if ( ! IsFollow() )
{
SwNumberPortion *pThis = const_cast<SwNumberPortion*>(this);
pThis->Width( nSumWidth );
rInf.DrawViewOpt( *this, PortionType::Number );
pThis->Width( nOldWidth );
}
// Numbering evades Fly, no nDiff in the second round // Tricky special case: FlyFrame is in the Area we were just // about to get a hold of. // The NumberPortion is marked as hidden if( nDiff > rInf.Width() )
{
nDiff = rInf.Width(); if( bFly )
SetHide( true );
}
void SwTextFrame::StopAnimation( const OutputDevice* pOut )
{
OSL_ENSURE( HasAnimation(), "SwTextFrame::StopAnimation: Which Animation?" ); if( !HasPara() ) return;
SwLineLayout *pLine = GetPara(); while( pLine )
{
SwLinePortion *pPor = pLine->GetNextPortion(); while( pPor )
{ if( pPor->IsGrfNumPortion() ) static_cast<SwGrfNumPortion*>(pPor)->StopAnimation( pOut ); // The NumberPortion is always at the first char, // which means we can cancel as soon as we've reached a portion // with a length > 0
pPor = pPor->GetLen() ? nullptr : pPor->GetNextPortion();
}
pLine = pLine->GetLen() ? nullptr : pLine->GetNext();
}
}
// Initialization of the scripttype array, // the arrays of width and position are filled by the format function
assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
SwFontScript nScr = SW_SCRIPTS; for( sal_Int32 i = 0; i < rText.getLength(); ++i )
{ switch ( g_pBreakIt->GetBreakIter()->getScriptType( rText, i ) ) { case i18n::ScriptType::LATIN : nScr = SwFontScript::Latin; break; case i18n::ScriptType::ASIAN : nScr = SwFontScript::CJK; break; case i18n::ScriptType::COMPLEX : nScr = SwFontScript::CTL; break;
}
m_aScrType[i] = nScr;
}
}
// the first character of the second row const sal_Int32 nTop = ( nCount + 1 ) / 2;
SwFont aTmpFont( *rInf.GetFont() );
aTmpFont.SetProportion( m_nProportion ); // a smaller font
SwFontSave aFontSave( rInf, &aTmpFont );
Point aOldPos = rInf.GetPos();
Point aOutPos( aOldPos.X(), aOldPos.Y() - m_nUpPos );// Y of the first row for( sal_Int32 i = 0 ; i < nCount; ++i )
{ if( i == nTop ) // change the row
aOutPos.setY( aOldPos.Y() + m_nLowPos ); // Y of the second row
aOutPos.setX( aOldPos.X() + m_aPos[i] ); // X position const SwFontScript nAct = m_aScrType[i]; // script type
aTmpFont.SetActual( nAct );
// if there're more than 4 characters to display, we choose fonts // with 2/3 of the original font width. if( m_aWidth[ nAct ] )
{
Size aTmpSz = aTmpFont.GetSize( nAct ); if( aTmpSz.Width() != m_aWidth[ nAct ] )
{
aTmpSz.setWidth( m_aWidth[ nAct ] );
aTmpFont.SetSize( aTmpSz, nAct );
}
} const_cast<SwTextPaintInfo&>(rInf).SetPos( aOutPos );
rInf.DrawText(m_aExpand, *this, TextFrameIndex(i), TextFrameIndex(1));
} // rInf is const, so we have to take back our manipulations const_cast<SwTextPaintInfo&>(rInf).SetPos( aOldPos );
OSL_ENSURE( nCount < 7, "Too much combined characters" );
// If there are leading "weak"-scripttyped characters in this portion, // they get the actual scripttype. for( sal_Int32 i = 0; i < nCount && SW_SCRIPTS == m_aScrType[i]; ++i )
m_aScrType[i] = rInf.GetFont()->GetActual(); if( nCount > 4 )
{ // more than four? Ok, then we need the 2/3 font width for( sal_Int32 i = 0; i < m_aExpand.getLength(); ++i )
{
OSL_ENSURE( m_aScrType[i] < SW_SCRIPTS, "Combined: Script fault" ); if( !m_aWidth[ m_aScrType[i] ] )
{
rInf.GetOut()->SetFont( rInf.GetFont()->GetFnt( m_aScrType[i] ) );
m_aWidth[ m_aScrType[i] ] =
2 * rInf.GetOut()->GetFontMetric().GetFontSize().Width() / 3;
}
}
}
const sal_Int32 nTop = ( nCount + 1 ) / 2; // the first character of the second line
SwViewShell *pSh = rInf.GetTextFrame()->getRootFrame()->GetCurrShell();
SwFont aTmpFont( *rInf.GetFont() );
SwFontSave aFontSave( rInf, &aTmpFont );
m_nProportion = 55; // In nMainAscent/Descent we store the ascent and descent // of the original surrounding font
SwTwips nMaxDescent, nMaxAscent, nMaxWidth;
sal_uInt16 nMainDescent = rInf.GetFont()->GetHeight( pSh, *rInf.GetOut() ); const sal_uInt16 nMainAscent = rInf.GetFont()->GetAscent( pSh, *rInf.GetOut() );
nMainDescent = nMainDescent - nMainAscent; // we start with a 50% font, but if we notice that the combined portion // becomes bigger than the surrounding font, we check 45% and maybe 40%. do
{
m_nProportion -= 5;
aTmpFont.SetProportion( m_nProportion );
memset( &m_aPos, 0, sizeof(m_aPos) );
nMaxDescent = 0;
nMaxAscent = 0;
nMaxWidth = 0;
m_nUpPos = m_nLowPos = 0;
// Now we get the width of all characters. // The ascent and the width of the first line are stored in the // ascent member of the portion, the descent in nLowPos. // The ascent, descent and width of the second line are stored in the // local nMaxAscent, nMaxDescent and nMaxWidth variables. for( sal_Int32 i = 0; i < nCount; ++i )
{
SwFontScript nScrp = m_aScrType[i];
aTmpFont.SetActual( nScrp ); if( m_aWidth[ nScrp ] )
{
Size aFontSize( aTmpFont.GetSize( nScrp ) );
aFontSize.setWidth( m_aWidth[ nScrp ] );
aTmpFont.SetSize( aFontSize, nScrp );
}
SwDrawTextInfo aDrawInf(pSh, *rInf.GetOut(), m_aExpand, i, 1);
Size aSize = aTmpFont.GetTextSize_( aDrawInf ); const sal_uInt16 nAsc = aTmpFont.GetAscent( pSh, *rInf.GetOut() );
m_aPos[i] = aSize.Width(); if( i == nTop ) // enter the second line
{
m_nLowPos = nMaxDescent;
Height( nMaxDescent + nMaxAscent );
Width( nMaxWidth );
SetAscent( nMaxAscent );
nMaxAscent = 0;
nMaxDescent = 0;
nMaxWidth = 0;
}
nMaxWidth = nMaxWidth + m_aPos[ i ]; if( nAsc > nMaxAscent )
nMaxAscent = nAsc; if( aSize.Height() - nAsc > nMaxDescent )
nMaxDescent = aSize.Height() - nAsc;
} // for one or two characters we double the width of the portion if( nCount < 3 )
{
nMaxWidth *= 2;
Width( 2*Width() ); if( nCount < 2 )
{
Height( nMaxAscent + nMaxDescent );
m_nLowPos = nMaxDescent;
}
}
Height( Height() + nMaxDescent + nMaxAscent );
m_nUpPos = nMaxAscent;
SetAscent( Height() - nMaxDescent - m_nLowPos );
} while( m_nProportion > 40 && ( GetAscent() > nMainAscent ||
Height() - GetAscent() > nMainDescent ) ); // if the combined portion is smaller than the surrounding text, // the portion grows. This looks better, if there's a character background. if( GetAscent() < nMainAscent )
{
Height( Height() + nMainAscent - GetAscent() );
SetAscent( nMainAscent );
} if( Height() < nMainAscent + nMainDescent )
Height( nMainAscent + nMainDescent );
// We calculate the x positions of the characters in both lines...
SwTwips nTopDiff = 0;
SwTwips nBotDiff = 0; if( nMaxWidth > Width() )
{
nTopDiff = ( nMaxWidth - Width() ) / 2;
Width( nMaxWidth );
} else
nBotDiff = ( Width() - nMaxWidth ) / 2; switch( nTop)
{ case 3: m_aPos[1] = m_aPos[0] + nTopDiff;
[[fallthrough]]; case 2: m_aPos[nTop-1] = Width() - m_aPos[nTop-1];
}
m_aPos[0] = 0; switch( nCount )
{ case 5: m_aPos[4] = m_aPos[3] + nBotDiff;
[[fallthrough]]; case 3: m_aPos[nTop] = nBotDiff; break; case 6: m_aPos[4] = m_aPos[3] + nBotDiff;
[[fallthrough]]; case 4: m_aPos[nTop] = 0;
[[fallthrough]]; case 2: m_aPos[nCount-1] = Width() - m_aPos[nCount-1];
}
SwTwips SwCombinedPortion::GetViewWidth(const SwTextSizeInfo& rInf) const
{ if( !GetLen() ) // for the dummy part at the end of the line, where return 0; // the combined portion doesn't fit. return SwFieldPortion::GetViewWidth( rInf );
}
// Map the text of the field to the descriptor's text. static sal_Unicode constexpr aForbidden[] = { CH_TXTATR_BREAKWORD, 0 };
aDescriptor.Text = comphelper::string::removeAny(GetExp(), aForbidden);
// Description for accessibility purposes. if (!m_sHelp.isEmpty())
aDescriptor.Description = m_sHelp;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.