/* -*- 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 .
*/
namespace { //! Calculates and sets optimal repaint offset for the current line
tools::Long lcl_CalcOptRepaint( SwTextFormatter &rThis,
SwLineLayout const &rCurr,
TextFrameIndex nOldLineEnd, const std::vector<tools::Long> &rFlyStarts ); //! Determine if we need to build hidden portions bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex &rPos);
// Check whether the two font has the same border bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond);
}
if (m_nStart > TextFrameIndex(GetInfo().GetText().getLength()))
{
OSL_ENSURE( false, "+SwTextFormatter::CTOR: bad offset" );
m_nStart = TextFrameIndex(GetInfo().GetText().getLength());
}
}
SwTextFormatter::~SwTextFormatter()
{ // Extremely unlikely, but still possible // e.g.: field splits up, widows start to matter if( GetInfo().GetRest() )
{ delete GetInfo().GetRest();
GetInfo().SetRest(nullptr);
}
}
void SwTextFormatter::Insert( SwLineLayout *pLay )
{ // Insert BEHIND the current element if ( m_pCurr )
{
pLay->SetNext( m_pCurr->GetNext() );
m_pCurr->SetNext( pLay );
} else
m_pCurr = pLay;
}
SwTwips SwTextFormatter::GetFrameRstHeight() const
{ // We want the rest height relative to the page. // If we're in a table, then pFrame->GetUpper() is not the page.
// Save flys and set to 0, or else segmentation fault // Not ClearFly(rInf) !
SwFlyPortion *pFly = rInf.GetFly();
rInf.SetFly( nullptr );
FeedInf( rInf );
rInf.SetLast( m_pCurr ); // pUnderflow does not need to be deleted, because it will drown in the following // Truncate()
rInf.SetUnderflow(nullptr);
rInf.SetSoftHyphPos( nSoftHyphPos );
rInf.SetPaintOfst( GetLeftMargin() );
// We look for the portion with the under-flow position
SwLinePortion *pPor = m_pCurr->GetFirstPortion(); if( pPor != pUnderflow )
{ // pPrev will be the last portion before pUnderflow, // which still has a real width. // Exception: SoftHyphPortion must not be forgotten, of course! // Although they don't have a width.
SwLinePortion *pTmpPrev = pPor; while( pPor && pPor != pUnderflow )
{ if( !pPor->IsKernPortion() &&
( pPor->Width() || pPor->IsSoftHyphPortion() ) )
{ while( pTmpPrev != pPor )
{
pTmpPrev->Move( rInf );
rInf.SetLast( pTmpPrev );
pTmpPrev = pTmpPrev->GetNextPortion();
OSL_ENSURE( pTmpPrev, "Underflow: losing control!" );
};
}
pPor = pPor->GetNextPortion();
}
pPor = pTmpPrev; if( pPor && // Skip flys and initials when underflow.
( pPor->IsFlyPortion() || pPor->IsDropPortion() ||
pPor->IsFlyCntPortion() ) )
{
pPor->Move( rInf );
rInf.SetLast( pPor );
rInf.SetStopUnderflow( true );
pPor = pUnderflow;
}
}
// What? The under-flow portion is not in the portion chain?
OSL_ENSURE( pPor, "SwTextFormatter::Underflow: overflow but underflow" );
// Snapshot if ( pPor==rInf.GetLast() )
{ // We end up here, if the portion triggering the under-flow // spans over the whole line. E.g. if a word spans across // multiple lines and flows into a fly in the second line.
rInf.SetFly( pFly );
pPor->Truncate(); return pPor; // Is that enough?
} // End the snapshot
// Preparing for Format() // We need to chip off the chain behind pLast, because we Insert after the Format()
SeekAndChg( rInf );
// line width is adjusted, so that pPor does not fit to current // line anymore
rInf.Width( rInf.X() + (pPor->Width() ? pPor->Width() - 1 : 0) );
rInf.SetLen( pPor->GetLen() );
rInf.SetFull( false ); if( pFly )
{ // We need to recalculate the FlyPortion due to the following reason: // If the base line is lowered by a big font in the middle of the line, // causing overlapping with a fly, the FlyPortion has a wrong size/fixed // size.
rInf.SetFly( pFly );
CalcFlyWidth( rInf );
}
rInf.GetLast()->SetNextPortion(nullptr);
// The SwLineLayout is an exception to this, which splits at the first // portion change. // Here only the other way around: if( rInf.GetLast() == m_pCurr )
{ if( pPor->InTextGrp() && !pPor->InExpGrp() )
{ const PortionType nOldWhich = m_pCurr->GetWhichPor();
*static_cast<SwLinePortion*>(m_pCurr) = *pPor;
m_pCurr->SetNextPortion( pPor->GetNextPortion() );
m_pCurr->SetWhichPor( nOldWhich );
pPor->SetNextPortion( nullptr ); delete pPor;
pPor = m_pCurr;
}
}
// Make sure that m_pFirstOfBorderMerge does not point to a portion which // will be deleted by Truncate() below.
SwLinePortion* pNext = pPor->GetNextPortion(); while (pNext)
{ if (ClearIfIsFirstOfBorderMerge(pNext)) break;
pNext = pNext->GetNextPortion();
}
pPor->Truncate();
SwLinePortion *const pRest( rInf.GetRest() ); if (pRest && pRest->InFieldGrp() && static_cast<SwFieldPortion*>(pRest)->IsNoLength())
{ // HACK: decrement again, so we pick up the suffix in next line!
m_pByEndIter->PrevAttr();
} delete pRest;
rInf.SetRest(nullptr); return pPor;
}
void SwTextFormatter::InsertPortion( SwTextFormatInfo &rInf,
SwLinePortion *pPor )
{
SwLinePortion *pLast = nullptr; // The new portion is inserted, but everything's different for // LineLayout... if( pPor == m_pCurr )
{ if ( m_pCurr->GetNextPortion() )
{
pLast = pPor;
pPor = m_pCurr->GetNextPortion();
}
// i#112181 - Prevent footnote anchor being wrapped to next line // without preceding word
rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() );
} else
{
pLast = rInf.GetLast(); if( pLast->GetNextPortion() )
{ while( pLast->GetNextPortion() )
pLast = pLast->GetNextPortion();
rInf.SetLast( pLast );
}
pLast->Insert( pPor );
if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY))
{ // For DOCX with compat=14 the only shape in line defines height of the line in spite of used font if (pLast->IsFlyCntPortion() && pPor->IsTextPortion() && pPor->GetLen() == TextFrameIndex(0))
{
m_pCurr->SetAscent(pLast->GetAscent());
m_pCurr->Height(pLast->Height());
}
}
}
// Sometimes chains are constructed (e.g. by hyphenate)
rInf.SetLast( pPor ); while( pPor )
{ if (!pPor->IsDropPortion())
MergeCharacterBorder(*pPor, pLast, rInf);
void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf )
{
OSL_ENSURE( rInf.GetText().getLength() < COMPLETE_STRING, "SwTextFormatter::BuildPortions: bad text length in info" );
rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() );
// First NewTextPortion() decides whether pCurr ends up in pPor. // We need to make sure that the font is being set in any case. // This is done automatically in CalcAscent.
rInf.SetLast( m_pCurr );
rInf.ForcedLeftMargin( 0 );
OSL_ENSURE( m_pCurr->FindLastPortion() == m_pCurr, "pLast supposed to equal pCurr" );
// Width() is shortened in CalcFlyWidth if we have a FlyPortion
OSL_ENSURE( !rInf.X() || m_pMulti, "SwTextFormatter::BuildPortion X=0?" );
CalcFlyWidth( rInf );
SwFlyPortion *pFly = rInf.GetFly(); if( pFly )
{ if ( 0 < pFly->GetFix() )
ClearFly( rInf ); else
rInf.SetFull(true);
}
::std::optional<TextFrameIndex> oMovedFlyIndex; if (SwTextFrame const*const pFollow = GetTextFrame()->GetFollow())
{ // flys are always on master! if (GetTextFrame()->GetDrawObjs() && pFollow->GetUpper() != GetTextFrame()->GetUpper())
{ for (SwAnchoredObject const*const pAnchoredObj : *GetTextFrame()->GetDrawObjs())
{ // tdf#146500 try to stop where a fly is anchored in the follow // that has recently been moved (presumably by splitting this // frame); similar to check in SwFlowFrame::MoveBwd() if (pAnchoredObj->RestartLayoutProcess()
&& !pAnchoredObj->IsTmpConsiderWrapInfluence())
{
SwFormatAnchor const& rAnchor(pAnchoredObj->GetFrameFormat()->GetAnchor());
assert(rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR || rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA);
TextFrameIndex const nAnchor(GetTextFrame()->MapModelToViewPos(*rAnchor.GetContentAnchor())); if (pFollow->GetOffset() <= nAnchor
&& (pFollow->GetFollow() == nullptr
|| nAnchor < pFollow->GetFollow()->GetOffset()))
{ if (!oMovedFlyIndex || nAnchor < *oMovedFlyIndex)
{
oMovedFlyIndex.emplace(nAnchor);
}
}
}
}
}
}
// Asian grid stuff
SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
// tdf#149089: For compatibility with MSO grid layout, do not insert kern portions to // align successive portions to the char grid when MS_WORD_COMP_GRID_METRICS is set. // See also tdf#161145. // tdf#139418: However, in testing, this only seems to apply to horizontal text. constbool bUseGridKernPors = GetTextFrame()->IsVertical()
|| !GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
DocumentSettingId::MS_WORD_COMP_GRID_METRICS); constbool bHasGrid = pGrid && rInf.SnapToGrid()
&& SwTextGrid::LinesAndChars == pGrid->GetGridType() && bUseGridKernPors;
// used for grid mode only: // the pointer is stored, because after formatting of non-asian text, // the width of the kerning portion has to be adjusted // Inserting a SwKernPortion before a SwTabPortion isn't necessary // and will break the SwTabPortion.
SwKernPortion* pGridKernPortion = nullptr;
while( pPor && !rInf.IsStop() )
{
OSL_ENSURE(rInf.GetLen() < TextFrameIndex(COMPLETE_STRING) &&
rInf.GetIdx() <= TextFrameIndex(rInf.GetText().getLength()), "SwTextFormatter::BuildPortions: bad length in info" );
// We have to check the script for fields in order to set the // correct nActual value for the font. if( pPor->InFieldGrp() ) static_cast<SwFieldPortion*>(pPor)->CheckScript( rInf );
auto fnRequireKerningAtPosition = [&](TextFrameIndex nIdx)
{ if (nIdx == TextFrameIndex{ 0 }) returnfalse;
auto nCurrScriptType = m_pScriptInfo->ScriptType(nIdx); auto nPrevScriptType = m_pScriptInfo->ScriptType(nIdx - TextFrameIndex{ 1 }); if (nCurrScriptType == nPrevScriptType) returnfalse;
// tdf#89288: Only insert space between CJK and non-CJK text if (nCurrScriptType != css::i18n::ScriptType::ASIAN
&& nPrevScriptType != css::i18n::ScriptType::ASIAN) returnfalse;
// tdf#136663: However, this extra space is only a feature of Chinese and Japanese // typesetting. Extra space shouldn't be inserted between Hangul and non-CJK characters. const CharClass& rCC = GetAppCharClass(); auto fnIsHangul = [&](TextFrameIndex nPos)
{ auto nType = rCC.getScript(rInf.GetText(), static_cast<sal_Int32>(nPos)); switch (nType)
{ case css::i18n::UnicodeScript_kHangulJamo: case css::i18n::UnicodeScript_kHangulCompatibilityJamo: case css::i18n::UnicodeScript_kHangulSyllable: returntrue;
nLstHeight /= 5; // does the kerning portion still fit into the line? if (bAllowBefore && fnRequireKerningAtPosition(rInf.GetIdx()) && nLstHeight
&& rInf.X() + nLstHeight <= rInf.Width() && !pPor->InTabGrp())
{
SwKernPortion* pKrn = new SwKernPortion( *rInf.GetLast(), nLstHeight,
pLast->InFieldGrp() && pPor->InFieldGrp() );
// ofz#58550 Direct-leak, pKrn adds itself as the NextPortion // of rInf.GetLast(), but may use CopyLinePortion to add a copy // of itself, which will then be left dangling with the following // SetNextPortion(nullptr)
SwLinePortion *pNext = rInf.GetLast()->GetNextPortion(); if (pNext != pKrn) delete pNext;
// if we have a new GridKernPortion, we initially calculate // its size so that its ends on the grid const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame(); const SwLayoutFrame* pBody = pPageFrame->FindBodyCont();
SwRectFnSet aRectFnSet(pPageFrame);
// the multi-portion has its own format function if( pPor->IsMultiPortion() && ( !m_pMulti || m_pMulti->IsBidi() ) )
bFull = BuildMultiPortion( rInf, *static_cast<SwMultiPortion*>(pPor) ); else
bFull = pPor->Format( rInf );
// if we are underlined, we store the beginning of this underlined // segment for repaint optimization if ( LINESTYLE_NONE != m_pFont->GetUnderline() && ! nUnderLineStart )
nUnderLineStart = GetLeftMargin() + rInf.X();
if ( pPor->IsFlyPortion() )
m_pCurr->SetFly( true ); // some special cases, where we have to take care for the repaint // offset: // 1. Underlined portions due to special underline feature // 2. Right Tab // 3. BidiPortions // 4. other Multiportions // 5. DropCaps // 6. Grid Mode elseif ( ( ! rInf.GetPaintOfst() || nUnderLineStart < rInf.GetPaintOfst() ) && // 1. Underlined portions
nUnderLineStart && // reformat is at end of an underlined portion and next portion // is not underlined
( ( rInf.GetReformatStart() == rInf.GetIdx() &&
LINESTYLE_NONE == m_pFont->GetUnderline()
) || // reformat is inside portion and portion is underlined
( rInf.GetReformatStart() >= rInf.GetIdx() &&
rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() &&
LINESTYLE_NONE != m_pFont->GetUnderline() ) ) )
rInf.SetPaintOfst( nUnderLineStart ); elseif ( ! rInf.GetPaintOfst() && // 2. Right Tab
( ( pPor->InTabGrp() && !pPor->IsTabLeftPortion() ) || // 3. BidiPortions
( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() ) || // 4. Multi Portion and 5. Drop Caps
( ( pPor->IsDropPortion() || pPor->IsMultiPortion() ) &&
rInf.GetReformatStart() >= rInf.GetIdx() &&
rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() ) // 6. Grid Mode
|| ( bHasGrid && SwFontScript::CJK != m_pFont->GetActual() )
)
) // we store the beginning of the critical portion as our // paint offset
rInf.SetPaintOfst( GetLeftMargin() + rInf.X() );
// under one of these conditions we are allowed to delete the // start of the underline portion if ( IsUnderlineBreak( *pPor, *m_pFont ) )
nUnderLineStart = 0;
if( pPor->IsFlyCntPortion() || ( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->HasFlyInContent() ) )
SetFlyInCntBase(); // bUnderflow needs to be reset or we wrap again at the next softhyphen if ( !bFull )
{
rInf.ClrUnderflow(); if( ! bHasGrid && rInf.HasScriptSpace() && pPor->InTextGrp() &&
pPor->GetLen() && !pPor->InFieldGrp() )
{ // The distance between two different scripts is set // to 20% of the fontheight.
TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen(); if (nTmp == m_pScriptInfo->NextScriptChg(nTmp - TextFrameIndex(1))
&& nTmp != TextFrameIndex(rInf.GetText().getLength())
&& fnRequireKerningAtPosition(nTmp))
{ const SwTwips nDist = rInf.GetFont()->GetHeight()/5;
if( nDist )
{ // we do not want a kerning portion if any end // would be a punctuation character const CharClass& rCC = GetAppCharClass(); if (rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp) - 1)
&& rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp)))
{ // does the kerning portion still fit into the line? if ( rInf.X() + pPor->Width() + nDist <= rInf.Width() ) new SwKernPortion( *pPor, nDist ); else
bFull = true;
}
}
}
}
}
// snap non-asian text to grid if next portion is ASIAN or // there are no more portions in this line // be careful when handling an underflow event: the gridkernportion // could have been deleted if ( nRestWidth > 0 && SwFontScript::CJK != nCurrScript &&
! rInf.IsUnderflow() && ( bFull || SwFontScript::CJK == nNextScript ) )
{
OSL_ENSURE( pGridKernPortion, "No GridKernPortion available" );
OSL_ENSURE( nKernWidth <= nRestWidth, "Not enough space left for adjusting non-asian text in grid mode" ); if (nKernWidth_1)
{
pGridKernPortion->Width( pGridKernPortion->Width() + nKernWidth_1 );
rInf.X( rInf.X() + nKernWidth_1 );
}
if ( ! bFull && nKernWidth - nKernWidth_1 > 0 ) new SwKernPortion( *pPor, static_cast<short>(nKernWidth - nKernWidth_1), false, true );
// Restportions from fields with multiple lines don't yet have the right ascent if ( !pPor->GetLen() && !pPor->IsFlyPortion()
&& !pPor->IsGrfNumPortion() && ! pPor->InNumberGrp()
&& !pPor->IsMultiPortion() )
CalcAscent( rInf, pPor );
InsertPortion( rInf, pPor ); if (pPor->IsMultiPortion() && (!m_pMulti || m_pMulti->IsBidi()))
{
(void) rInf.CheckCurrentPosBookmark(); // bookmark was already created inside MultiPortion!
}
pPor = NewPortion(rInf, oMovedFlyIndex);
}
// Reinit the tab overflow flag after the line
rInf.SetTabOverflow( false );
}
void SwTextFormatter::CalcAdjustLine( SwLineLayout *pCurrent )
{ if( SvxAdjust::Left != GetAdjust() && !m_pMulti)
{
pCurrent->SetFormatAdj(true); if( IsFlyInCntBase() )
{
CalcAdjLine( pCurrent ); // For e.g. centered fly we need to switch the RefPoint // That's why bAlways = true
UpdatePos( pCurrent, GetTopLeft(), GetStart(), true );
}
}
}
void SwTextFormatter::CalcAscent( SwTextFormatInfo &rInf, SwLinePortion *pPor )
{ bool bCalc = false; if ( pPor->InFieldGrp() && static_cast<SwFieldPortion*>(pPor)->GetFont() )
{ // Numbering + InterNetFields can keep an own font, then their size is // independent from hard attribute values
SwFont* pFieldFnt = static_cast<SwFieldPortion*>(pPor)->m_pFont.get();
SwFontSave aSave( rInf, pFieldFnt );
pPor->Height( rInf.GetTextHeight() );
pPor->SetAscent( rInf.GetAscent() );
bCalc = true;
} // i#89179 // tab portion representing the list tab of a list label gets the // same height and ascent as the corresponding number portion elseif ( pPor->InTabGrp() && pPor->GetLen() == TextFrameIndex(0) &&
rInf.GetLast() && rInf.GetLast()->InNumberGrp() && static_cast<const SwNumberPortion*>(rInf.GetLast())->HasFont() )
{ const SwLinePortion* pLast = rInf.GetLast();
pPor->Height( pLast->Height() );
pPor->SetAscent( pLast->GetAscent() );
} elseif (pPor->GetWhichPor() == PortionType::Bookmark
&& rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
{ // bookmark at end of paragraph: *don't* advance iterator, use the // current font instead; it's possible that there's a font size on the // paragraph and it's overridden on the last line of the paragraph and // we don't want to apply it via SwBookmarkPortion and grow the line // height (example: n758883.docx)
SwLinePortion const*const pLast = rInf.GetLast();
assert(pLast);
pPor->Height( pLast->Height(), false );
pPor->SetAscent( pLast->GetAscent() );
} else
{ const SwLinePortion *pLast = rInf.GetLast(); bool bChg = false;
// In empty lines the attributes are switched on via SeekStart constbool bFirstPor = rInf.GetLineStart() == rInf.GetIdx();
if ( pPor->IsQuoVadisPortion() )
bChg = SeekStartAndChg( rInf, true ); else
{ if( bFirstPor )
{ if( !rInf.GetText().isEmpty() )
{ if ((rInf.GetIdx() != TextFrameIndex(rInf.GetText().getLength())
|| rInf.GetRest() // field continued - not empty
|| !GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
DocumentSettingId::APPLY_TEXT_ATTR_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH))
&& (pPor->GetLen() || !rInf.GetIdx()
|| (m_pCurr != pLast && !pLast->IsFlyPortion())
|| !m_pCurr->IsRest())) // instead of !rInf.GetRest()
{
bChg = SeekAndChg( rInf );
} else
bChg = SeekAndChgBefore( rInf );
} elseif ( m_pMulti ) // do not open attributes starting at 0 in empty multi // portions (rotated numbering followed by a footnote // can cause trouble, because the footnote attribute // starts at 0, but if we open it, the attribute handler // cannot handle it.
bChg = false; else
bChg = SeekStartAndChg( rInf );
} else
bChg = SeekAndChg( rInf );
} if( bChg || bFirstPor || !pPor->GetAscent()
|| !rInf.GetLast()->InTextGrp() )
{
pPor->SetHangingBaseline( rInf.GetHangingBaseline() );
pPor->SetAscent( rInf.GetAscent() );
pPor->Height(rInf.GetTextHeight());
bCalc = true;
} else
{
pPor->Height( pLast->Height() );
pPor->SetAscent( pLast->GetAscent() );
}
}
/// A content control portion is a text portion that is inside RES_TXTATR_CONTENTCONTROL. class SwContentControlPortion : public SwTextPortion
{
SwTextContentControl* m_pTextContentControl; public:
SwContentControlPortion(SwTextContentControl* pTextContentControl); virtualvoid Paint(const SwTextPaintInfo& rInf) const override;
/// Emits a PDF form widget for this portion on success, does nothing on failure. bool DescribePDFControl(const SwTextPaintInfo& rInf) const;
};
}
SwTextNode* pTextNode = pContentControl->GetTextNode();
SwDoc& rDoc = pTextNode->GetDoc(); if (rDoc.IsInHeaderFooter(*pTextNode))
{ // Form control in header/footer makes no sense, would allow multiple values for the same // control. returnfalse;
}
// Check if this is the first content control portion of this content control.
sal_Int32 nStart = m_pTextContentControl->GetStart();
sal_Int32 nEnd = *m_pTextContentControl->GetEnd();
TextFrameIndex nViewStart = rInf.GetTextFrame()->MapModelToView(pTextNode, nStart);
TextFrameIndex nViewEnd = rInf.GetTextFrame()->MapModelToView(pTextNode, nEnd); // The content control portion starts 1 char after the starting dummy character. if (rInf.GetIdx() != nViewStart + TextFrameIndex(1))
{ // Ignore: don't process and also don't emit plain text fallback. returntrue;
}
std::unique_ptr<vcl::PDFWriter::AnyWidget> pDescriptor; switch (pContentControl->GetType())
{ case SwContentControlType::RICH_TEXT: case SwContentControlType::PLAIN_TEXT:
{
pDescriptor = std::make_unique<vcl::PDFWriter::EditWidget>(); auto pEditWidget = static_cast<vcl::PDFWriter::EditWidget*>(pDescriptor.get());
pEditWidget->MultiLine = true; break;
} case SwContentControlType::CHECKBOX:
{
pDescriptor = std::make_unique<vcl::PDFWriter::CheckBoxWidget>(); auto pCheckBoxWidget = static_cast<vcl::PDFWriter::CheckBoxWidget*>(pDescriptor.get());
pCheckBoxWidget->Checked = pContentControl->GetChecked(); // If it's checked already, then leave the default "Yes" OnValue unchanged, so the // appropriate appearance is found by PDF readers. if (!pCheckBoxWidget->Checked)
{
pCheckBoxWidget->OnValue = pContentControl->GetCheckedState();
pCheckBoxWidget->OffValue = pContentControl->GetUncheckedState();
} break;
} case SwContentControlType::DROP_DOWN_LIST:
{
pDescriptor = std::make_unique<vcl::PDFWriter::ListBoxWidget>(); auto pListWidget = static_cast<vcl::PDFWriter::ListBoxWidget*>(pDescriptor.get());
pListWidget->DropDown = true;
sal_Int32 nIndex = 0; bool bTextFound = false; for (constauto& rItem : pContentControl->GetListItems())
{
pListWidget->Entries.push_back(rItem.m_aDisplayText); if (rItem.m_aDisplayText == aText)
{
pListWidget->SelectedEntries.push_back(nIndex);
bTextFound = true;
}
++nIndex;
} if (!aText.isEmpty() && !bTextFound)
{ // The selected entry has to be an index, if there is no index for it, insert one at // the start.
pListWidget->Entries.insert(pListWidget->Entries.begin(), aText);
pListWidget->SelectedEntries.push_back(0);
} break;
} case SwContentControlType::COMBO_BOX:
{
pDescriptor = std::make_unique<vcl::PDFWriter::ComboBoxWidget>(); auto pComboWidget = static_cast<vcl::PDFWriter::ComboBoxWidget*>(pDescriptor.get()); for (constauto& rItem : pContentControl->GetListItems())
{
pComboWidget->Entries.push_back(rItem.m_aDisplayText);
} break;
} case SwContentControlType::DATE:
{
pDescriptor = std::make_unique<vcl::PDFWriter::EditWidget>(); auto pEditWidget = static_cast<vcl::PDFWriter::EditWidget*>(pDescriptor.get());
pEditWidget->Format = vcl::PDFWriter::Date; // GetDateFormat() uses a syntax that works with SvNumberFormatter::PutEntry(), PDF's // AFDate_FormatEx() uses a similar syntax, but uses lowercase characters in case of // "Y", "M" and "D" at least.
pEditWidget->DateFormat = pContentControl->GetDateFormat().toAsciiLowerCase(); break;
} default: break;
}
if (!pDescriptor)
{ returnfalse;
}
bool bShrinkPageForPostIts = pPDFExtOutDevData->GetIsExportNotesInMargin()
&& sw_GetPostIts(rDoc.getIDocumentFieldsAccess(), nullptr); const SwFont* pFont = rInf.GetFont(); if (pFont)
{
pDescriptor->TextFont = pFont->GetActualFont(); if (bShrinkPageForPostIts)
{ // Page area is scaled down so we have space for comments. Scale down the font height // for the content of the widgets, too. double fScale = SwEnhancedPDFExportHelper::GetSwRectToPDFRectScale();
pDescriptor->TextFont.SetFontHeight(pDescriptor->TextFont.GetFontHeight() * fScale);
}
// Need to transport the color explicitly, so it's applied to both already filled in and // future content.
pDescriptor->TextColor = pFont->GetColor();
}
// Description for accessibility purposes. if (!pContentControl->GetAlias().isEmpty())
{
pDescriptor->Description = pContentControl->GetAlias();
}
// Map the text of the content control to the descriptor's text.
pDescriptor->Text = aText;
// Calculate the bounding rectangle of this content control, which can be one or more layout // portions in one or more lines.
SwRect aLocation; auto pTextFrame = const_cast<SwTextFrame*>(rInf.GetTextFrame());
SwTextSizeInfo aInf(pTextFrame);
SwTextCursor aLine(pTextFrame, &aInf);
SwRect aStartRect, aEndRect;
aLine.GetCharRect(&aStartRect, nViewStart);
aLine.GetCharRect(&aEndRect, nViewEnd);
// Handling RTL text direction if(rInf.GetTextFrame()->IsRightToLeft())
{
rInf.GetTextFrame()->SwitchLTRtoRTL( aStartRect );
rInf.GetTextFrame()->SwitchLTRtoRTL( aEndRect );
} // TODO: handle rInf.GetTextFrame()->IsVertical()
// PDF spec 12.5.2 Annotation Dictionaries says the default border with is 1pt wide, increase // the rectangle to compensate for that, otherwise the text will be cut off at the end.
aLocation.AddTop(-20);
aLocation.AddBottom(20);
aLocation.AddLeft(-20);
aLocation.AddRight(20);
tools::Rectangle aRect = aLocation.SVRect(); if (bShrinkPageForPostIts)
{ // Map the rectangle of the form widget, similar to how it's done for e.g. hyperlinks. const SwPageFrame* pPageFrame = pTextFrame->FindPageFrame(); if (pPageFrame)
{
aRect = SwEnhancedPDFExportHelper::MapSwRectToPDFRect(pPageFrame, aRect);
}
}
pDescriptor->Location = aRect;
while (xEnum->hasMoreElements())
{
rdf::Statement stmt; if (!(xEnum->nextElement() >>= stmt)) { throw uno::RuntimeException();
} const uno::Reference<rdf::XLiteral> xObject(stmt.Object, uno::UNO_QUERY); if (!xObject.is()) continue; if (xEnum->hasMoreElements()) {
SAL_INFO("sw.uno", "ignoring other odf:shading statements");
}
Color rColor = Color::STRtoRGB(xObject->getValue());
pMetaPor->SetShadowColor(rColor); break;
}
}
}
pPor = pMetaPor;
} elseif (GetFnt()->IsContentControl())
{
SwTextFrame const*const pFrame(rInf.GetTextFrame());
SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
SwTextNode* pTextNode = aPosition.GetNode().GetTextNode();
SwTextContentControl* pTextContentControl = nullptr; if (pTextNode)
{
sal_Int32 nIndex = aPosition.GetContentIndex(); if (SwTextAttr* pAttr = pTextNode->GetTextAttrAt(nIndex, RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent))
{
pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr);
}
}
pPor = new SwContentControlPortion(pTextContentControl);
} else
{ // Only at the End! // If pCurr does not have a width, it can however already have content. // E.g. for non-displayable characters
autoconst ch(rInf.GetChar(rInf.GetIdx()));
SwTextFrame const*const pFrame(rInf.GetTextFrame());
SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
sw::mark::Fieldmark *pBM = pFrame->GetDoc().getIDocumentMarkAccess()->getInnerFieldmarkFor(aPosition); if(pBM != nullptr && pBM->GetFieldname( ) == ODF_FORMDATE)
{ if (ch == CH_TXT_ATR_FIELDSTART)
pPor = new SwFieldFormDatePortion(pBM, true); elseif (ch == CH_TXT_ATR_FIELDSEP)
pPor = new SwFieldMarkPortion(); // it's added in DateFieldmark? elseif (ch == CH_TXT_ATR_FIELDEND)
pPor = new SwFieldFormDatePortion(pBM, false);
} elseif (ch == CH_TXT_ATR_FIELDSTART)
pPor = new SwFieldMarkPortion(); elseif (ch == CH_TXT_ATR_FIELDSEP)
pPor = new SwFieldMarkPortion(); elseif (ch == CH_TXT_ATR_FIELDEND)
pPor = new SwFieldMarkPortion(); elseif (ch == CH_TXT_ATR_FORMELEMENT)
{
OSL_ENSURE(pBM != nullptr, "Where is my form field bookmark???"); if (pBM != nullptr)
{ if (pBM->GetFieldname( ) == ODF_FORMCHECKBOX)
{
pPor = new SwFieldFormCheckboxPortion();
} elseif (pBM->GetFieldname( ) == ODF_FORMDROPDOWN)
{
pPor = new SwFieldFormDropDownPortion(pBM, sw::mark::ExpandFieldmark(pBM));
} /* we need to check for ODF_FORMTEXT for scenario having FormFields inside FORMTEXT. * Otherwise file will crash on open.
*/ elseif (pBM->GetFieldname( ) == ODF_FORMTEXT)
{
pPor = new SwFieldMarkPortion();
}
}
} if( !pPor )
{ if( !rInf.X() && !m_pCurr->GetNextPortion() && !m_pCurr->GetLen() && !GetFnt()->IsURL() )
pPor = m_pCurr; else
{
pPor = new SwTextPortion; if (pBM && pBM->GetFieldname() == ODF_FORMTEXT)
pPor->SetFieldmarkText(true);
}
}
}
} return pPor;
}
// We calculate the length, the following portion limits are defined: // 1) Tabs // 2) Linebreaks // 3) CH_TXTATR_BREAKWORD / CH_TXTATR_INWORD // 4) next attribute change
SwTextPortion *SwTextFormatter::NewTextPortion( SwTextFormatInfo &rInf )
{ // If we're at the line's beginning, we take pCurr // If pCurr is not derived from SwTextPortion, we need to duplicate
Seek( rInf.GetIdx() );
SwTextPortion *pPor = WhichTextPor( rInf );
// Turbo boost: // We assume that font characters are not larger than twice // as wide as height. // Very crazy: we need to take the ascent into account.
// Mind the trap! GetSize() contains the wished-for height, the real height // is only known in CalcAscent!
// The ratio is even crazier: a blank in Times New Roman has an ascent of // 182, a height of 200 and a width of 53! // It follows that a line with a lot of blanks is processed incorrectly. // Therefore we increase from factor 2 to 8 (due to negative kerning).
// Generate a new layout context for the text portion. This is necessary // for the first text portion in a paragraph, or for any successive // portions that are outside of the bounds of the previous context. if (!rInf.GetLayoutContext().has_value()
|| rInf.GetLayoutContext()->m_nBegin < rInf.GetLineStart().get()
|| rInf.GetLayoutContext()->m_nEnd < nNextChg.get())
{ // The layout context must terminate at special characters
sal_Int32 nEnd = rInf.GetIdx().get(); for (; nEnd < nNextContext.get(); ++nEnd)
{ bool bAtEnd = false; switch (rInf.GetText()[nEnd])
{ case CH_TXTATR_BREAKWORD: case CH_TXTATR_INWORD: case CH_TXTATR_TAB: case CH_TXTATR_NEWLINE: case CH_TXT_ATR_INPUTFIELDSTART: case CH_TXT_ATR_INPUTFIELDEND: case CH_TXT_ATR_FORMELEMENT: case CH_TXT_ATR_FIELDSTART: case CH_TXT_ATR_FIELDSEP: case CH_TXT_ATR_FIELDEND: case CHAR_SOFTHYPHEN:
bAtEnd = true; break;
// We can stand in the follow, it's crucial that // pFrame->GetOffset() == 0! if( rInf.GetIdx() )
{ // We now too can elongate FootnotePortions and ErgoSumPortions
// 3. Kerning portions at beginning of line in grid mode if ( ! pPor && ! m_pCurr->GetNextPortion() )
{
SwTextGridItem const*const pGrid(
GetGridItem(GetTextFrame()->FindPageFrame())); if ( pGrid )
pPor = new SwKernPortion( *m_pCurr );
}
// 4. The line rests (multiline fields) if( !pPor )
{
pPor = rInf.GetRest(); // Only for pPor of course if( pPor )
{
m_pCurr->SetRest( true );
rInf.SetRest(nullptr);
}
}
} else
{ // 5. The foot note count if( !rInf.IsFootnoteDone() )
{
OSL_ENSURE( ( ! rInf.IsMulti() && ! m_pMulti ) || m_pMulti->HasRotation(), "Rotated number portion trouble" );
// If we're in the follow, then of course not if (GetTextFrame()->GetTextNodeForParaProps()->GetNumRule())
pPor = NewNumberPortion( rInf );
rInf.SetNumDone( true );
} // 8. The DropCaps if( !pPor && GetDropFormat() && ! rInf.IsMulti() )
pPor = NewDropPortion( rInf );
// 9. Kerning portions at beginning of line in grid mode if ( !pPor && !m_pCurr->GetNextPortion() )
{
SwTextGridItem const*const pGrid(
GetGridItem(GetTextFrame()->FindPageFrame())); if ( pGrid )
pPor = new SwKernPortion( *m_pCurr );
}
}
// 10. Decimal tab portion at the beginning of each line in table cells if ( !pPor && !m_pCurr->GetNextPortion() &&
GetTextFrame()->IsInTab() &&
GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT))
{
pPor = NewTabPortion( rInf, true );
}
// 11. suffix of meta-field if (!pPor)
{
pPor = TryNewNoLengthPortion(rInf);
}
// 12. bookmarks // check this *last* so that BuildMultiPortion() can find it! if (!pPor && rInf.CheckCurrentPosBookmark())
{ constauto bookmark = m_pScriptInfo->GetBookmarks(rInf.GetIdx()); if (!bookmark.empty())
{ // only for character width, maybe replaced with ] later
sal_Unicode mark = '[';
/* NewPortion sets rInf.nLen * A SwTextPortion is limited by a tab, break, txtatr or attr change * We can have three cases: * 1) The line is full and the wrap was not emulated * -> return 0; * 2) The line is full and a wrap was emulated * -> Reset width and return new FlyPortion * 3) We need to construct a new portion * -> CalcFlyWidth emulates the width and return portion, if needed
*/
SwLinePortion *SwTextFormatter::NewPortion(SwTextFormatInfo &rInf,
::std::optional<TextFrameIndex> const oMovedFlyIndex)
{ if (oMovedFlyIndex && *oMovedFlyIndex <= rInf.GetIdx())
{
SAL_WARN_IF(*oMovedFlyIndex != rInf.GetIdx(), "sw.core", "stopping too late, no portion break at fly anchor?");
rInf.SetStop(true); return nullptr;
}
// If the line is full, flys and Underflow portions could be waiting ... if( rInf.IsFull() )
{ // LineBreaks and Flys (bug05.sdw) // IsDummy() if( rInf.IsNewLine() && (!rInf.GetFly() || !m_pCurr->IsDummy()) ) return nullptr;
// When the text bumps into the Fly, or when the Fly comes first because // it juts out over the left edge, GetFly() is returned. // When IsFull() and no GetFly() is available, naturally zero is returned. if( rInf.GetFly() )
{ if( rInf.GetLast()->IsBreakPortion() )
{ delete rInf.GetFly();
rInf.SetFly( nullptr );
}
return rInf.GetFly();
}
// A nasty special case: A frame without wrap overlaps the Footnote area. // We must declare the Footnote portion as rest of line, so that // SwTextFrame::Format doesn't abort (the text mass already was formatted). if( rInf.GetRest() )
rInf.SetNewLine( true ); else
{ // When the next line begins with a rest of a field, but now no // rest remains, the line must definitely be formatted anew! if( lcl_OldFieldRest( GetCurr() ) )
rInf.SetNewLine( true ); else
{
SwLinePortion *pFirst = WhichFirstPortion( rInf ); if( pFirst )
{
rInf.SetNewLine( true ); if( pFirst->InNumberGrp() )
rInf.SetNumDone( false) ; delete pFirst;
}
}
}
return nullptr;
}
SwLinePortion *pPor = WhichFirstPortion( rInf );
// Check for Hidden Portion: if ( !pPor )
{
TextFrameIndex nEnd = rInf.GetIdx(); if ( ::lcl_BuildHiddenPortion( rInf, nEnd ) )
pPor = new SwHiddenTextPortion( nEnd - rInf.GetIdx() );
}
if( !pPor )
{ if( ( !m_pMulti || m_pMulti->IsBidi() ) && // i#42734 // No multi portion if there is a hook character waiting:
( !rInf.GetRest() || '\0' == rInf.GetHookChar() ) )
{ // We open a multiportion part, if we enter a multi-line part // of the paragraph.
TextFrameIndex nEnd = rInf.GetIdx();
std::optional<SwMultiCreator> pCreate = rInf.GetMultiCreator( nEnd, m_pMulti ); if( pCreate )
{
SwMultiPortion* pTmp = nullptr;
if( cChar )
{ /* We fetch cChar again to be sure that the tab is pending now and * didn't move to the next line (as happens behind frames). * However, when a FieldPortion is in the rest, we must naturally fetch * the cChar from the field content, e.g. DecimalTabs and fields (22615)
*/ if( !rInf.GetRest() || !rInf.GetRest()->InFieldGrp() )
cChar = rInf.GetChar( rInf.GetIdx() );
rInf.ClearHookChar();
} else
{ if (rInf.GetIdx() >= TextFrameIndex(rInf.GetText().getLength()))
{
rInf.SetFull(true);
CalcFlyWidth( rInf ); return pPor;
}
cChar = rInf.GetChar( rInf.GetIdx() );
}
case CH_BREAK:
{
SwTextAttr* pHint = GetAttr(rInf.GetIdx());
pPor = new SwBreakPortion(*rInf.GetLast(), pHint); break;
}
case CHAR_SOFTHYPHEN: // soft hyphen
pPor = new SwSoftHyphPortion; break;
case CHAR_HARDBLANK: // no-break space // Please check tdf#115067 if you want to edit the char
pPor = new SwBlankPortion( cChar ); break;
case CHAR_HARDHYPHEN: // non-breaking hyphen
pPor = new SwBlankPortion( '-' ); break;
case CHAR_ZWSP: // zero width space case CHAR_WJ : // word joiner
pPor = new SwControlCharPortion( cChar ); break;
case CH_TXTATR_BREAKWORD: case CH_TXTATR_INWORD: if( rInf.HasHint( rInf.GetIdx() ) )
{
pPor = NewExtraPortion( rInf ); break;
}
[[fallthrough]]; default :
{
SwTabPortion* pLastTabPortion = rInf.GetLastTab(); if ( pLastTabPortion && cChar == rInf.GetTabDecimal() )
{ // Abandon dec. tab position if line is full // We have a decimal tab portion in the line and the next character has to be // aligned at the tab stop position. We store the width from the beginning of // the tab stop portion up to the portion containing the decimal separator: if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT) /*rInf.GetVsh()->IsTabCompat();*/ &&
PortionType::TabDecimal == pLastTabPortion->GetWhichPor() )
{
OSL_ENSURE( rInf.X() >= pLastTabPortion->GetFix(), "Decimal tab stop position cannot be calculated" ); const SwTwips nWidthOfPortionsUpToDecimalPosition = rInf.X() - pLastTabPortion->GetFix(); static_cast<SwTabDecimalPortion*>(pLastTabPortion)->SetWidthOfPortionsUpToDecimalPosition( nWidthOfPortionsUpToDecimalPosition );
rInf.SetTabDecimal( 0 );
} else
rInf.SetFull( rInf.GetLastTab()->Format( rInf ) );
}
// if a portion is created despite there being a pending RestPortion, // then it is a field which has been split (e.g. because it contains a Tab) if( pPor && rInf.GetRest() )
pPor->SetLen(TextFrameIndex(0));
assert(pPor && "can only reach here with pPor existing");
// Special portions containing numbers (footnote anchor, footnote number, // numbering) can be contained in a rotated portion, if the user // choose a rotated character attribute. if (!m_pMulti)
{ if ( pPor->IsFootnotePortion() )
{ const SwTextFootnote* pTextFootnote = static_cast<SwFootnotePortion*>(pPor)->GetTextFootnote();
// The font is set in output device, // the ascent and the height will be calculated. if( !pPor->GetAscent() && !pPor->Height() )
CalcAscent( rInf, pPor );
rInf.SetLen( pPor->GetLen() );
// In CalcFlyWidth Width() will be shortened if a FlyPortion is present.
CalcFlyWidth( rInf );
// One must not forget that pCurr as GetLast() must provide reasonable values: if( !m_pCurr->Height() )
{
OSL_ENSURE( m_pCurr->Height(), "SwTextFormatter::NewPortion: limbo dance" );
m_pCurr->Height( pPor->Height(), false );
m_pCurr->SetAscent( pPor->GetAscent() );
}
// For the formatting routines, we set pOut to the reference device.
SwHookOut aHook( GetInfo() ); if (GetInfo().GetLen() < TextFrameIndex(GetInfo().GetText().getLength()))
GetInfo().SetLen(TextFrameIndex(GetInfo().GetText().getLength()));
// Recycling must be suppressed by changed line height and also // by changed ascent (lowering of baseline). const SwTwips nOldHeight = m_pCurr->Height(); const SwTwips nOldAscent = m_pCurr->GetAscent();
// fly positioning can make it necessary format a line several times // for this, we have to keep a copy of our rest portion
SwLinePortion* pField = GetInfo().GetRest();
std::unique_ptr<SwFieldPortion> xSaveField;
// for an optimal repaint rectangle, we want to compare fly portions // before and after the BuildPortions call constbool bOptimizeRepaint = AllowRepaintOpt();
TextFrameIndex const nOldLineEnd = nStartPos + m_pCurr->GetLen();
std::vector<tools::Long> flyStarts;
// these are the conditions for a fly position comparison if ( bOptimizeRepaint && m_pCurr->IsFly() )
{
SwLinePortion* pPor = m_pCurr->GetFirstPortion();
tools::Long nPOfst = 0; while ( pPor )
{ if ( pPor->IsFlyPortion() ) // insert start value of fly portion
flyStarts.push_back( nPOfst );
// Here soon the underflow check follows. while( bBuild )
{
GetInfo().SetFootnoteInside( false );
GetInfo().SetOtherThanFootnoteInside( false );
// These values must not be reset by FormatReset(); constbool bOldNumDone = GetInfo().IsNumDone(); constbool bOldFootnoteDone = GetInfo().IsFootnoteDone(); constbool bOldArrowDone = GetInfo().IsArrowDone(); constbool bOldErgoDone = GetInfo().IsErgoDone();
// besides other things, this sets the repaint offset to 0
FormatReset( GetInfo() );
// Don't oversize the line in case of split flys, so we don't try to move the anchor // of a precede fly forward, next to its follow. if (m_pFrame->HasNonLastSplitFlyDrawObj())
{
m_pCurr->SetRealHeight(GetFrameRstHeight());
}
//i#120864 For Special case that at the first calculation couldn't get //correct height. And need to recalculate for the right height.
SwLinePortion* pPorTmp = m_pCurr->GetNextPortion(); if ( IsFlyInCntBase() && (!IsQuick() || (pPorTmp && pPorTmp->IsFlyCntPortion() && !pPorTmp->GetNextPortion() &&
m_pCurr->Height() > pPorTmp->Height())))
{
SwTwips nTmpAscent, nTmpHeight;
CalcAscentAndHeight( nTmpAscent, nTmpHeight );
AlignFlyInCntBase( Y() + tools::Long( nTmpAscent ) );
m_pCurr->CalcLine( *this, GetInfo() );
CalcRealHeight();
}
// bBuild decides if another lap of honor is done if ( m_pCurr->GetRealHeight() <= GetInfo().GetLineHeight() )
{
m_pCurr->SetRealHeight( GetInfo().GetLineHeight() );
bBuild = false;
} else
{
bBuild = ( GetInfo().GetTextFly().IsOn() && ChkFlyUnderflow(GetInfo()) )
|| GetInfo().CheckFootnotePortion(m_pCurr); if( bBuild )
{ // fdo44018-2.doc: only restore m_bNumDone if a SwNumberPortion will be truncated for (SwLinePortion * pPor = m_pCurr->GetNextPortion(); pPor; pPor = pPor->GetNextPortion())
{ if (pPor->InNumberGrp())
{
GetInfo().SetNumDone( bOldNumDone ); break;
}
}
GetInfo().ResetMaxWidthDiff();
GetInfo().SetExtraSpace(0);
// delete old rest if ( GetInfo().GetRest() )
{ delete GetInfo().GetRest();
GetInfo().SetRest( nullptr );
}
// set original rest portion if ( xSaveField )
GetInfo().SetRest( new SwFieldPortion( *xSaveField ) );
// In case of compat mode, it's possible that a tab portion is wider after // formatting than before. If this is the case, we also have to make sure // the SwLineLayout is wider as well. if (GetInfo().GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_OVER_MARGIN))
{
SwTwips nSum = 0;
SwLinePortion* pPor = m_pCurr->GetFirstPortion();
while (pPor)
{
nSum += pPor->Width();
pPor = pPor->GetNextPortion();
}
if (nSum > m_pCurr->Width())
m_pCurr->Width(nSum);
}
// calculate optimal repaint rectangle if ( bOptimizeRepaint )
{
GetInfo().SetPaintOfst( ::lcl_CalcOptRepaint( *this, *m_pCurr, nOldLineEnd, flyStarts ) );
flyStarts.clear();
} else // Special case: we do not allow an optimization of the repaint // area, but during formatting the repaint offset is set to indicate // a maximum value for the offset. This value has to be reset:
GetInfo().SetPaintOfst( 0 );
// This corrects the start of the reformat range if something has // moved to the next line. Otherwise IsFirstReformat in AllowRepaintOpt // will give us a wrong result if we have to reformat another line
GetInfo().GetParaPortion()->GetReformat().LeftMove( GetInfo().GetIdx() );
// delete master copy of rest portion
xSaveField.reset();
if( nOldHeight != m_pCurr->Height() || nOldAscent != m_pCurr->GetAscent() )
{
SetFlyInCntBase();
GetInfo().SetPaintOfst( 0 ); // changed line height => no recycling // all following line must be painted and when Flys are around, // also formatted
GetInfo().SetShift( true );
}
// tdf#164871: Handle all types of line spacing in grid layout. // The prop/auto rule was originally implemented with #99106#, but other spacing // types were not implemented (perhaps unintentionally). These implementations // differ from the below non-grid implementations, so cannot be reused. const SvxLineSpacingItem* pSpace = m_aLineInf.GetLineSpacing(); if (pSpace)
{ switch (pSpace->GetLineSpaceRule())
{ case SvxLineSpaceRule::Min: // tdf#164871: MSO idiosyncratically disables the grid extra space // when the minimum height is 0. While strange, certain documents // require this and it seems harmless to emulate. if (pSpace->GetLineHeight() == 0)
{
nLineHeight = m_pCurr->Height() + nRubyHeight;
}
if (nLineHeight < pSpace->GetLineHeight())
{
nLineHeight = pSpace->GetLineHeight();
} break;
case SvxLineSpaceRule::Fix:
nLineHeight = pSpace->GetLineHeight(); break;
// we ignore any line spacing options except from ... if ( ! IsParaLine() && pSpace &&
SvxInterLineSpaceRule::Prop == pSpace->GetInterLineSpaceRule() )
{
sal_uLong nTmp = pSpace->GetPropLineSpace();
if( nTmp < 100 )
nTmp = 100;
nTmp *= nLineHeight;
nLineHeight = nTmp / 100;
}
m_pCurr->SetRealHeight( nLineHeight ); return;
}
// The dummy flag is set on lines that only contain flyportions, these shouldn't // consider register-true and so on. Unfortunately an empty line can be at // the end of a paragraph (empty paragraphs or behind a Shift-Return), // which should consider the register. if (!m_pCurr->IsDummy() || (!m_pCurr->GetNext()
&& GetStart() >= TextFrameIndex(GetTextFrame()->GetText().getLength())
&& !bNewLine))
{ const SvxLineSpacingItem *pSpace = m_aLineInf.GetLineSpacing(); if( pSpace )
{ switch( pSpace->GetLineSpaceRule() )
{ case SvxLineSpaceRule::Auto: // shrink first line of paragraph too on spacing < 100% if (IsParaLine() &&
pSpace->GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop
&& GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::PROP_LINE_SPACING_SHRINKS_FIRST_LINE))
{
tools::Long nTmp = pSpace->GetPropLineSpace(); // Word will render < 50% too but it's just not readable if( nTmp < 50 )
nTmp = nTmp ? 50 : 100; if (nTmp<100) { // code adapted from fixed line height
nTmp *= nLineHeight;
nTmp /= 100; if( !nTmp )
++nTmp;
nLineHeight = nTmp;
SwTwips nAsc = (4 * nLineHeight) / 5; // 80% #if 0 // could do clipping here (like Word does) // but at 0.5 its unreadable either way... if( nAsc < pCurr->GetAscent() ||
nLineHeight - nAsc < pCurr->Height() -
pCurr->GetAscent() )
pCurr->SetClipping( true ); #endif
m_pCurr->SetAscent( nAsc );
m_pCurr->Height( nLineHeight, false );
m_pInf->GetParaPortion()->SetFixLineHeight();
}
} break; case SvxLineSpaceRule::Min:
{ if( nLineHeight < pSpace->GetLineHeight() )
nLineHeight = pSpace->GetLineHeight(); break;
} case SvxLineSpaceRule::Fix:
{
nLineHeight = pSpace->GetLineHeight(); const SwTwips nAsc = (4 * nLineHeight) / 5; // 80% if( nAsc < m_pCurr->GetAscent() ||
nLineHeight - nAsc < m_pCurr->Height() - m_pCurr->GetAscent() )
m_pCurr->SetClipping( true );
m_pCurr->Height( nLineHeight, false );
m_pCurr->SetAscent( nAsc );
m_pInf->GetParaPortion()->SetFixLineHeight();
} break; default: OSL_FAIL( ": unknown LineSpaceRule" );
} // Note: for the _first_ line the line spacing of the previous // paragraph is applied in SwFlowFrame::CalcUpperSpace() if( !IsParaLine() ) switch( pSpace->GetInterLineSpaceRule() )
{ case SvxInterLineSpaceRule::Off: break; case SvxInterLineSpaceRule::Prop:
{
tools::Long nTmp = pSpace->GetPropLineSpace(); // 50% is the minimum, if 0% we switch to the // default value 100% ... if( nTmp < 50 )
nTmp = nTmp ? 50 : 100;
bool bPropLineShrinks = (nTmp < 100);
// extend line height by (nPropLineSpace - 100) percent of the font height
nTmp -= 100;
nTmp *= m_pCurr->GetTextHeight();
nTmp /= 100;
nTmp += nLineHeight; if (nTmp < 1)
nTmp = 1;
nLineHeight = nTmp;
// tdf#146081: The height and ascent of the first line may have been // adjusted above. In order to have consistent line spacing when rendering, // the same adjustments must be made to the following lines. if (bPropLineShrinks
&& GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
DocumentSettingId::PROP_LINE_SPACING_SHRINKS_FIRST_LINE))
{
SwTwips nAsc = (4 * nLineHeight) / 5; // 80%
m_pCurr->SetAscent(nAsc);
m_pCurr->Height(nLineHeight, false);
m_pInf->GetParaPortion()->SetFixLineHeight();
} break;
} case SvxInterLineSpaceRule::Fix:
{
nLineHeight = nLineHeight + pSpace->GetInterLineSpace(); break;
} default: OSL_FAIL( ": unknown InterLineSpaceRule" );
}
}
// FME/OD: This routine does a limited text formatting.
SwTwips SwTextFormatter::CalcFitToContent_()
{
FormatReset( GetInfo() );
BuildPortions( GetInfo() );
m_pCurr->CalcLine( *this, GetInfo() ); return m_pCurr->Width();
}
// determines if the calculation of a repaint offset is allowed // otherwise each line is painted from 0 (this is a copy of the beginning // of the former SwTextFormatter::Recycle() function bool SwTextFormatter::AllowRepaintOpt() const
{ // reformat position in front of current line? Only in this case // we want to set the repaint offset bool bOptimizeRepaint = m_nStart < GetInfo().GetReformatStart() &&
m_pCurr->GetLen();
// a special case is the last line of a block adjusted paragraph: if ( bOptimizeRepaint )
{ switch( GetAdjust() )
{ case SvxAdjust::Block:
{ if( IsLastBlock() || IsLastCenter() )
bOptimizeRepaint = false; else
{ // ????: blank in the last master line (blocksat.sdw)
bOptimizeRepaint = nullptr == m_pCurr->GetNext() && !m_pFrame->GetFollow(); if ( bOptimizeRepaint )
{
SwLinePortion *pPos = m_pCurr->GetFirstPortion(); while ( pPos && !pPos->IsFlyPortion() )
pPos = pPos->GetNextPortion();
bOptimizeRepaint = !pPos;
}
} break;
} case SvxAdjust::Center: case SvxAdjust::Right:
bOptimizeRepaint = false; break; default: ;
}
}
while( pPos )
{ // We only know one case where changing the position (caused by the // adjustment) could be relevant for a portion: We need to SetRefPoint // for FlyCntPortions. if( ( pPos->IsFlyCntPortion() || pPos->IsGrfNumPortion() )
&& ( bAlways || !IsQuick() ) )
{
pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, pPos );
// We now check every portion that could have lowered for overlapping // with the fly. const SwLinePortion *pPos = GetCurr()->GetFirstPortion();
aLine.Pos().setY( Y() + GetCurr()->GetRealHeight() - GetCurr()->Height() );
aLine.Height( GetCurr()->Height() );
// New flys from below? if( !pPos->IsFlyPortion() )
{ if( aInter.Overlaps( aLine ) )
{
aInter.Intersection_( aLine ); if( aInter.HasArea() )
{ // To be evaluated during reformat of this line: // RealHeight including spacing
rInf.SetLineHeight( nHeight ); // Height without extra spacing
rInf.SetLineNetHeight( m_pCurr->Height() ); returntrue;
}
}
} else
{ // The fly portion is not intersected by a fly anymore if ( ! aInter.Overlaps( aLine ) )
{
rInf.SetLineHeight( nHeight );
rInf.SetLineNetHeight( m_pCurr->Height() ); returntrue;
} else
{
aInter.Intersection_( aLine );
// No area means a fly has become invalid because of // lowering the line => reformat the line // we also have to reformat the line, if the fly size // differs from the intersection interval's size. if( ! aInter.HasArea() || static_cast<const SwFlyPortion*>(pPos)->GetFixWidth() != aInter.Width() )
{
rInf.SetLineHeight( nHeight );
rInf.SetLineNetHeight( m_pCurr->Height() ); returntrue;
}
}
}
if( rInf.GetLineHeight() )
{ // Real line height has already been calculated, we only have to // search for intersections in the lower part of the strip
nAscent = m_pCurr->GetAscent();
nHeight = rInf.GetLineNetHeight();
nTop += rInf.GetLineHeight() - nHeight;
} else
{ // We make a first guess for the lines real height if ( ! m_pCurr->GetRealHeight() )
CalcRealHeight();
if ( m_pCurr->GetRealHeight() > nHeight )
nTop += m_pCurr->GetRealHeight() - nHeight; else // Important for fixed space between lines
nHeight = m_pCurr->GetRealHeight();
}
bool bWordFlyWrap = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS); // tdf#116486: consider also the upper margin from getFramePrintArea because intersections // with this additional space should lead to repositioning of paragraphs // For compatibility we grab a related compat flag: if (bWordFlyWrap && IsFirstTextLine())
{
tools::Long nUpper = m_pFrame->getFramePrintArea().Top(); // Make sure that increase only happens in case the upper spacing comes from the upper // margin of the current text frame, not because of a lower spacing of the previous text // frame.
nUpper -= m_pFrame->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid(); // Increase the rectangle if( nUpper > 0 && nTop >= nUpper )
aLine.SubTop( nUpper );
}
if (IsFirstTextLine())
{ // Check if a compatibility mode requires considering also the lower margin of this text // frame, intersections with this additional space should lead to shifting the paragraph // marker down.
SwTwips nLower = m_pFrame->GetLowerMarginForFlyIntersect(); if (nLower > 0)
{
aLine.AddBottom(nLower);
}
}
if ( m_pFrame->IsVertical() )
m_pFrame->SwitchHorizontalToVertical( aLineVert );
// GetFrame(...) determines and returns the intersection rectangle
SwRect aInter( rTextFly.GetFrame( aLineVert ) );
if ( m_pFrame->IsRightToLeft() )
m_pFrame->SwitchRTLtoLTR( aInter );
if ( m_pFrame->IsVertical() )
m_pFrame->SwitchVerticalToHorizontal( aInter );
if (!aInter.IsEmpty() && aInter.Bottom() < nTop)
{ // Intersects with the frame area (with upper margin), but not with the print area (without // upper margin). Don't reserve space for the fly portion in this case, text is allowed to // flow there.
aInter.Height(0);
}
if( !aInter.Overlaps( aLine ) ) return;
aLine.Left( rInf.X() + nLeftMar ); bool bForced = false; bool bSplitFly = false; for (constauto& pObj : rTextFly.GetAnchoredObjList())
{ auto pFlyFrame = pObj->DynCastFlyFrame(); if (!pFlyFrame)
{ continue;
}
if (!pFlyFrame->IsFlySplitAllowed())
{ continue;
}
bSplitFly = true; break;
} if( aInter.Left() <= nLeftMin )
{
SwTwips nFrameLeft = GetTextFrame()->getFrameArea().Left();
SwTwips nFramePrintAreaLeft = GetTextFrame()->getFramePrintArea().Left(); if( nFramePrintAreaLeft < 0 )
nFrameLeft += GetTextFrame()->getFramePrintArea().Left(); if( aInter.Left() < nFrameLeft )
{
aInter.Left(nFrameLeft); // both sets left and reduces width if (bSplitFly && nFramePrintAreaLeft > 0 && nFramePrintAreaLeft < aInter.Width())
{ // We wrap around a split fly, the fly portion is on the // left of the paragraph and we have a positive // paragraph margin. Don't take space twice in this case // (margin, fly portion), decrease the width of the fly // portion accordingly.
aInter.Right(aInter.Right() - nFramePrintAreaLeft);
}
}
aInter.Width( aInter.Width() + nAddMar ); // For a negative first line indent, we set this flag to show // that the indentation/margin has been moved. // This needs to be respected by the DefaultTab at the zero position. if( IsFirstTextLine() && HasNegFirst() )
bForced = true;
}
aInter.Intersection( aLine ); if( !aInter.HasArea() ) return;
bool bFullLine = aLine.Left() == aInter.Left() &&
aLine.Right() == aInter.Right(); if (!bFullLine && bWordFlyWrap && !GetTextFrame()->IsInTab())
{ // Word style: if there is minimal space remaining, then handle that similar to a full line // and put the actual empty paragraph below the fly.
SwTwips nLimit = MINLAY; if (bSplitFly)
{ // We wrap around a floating table, that has a larger minimal wrap distance.
nLimit = TEXT_MIN_SMALL;
}
// Although no text is left, we need to format another line, // because also empty lines need to avoid a Fly with no wrapping. if (bFullLine && rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
{
rInf.SetNewLine( true ); // We know that for dummies, it holds ascent == height
m_pCurr->SetDummy(true);
}
if( bFullLine )
{ // In order to properly flow around Flys with different // wrapping attributes, we need to increase by units of line height. // The last avoiding line should be adjusted in height, so that // we don't get a frame spacing effect. // It is important that ascent == height, because the FlyPortion // values are transferred to pCurr in CalcLine and IsDummy() relies // on this behaviour. // To my knowledge we only have two places where DummyLines can be // created: here and in MakeFlyDummies. // IsDummy() is evaluated in IsFirstTextLine(), when moving lines // and in relation with DropCaps.
pFly->Height( aInter.Height() );
// nNextTop now contains the margin's bottom edge, which we avoid // or the next margin's top edge, which we need to respect. // That means we can comfortably grow up to this value; that's how // we save a few empty lines.
tools::Long nNextTop = rTextFly.GetNextTop(); if ( m_pFrame->IsVertical() )
nNextTop = m_pFrame->SwitchVerticalToHorizontal( nNextTop ); if( nNextTop > aInter.Bottom() )
{
SwTwips nH = nNextTop - aInter.Top(); if( nH < SAL_MAX_UINT16 )
pFly->Height( nH );
} if( nAscent < pFly->Height() )
pFly->SetAscent( nAscent ); else
pFly->SetAscent( pFly->Height() );
} else
{ if (rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
{ // Don't use nHeight, or we have a huge descent
pFly->Height( pLast->Height() );
pFly->SetAscent( pLast->GetAscent() );
} else
{
pFly->Height( aInter.Height() ); if( nAscent < pFly->Height() )
pFly->SetAscent( nAscent ); else
pFly->SetAscent( pFly->Height() );
}
}
SwFlyInContentFrame *pFly;
SwFrameFormat* pFrameFormat = static_cast<SwTextFlyCnt*>(pHint)->GetFlyCnt().GetFrameFormat(); if( RES_FLYFRMFMT == pFrameFormat->Which() )
{ // set Lock pFrame to avoid m_pCurr getting deleted
TextFrameLockGuard aGuard(m_pFrame);
pFly = static_cast<SwTextFlyCnt*>(pHint)->GetFlyFrame(pFrame);
} else
pFly = nullptr; // aBase is the document-global position, from which the new extra portion is placed // aBase.X() = Offset in the line after the current position // aBase.Y() = LineIter.Y() + Ascent of the current position
SwTwips nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc; // i#11859 - use new method <SwLineLayout::MaxAscentDescent(..)> // to change line spacing behaviour at paragraph - Compatibility to MS Word //SwLinePortion *pPos = pCurr->GetFirstPortion(); //lcl_MaxAscDescent( pPos, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc );
m_pCurr->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc );
// If the ascent of the frame is larger than the ascent of the current position, // we use this one when calculating the base, or the frame would be positioned // too much to the top, sliding down after all causing a repaint in an area // he actually never was in.
SwTwips nAscent = 0;
// Check if be prefer to use the ascent of the last portion: if ( IsQuick() ||
!bUseFlyAscent ||
nAscent < rInf.GetLast()->GetAscent() )
{
nAscent = rInf.GetLast()->GetAscent();
} elseif( nAscent > nFlyAsc )
nFlyAsc = nAscent;
Point aTmpBase( aBase ); if ( GetInfo().GetTextFrame()->IsVertical() )
GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase );
SwFlyCntPortion* pRet(nullptr); if( pFly )
{
pRet = sw::FlyContentPortion::Create(*GetInfo().GetTextFrame(), pFly, aTmpBase, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, nMode); // We need to make sure that our font is set again in the OutputDevice // It could be that the FlyInCnt was added anew and GetFlyFrame() would // in turn cause, that it'd be created anew again. // This one's frames get formatted right away, which change the font.
rInf.SelectFont(); if( pRet->GetAscent() > nAscent )
{
aBase.setY( Y() + pRet->GetAscent() );
nMode |= AsCharFlags::UlSpace; if( !rInf.IsTest() )
{
aTmpBase = aBase; if ( GetInfo().GetTextFrame()->IsVertical() )
GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase );
/* Drop portion is a special case, because it has parts which aren't portions
but we have handle them just like portions */ void SwTextFormatter::MergeCharacterBorder( SwDropPortion const & rPortion )
{ if( rPortion.GetLines() <= 1 ) return;
if (pPrev && pPrev->GetJoinBorderWithNext() )
{ // In some case border merge is called twice to the portion if( !rPortion.GetJoinBorderWithPrev() )
{
rPortion.SetJoinBorderWithPrev(true); if( rPortion.InTextGrp() && rPortion.Width() > aCurFont.GetLeftBorderSpace() )
rPortion.Width(rPortion.Width() - aCurFont.GetLeftBorderSpace());
}
} else
{
rPortion.SetJoinBorderWithPrev(false);
m_pFirstOfBorderMerge = &rPortion;
}
// Get next portion's font bool bSeek; if (rPortion.GetNextPortion() && rPortion.GetNextPortion()->IsHolePortion())
{ // Regardless of rInf.IsFull(), which may be true here, consider the next hole portion. // If it wants to show decorations: do seek; if it doesn't: don't seek (last case allows // to have proper right border at the margin).
bSeek = static_cast<SwHolePortion*>(rPortion.GetNextPortion())->ShowUnderline();
} else
{ // The border can be merged when this is not the last portion of the line (in case of line // break), and not the last portion of the paragraph.
bSeek = !rInf.IsFull()
&& rInf.GetIdx() + rPortion.GetLen() != TextFrameIndex(rInf.GetText().getLength());
} if (bSeek)
{
bSeek = Seek(rInf.GetIdx() + rPortion.GetLen());
} // Don't join the next portion if SwKernPortion sits between two different boxes. bool bDisconnect = rPortion.IsKernPortion() && !rPortion.GetJoinBorderWithPrev(); // If next portion has the same border then merge if( bSeek && GetFnt()->HasBorder() && ::lcl_HasSameBorder(aCurFont, *GetFnt()) && !bDisconnect )
{ // In some case border merge is called twice to the portion if( !rPortion.GetJoinBorderWithNext() )
{
rPortion.SetJoinBorderWithNext(true); if( rPortion.InTextGrp() && rPortion.Width() > aCurFont.GetRightBorderSpace() )
rPortion.Width(rPortion.Width() - aCurFont.GetRightBorderSpace());
}
} // If this is the last portion of the merge group then make the real height merge else
{
rPortion.SetJoinBorderWithNext(false); if( m_pFirstOfBorderMerge != &rPortion )
{ // Calculate maximum height and ascent
SwLinePortion* pActPor = m_pFirstOfBorderMerge;
SwTwips nMaxAscent = 0;
SwTwips nMaxHeight = 0; bool bReachCurrent = false; while( pActPor )
{ if( nMaxHeight < pActPor->Height() )
nMaxHeight = pActPor->Height(); if( nMaxAscent < pActPor->GetAscent() )
nMaxAscent = pActPor->GetAscent();
namespace { // calculates and sets optimal repaint offset for the current line
tools::Long lcl_CalcOptRepaint( SwTextFormatter &rThis,
SwLineLayout const &rCurr,
TextFrameIndex const nOldLineEnd, const std::vector<tools::Long> &rFlyStarts )
{
SwTextFormatInfo& txtFormatInfo = rThis.GetInfo(); if ( txtFormatInfo.GetIdx() < txtFormatInfo.GetReformatStart() ) // the reformat position is behind our new line, that means // something of our text has moved to the next line return 0;
// in case we do not have any fly in our line, our repaint position // is the changed position - 1 if ( rFlyStarts.empty() && ! rCurr.IsFly() )
{ // this is the maximum repaint offset determined during formatting // for example: the beginning of the first right tab stop // if this value is 0, this means that we do not have an upper // limit for the repaint offset const tools::Long nFormatRepaint = txtFormatInfo.GetPaintOfst();
if (nReformat < txtFormatInfo.GetLineStart() + TextFrameIndex(3)) return 0;
// step back two positions for smoother repaint
nReformat -= TextFrameIndex(2);
// i#28795, i#34607, i#38388 // step back more characters, this is required by complex scripts // e.g., for Khmer (thank you, Javier!) staticconst TextFrameIndex nMaxContext(10); if (nReformat > txtFormatInfo.GetLineStart() + nMaxContext)
nReformat = nReformat - nMaxContext; else
{
nReformat = txtFormatInfo.GetLineStart(); //reset the margin flag - prevent loops
SwTextCursor::SetRightMargin(false);
}
// Weird situation: Our line used to end with a hole portion // and we delete some characters at the end of our line. We have // to take care for repainting the blanks which are not anymore // covered by the hole portion while ( nReformat > txtFormatInfo.GetLineStart() &&
CH_BLANK == txtFormatInfo.GetChar( nReformat ) )
--nReformat;
OSL_ENSURE( nReformat < txtFormatInfo.GetIdx(), "Reformat too small for me!" );
SwRect aRect;
// Note: GetChareRect is not const. It definitely changes the // bMulti flag. We have to save and restore the old value. bool bOldMulti = txtFormatInfo.IsMulti();
rThis.GetCharRect( &aRect, nReformat );
txtFormatInfo.SetMulti( bOldMulti );
return nFormatRepaint ? std::min( aRect.Left(), nFormatRepaint ) :
aRect.Left();
} else
{ // nReformat may be wrong, if something around flys has changed: // we compare the former and the new fly positions in this line // if anything has changed, we carefully have to adjust the right // repaint position
tools::Long nPOfst = 0;
size_t nCnt = 0;
tools::Long nX = 0;
TextFrameIndex nIdx = rThis.GetInfo().GetLineStart();
SwLinePortion* pPor = rCurr.GetFirstPortion();
while ( pPor )
{ if ( pPor->IsFlyPortion() )
{ // compare start of fly with former start of fly if (nCnt < rFlyStarts.size() &&
nX == rFlyStarts[ nCnt ] &&
nIdx < nReformat
) // found fix position, nothing has changed left from nX
nPOfst = nX + pPor->Width(); else break;
// Determine if we need to build hidden portions bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex & rPos)
{ // Only if hidden text should not be shown: // if ( rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar() ) constbool bShowInDocView = rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar(); constbool bShowForPrinting = rInf.GetOpt().IsShowHiddenChar( true ) && rInf.GetOpt().IsPrinting(); if (bShowInDocView || bShowForPrinting) returnfalse;
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.