/* -*- 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
*/
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.