diff options
Diffstat (limited to 'sw/source/core/text/itrform2.cxx')
-rw-r--r-- | sw/source/core/text/itrform2.cxx | 3321 |
1 files changed, 3321 insertions, 0 deletions
diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx new file mode 100644 index 0000000000..c0b4894f8a --- /dev/null +++ b/sw/source/core/text/itrform2.cxx @@ -0,0 +1,3321 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <hintids.hxx> + +#include <memory> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <editeng/lspcitem.hxx> +#include <txtflcnt.hxx> +#include <txtftn.hxx> +#include <flyfrms.hxx> +#include <fmtflcnt.hxx> +#include <fmtftn.hxx> +#include <ftninfo.hxx> +#include <charfmt.hxx> +#include <editeng/charrotateitem.hxx> +#include <layfrm.hxx> +#include <viewsh.hxx> +#include <viewopt.hxx> +#include <paratr.hxx> +#include "itrform2.hxx" +#include "porrst.hxx" +#include "portab.hxx" +#include "porfly.hxx" +#include "portox.hxx" +#include "porref.hxx" +#include "porfld.hxx" +#include "porftn.hxx" +#include "porhyph.hxx" +#include "pordrop.hxx" +#include "redlnitr.hxx" +#include <sortedobjs.hxx> +#include <fmtanchr.hxx> +#include <pagefrm.hxx> +#include <tgrditem.hxx> +#include <doc.hxx> +#include "pormulti.hxx" +#include <unotools/charclass.hxx> +#include <xmloff/odffields.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IMark.hxx> +#include <IDocumentMarkAccess.hxx> +#include <comphelper/processfactory.hxx> +#include <vcl/pdfextoutdevdata.hxx> +#include <comphelper/string.hxx> +#include <docsh.hxx> +#include <unocrsrhelper.hxx> +#include <textcontentcontrol.hxx> +#include <com/sun/star/rdf/Statement.hpp> +#include <com/sun/star/rdf/URI.hpp> +#include <com/sun/star/rdf/URIs.hpp> +#include <com/sun/star/rdf/XDocumentMetadataAccess.hpp> +#include <com/sun/star/rdf/XLiteral.hpp> +#include <com/sun/star/text/XTextContent.hpp> + +using namespace ::com::sun::star; + +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); +} + +static void ClearFly( SwTextFormatInfo &rInf ) +{ + delete rInf.GetFly(); + rInf.SetFly(nullptr); +} + +void SwTextFormatter::CtorInitTextFormatter( SwTextFrame *pNewFrame, SwTextFormatInfo *pNewInf ) +{ + CtorInitTextPainter( pNewFrame, pNewInf ); + m_pInf = pNewInf; + m_pDropFormat = GetInfo().GetDropFormat(); + m_pMulti = nullptr; + + m_bOnceMore = false; + m_bFlyInContentBase = false; + m_bTruncLines = false; + m_nContentEndHyph = 0; + m_nContentMidHyph = 0; + m_nLeftScanIdx = TextFrameIndex(COMPLETE_STRING); + m_nRightScanIdx = TextFrameIndex(0); + m_pByEndIter.reset(); + m_pFirstOfBorderMerge = nullptr; + + 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; +} + +sal_uInt16 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. + + // GetFrameRstHeight() is being called with Footnote. + // Wrong: const SwFrame *pUpper = pFrame->GetUpper(); + const SwFrame *pPage = m_pFrame->FindPageFrame(); + const SwTwips nHeight = pPage->getFrameArea().Top() + + pPage->getFramePrintArea().Top() + + pPage->getFramePrintArea().Height() - Y(); + if( 0 > nHeight ) + return m_pCurr->Height(); + else + return sal_uInt16( nHeight ); +} + +bool SwTextFormatter::ClearIfIsFirstOfBorderMerge(const SwLinePortion* pPortion) +{ + if (pPortion == m_pFirstOfBorderMerge) + { + m_pFirstOfBorderMerge = nullptr; + return true; + } + return false; +} + +SwLinePortion *SwTextFormatter::Underflow( SwTextFormatInfo &rInf ) +{ + // Save values and initialize rInf + SwLinePortion *pUnderflow = rInf.GetUnderflow(); + if( !pUnderflow ) + return nullptr; + + // We format backwards, i.e. attribute changes can happen the next + // line again. + // Can be seen in 8081.sdw, if you enter text in the first line + + TextFrameIndex const nSoftHyphPos = rInf.GetSoftHyphPos(); + TextFrameIndex const nUnderScorePos = rInf.GetUnderScorePos(); + + // 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.SetUnderScorePos( nUnderScorePos ); + 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 + + // X + Width == 0 with SoftHyph > Line?! + if( !pPor || !(rInf.X() + pPor->Width()) ) + { + delete pFly; + return nullptr; + } + + // 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 ); + + rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() ); + + // Adjust maxima + if( m_pCurr->Height() < pPor->Height() ) + m_pCurr->Height( pPor->Height(), pPor->IsTextPortion() ); + if( m_pCurr->GetAscent() < pPor->GetAscent() ) + m_pCurr->SetAscent( pPor->GetAscent() ); + if( m_pCurr->GetHangingBaseline() < pPor->GetHangingBaseline() ) + m_pCurr->SetHangingBaseline( pPor->GetHangingBaseline() ); + + 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); + + pPor->Move( rInf ); + rInf.SetLast( pPor ); + pLast = pPor; + pPor = pPor->GetNextPortion(); + } +} + +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" ); + + if( !m_pCurr->GetAscent() && !m_pCurr->Height() ) + CalcAscent( rInf, m_pCurr ); + + SeekAndChg( rInf ); + + // 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); + } + } + } + } + } + } + + SwLinePortion *pPor = NewPortion(rInf, oMovedFlyIndex); + + // Asian grid stuff + SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); + const bool bHasGrid = pGrid && rInf.SnapToGrid() && + GRID_LINES_CHARS == pGrid->GetGridType(); + + + const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc(); + const sal_uInt16 nGridWidth = bHasGrid ? GetGridWidth(*pGrid, rDoc) : 0; + + // 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; + + bool bFull = false; + SwTwips nUnderLineStart = 0; + rInf.Y( Y() ); + + 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 ); + + if( ! bHasGrid && rInf.HasScriptSpace() && + rInf.GetLast() && rInf.GetLast()->InTextGrp() && + rInf.GetLast()->Width() && !rInf.GetLast()->InNumberGrp() ) + { + SwFontScript nNxtActual = rInf.GetFont()->GetActual(); + SwFontScript nLstActual = nNxtActual; + sal_uInt16 nLstHeight = o3tl::narrowing<sal_uInt16>(rInf.GetFont()->GetHeight()); + bool bAllowBehind = false; + const CharClass& rCC = GetAppCharClass(); + + // are there any punctuation characters on both sides + // of the kerning portion? + if ( pPor->InFieldGrp() ) + { + OUString aAltText; + if ( static_cast<SwFieldPortion*>(pPor)->GetExpText( rInf, aAltText ) && + !aAltText.isEmpty() ) + { + bAllowBehind = rCC.isLetterNumeric( aAltText, 0 ); + + const SwFont* pTmpFnt = static_cast<SwFieldPortion*>(pPor)->GetFont(); + if ( pTmpFnt ) + nNxtActual = pTmpFnt->GetActual(); + } + } + else + { + const OUString& rText = rInf.GetText(); + sal_Int32 nIdx = sal_Int32(rInf.GetIdx()); + bAllowBehind = nIdx < rText.getLength() && rCC.isLetterNumeric(rText, nIdx); + } + + const SwLinePortion* pLast = rInf.GetLast(); + if ( bAllowBehind && pLast ) + { + bool bAllowBefore = false; + + if ( pLast->InFieldGrp() ) + { + OUString aAltText; + if ( static_cast<const SwFieldPortion*>(pLast)->GetExpText( rInf, aAltText ) && + !aAltText.isEmpty() ) + { + bAllowBefore = rCC.isLetterNumeric( aAltText, aAltText.getLength() - 1 ); + + const SwFont* pTmpFnt = static_cast<const SwFieldPortion*>(pLast)->GetFont(); + if ( pTmpFnt ) + { + nLstActual = pTmpFnt->GetActual(); + nLstHeight = o3tl::narrowing<sal_uInt16>(pTmpFnt->GetHeight()); + } + } + } + else if ( rInf.GetIdx() ) + { + bAllowBefore = rCC.isLetterNumeric(rInf.GetText(), sal_Int32(rInf.GetIdx()) - 1); + // Note: ScriptType returns values in [1,4] + if ( bAllowBefore ) + nLstActual = SwFontScript(m_pScriptInfo->ScriptType(rInf.GetIdx() - TextFrameIndex(1)) - 1); + } + + nLstHeight /= 5; + // does the kerning portion still fit into the line? + if( bAllowBefore && ( nLstActual != nNxtActual ) && + // tdf#89288 we want to insert space between CJK and non-CJK text only. + ( nLstActual == SwFontScript::CJK || nNxtActual == SwFontScript::CJK ) && + 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; + + rInf.GetLast()->SetNextPortion( nullptr ); + InsertPortion( rInf, pKrn ); + } + } + } + else if ( bHasGrid && ! pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() ) + { + // insert a grid kerning portion + pGridKernPortion = pPor->IsKernPortion() ? + static_cast<SwKernPortion*>(pPor) : + new SwKernPortion( *m_pCurr ); + + // 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); + + const tools::Long nGridOrigin = pBody ? + aRectFnSet.GetPrtLeft(*pBody) : + aRectFnSet.GetPrtLeft(*pPageFrame); + + SwTwips nStartX = rInf.X() + GetLeftMargin(); + if ( aRectFnSet.IsVert() ) + { + Point aPoint( nStartX, 0 ); + m_pFrame->SwitchHorizontalToVertical( aPoint ); + nStartX = aPoint.Y(); + } + + const SwTwips nOfst = nStartX - nGridOrigin; + if ( nOfst ) + { + const sal_uLong i = ( nOfst > 0 ) ? + ( ( nOfst - 1 ) / nGridWidth + 1 ) : + 0; + const SwTwips nKernWidth = i * nGridWidth - nOfst; + const SwTwips nRestWidth = rInf.Width() - rInf.X(); + + if ( nKernWidth <= nRestWidth ) + pGridKernPortion->Width( nKernWidth ); + } + + if ( pGridKernPortion != pPor ) + InsertPortion( rInf, pGridKernPortion ); + } + + if( pPor->IsDropPortion() ) + MergeCharacterBorder(*static_cast<SwDropPortion*>(pPor)); + + // 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( rInf.IsRuby() && !rInf.GetRest() ) + bFull = true; + + // 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 + else if ( ( ! 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 ); + else if ( ! 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()) && + (m_pScriptInfo->ScriptType(nTmp - TextFrameIndex(1)) == css::i18n::ScriptType::ASIAN || + m_pScriptInfo->ScriptType(nTmp) == css::i18n::ScriptType::ASIAN) ) + { + 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; + } + } + } + } + } + + if ( bHasGrid && pPor != pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() ) + { + TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen(); + const SwTwips nRestWidth = rInf.Width() - rInf.X() - pPor->Width(); + + const SwFontScript nCurrScript = m_pFont->GetActual(); // pScriptInfo->ScriptType( rInf.GetIdx() ); + const SwFontScript nNextScript = + nTmp >= TextFrameIndex(rInf.GetText().getLength()) + ? SwFontScript::CJK + : m_pScriptInfo->WhichFont(nTmp); + + // 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" ); + + // calculate size + SwLinePortion* pTmpPor = pGridKernPortion->GetNextPortion(); + sal_uInt16 nSumWidth = pPor->Width(); + while ( pTmpPor ) + { + nSumWidth = nSumWidth + pTmpPor->Width(); + pTmpPor = pTmpPor->GetNextPortion(); + } + + const SwTwips i = nSumWidth ? + ( nSumWidth - 1 ) / nGridWidth + 1 : + 0; + const SwTwips nTmpWidth = i * nGridWidth; + const SwTwips nKernWidth = std::min(nTmpWidth - nSumWidth, nRestWidth); + const SwTwips nKernWidth_1 = pGrid->IsSnapToChars() ? + nKernWidth / 2 : 0; + + 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 ); + + pGridKernPortion = nullptr; + } + else if ( pPor->IsMultiPortion() || pPor->InFixMargGrp() || + pPor->IsFlyCntPortion() || pPor->InNumberGrp() || + pPor->InFieldGrp() || nCurrScript != nNextScript ) + // next portion should snap to grid + pGridKernPortion = nullptr; + } + + rInf.SetFull( bFull ); + + // 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); + } + + if( !rInf.IsStop() ) + { + // The last right centered, decimal tab + SwTabPortion *pLastTab = rInf.GetLastTab(); + if( pLastTab ) + pLastTab->FormatEOL( rInf ); + else if( rInf.GetLast() && rInf.LastKernPortion() ) + rInf.GetLast()->FormatEOL( rInf ); + } + if( m_pCurr->GetNextPortion() && m_pCurr->GetNextPortion()->InNumberGrp() + && static_cast<SwNumberPortion*>(m_pCurr->GetNextPortion())->IsHide() ) + rInf.SetNumDone( false ); + + // Delete fly in any case + ClearFly( rInf ); + + // 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 + else if ( 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() ); + } + else if (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 + const bool bFirstPor = rInf.GetLineStart() == rInf.GetIdx(); + if ( pPor->IsQuoVadisPortion() ) + bChg = SeekStartAndChg( rInf, true ); + else + { + if( bFirstPor ) + { + if( !rInf.GetText().isEmpty() ) + { + if ( pPor->GetLen() || !rInf.GetIdx() + || ( m_pCurr != pLast && !pLast->IsFlyPortion() ) + || !m_pCurr->IsRest() ) // instead of !rInf.GetRest() + bChg = SeekAndChg( rInf ); + else + bChg = SeekAndChgBefore( rInf ); + } + else if ( 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() ); + } + } + + if( pPor->InTextGrp() && bCalc ) + { + pPor->SetAscent(pPor->GetAscent() + + rInf.GetFont()->GetTopBorderSpace()); + pPor->Height(pPor->Height() + + rInf.GetFont()->GetTopBorderSpace() + + rInf.GetFont()->GetBottomBorderSpace() ); + } +} + +namespace { + +class SwMetaPortion : public SwTextPortion +{ + Color m_aShadowColor; +public: + SwMetaPortion() { SetWhichPor( PortionType::Meta ); } + virtual void Paint( const SwTextPaintInfo &rInf ) const override; + void SetShadowColor(const Color& rCol ) { m_aShadowColor = rCol; } +}; + +/// 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); + virtual void 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; +}; +} + +void SwMetaPortion::Paint( const SwTextPaintInfo &rInf ) const +{ + if ( Width() ) + { + rInf.DrawViewOpt( *this, PortionType::Meta, + // custom shading (RDF metadata) + COL_BLACK == m_aShadowColor + ? nullptr + : &m_aShadowColor ); + + SwTextPortion::Paint( rInf ); + } +} + +SwContentControlPortion::SwContentControlPortion(SwTextContentControl* pTextContentControl) + : m_pTextContentControl(pTextContentControl) +{ + SetWhichPor(PortionType::ContentControl); +} + +bool SwContentControlPortion::DescribePDFControl(const SwTextPaintInfo& rInf) const +{ + auto pPDFExtOutDevData = dynamic_cast<vcl::PDFExtOutDevData*>(rInf.GetOut()->GetExtOutDevData()); + if (!pPDFExtOutDevData) + { + return false; + } + + if (!pPDFExtOutDevData->GetIsExportFormFields()) + { + return false; + } + + if (!m_pTextContentControl) + { + return false; + } + + const SwFormatContentControl& rFormatContentControl = m_pTextContentControl->GetContentControl(); + const std::shared_ptr<SwContentControl>& pContentControl = rFormatContentControl.GetContentControl(); + if (!pContentControl) + { + return false; + } + + 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. + return false; + } + + // 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. + return true; + } + + const SwPaM aPam(*pTextNode, nEnd, *pTextNode, nStart); + static sal_Unicode const aForbidden[] = { + CH_TXTATR_BREAKWORD, + 0 + }; + const OUString aText = comphelper::string::removeAny(aPam.GetText(), aForbidden); + + 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>(); + break; + } + case SwContentControlType::CHECKBOX: + { + pDescriptor = std::make_unique<vcl::PDFWriter::CheckBoxWidget>(); + auto pCheckBoxWidget = static_cast<vcl::PDFWriter::CheckBoxWidget*>(pDescriptor.get()); + pCheckBoxWidget->Checked = pContentControl->GetChecked(); + 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; + for (const auto& rItem : pContentControl->GetListItems()) + { + pListWidget->Entries.push_back(rItem.m_aDisplayText); + if (rItem.m_aDisplayText == aText) + pListWidget->SelectedEntries.push_back(nIndex); + ++nIndex; + } + break; + } + case SwContentControlType::COMBO_BOX: + { + pDescriptor = std::make_unique<vcl::PDFWriter::ComboBoxWidget>(); + auto pComboWidget = static_cast<vcl::PDFWriter::ComboBoxWidget*>(pDescriptor.get()); + for (const auto& 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) + { + return false; + } + + const SwFont* pFont = rInf.GetFont(); + if (pFont) + { + pDescriptor->TextFont = pFont->GetActualFont(); + } + + // 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() + + aLocation = aStartRect; + aLocation.Union(aEndRect); + pDescriptor->Location = aLocation.SVRect(); + + pPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Form); + pPDFExtOutDevData->CreateControl(*pDescriptor); + pPDFExtOutDevData->EndStructureElement(); + + return true; +} + +void SwContentControlPortion::Paint(const SwTextPaintInfo& rInf) const +{ + if (Width()) + { + rInf.DrawViewOpt(*this, PortionType::ContentControl); + + if (DescribePDFControl(rInf)) + { + return; + } + + SwTextPortion::Paint(rInf); + } +} + +namespace sw::mark { + OUString ExpandFieldmark(IFieldmark* pBM) + { + if (pBM->GetFieldname() == ODF_FORMCHECKBOX) + { + ::sw::mark::ICheckboxFieldmark const*const pCheckboxFm( + dynamic_cast<ICheckboxFieldmark const*>(pBM)); + assert(pCheckboxFm); + return pCheckboxFm->IsChecked() + ? u"\u2612"_ustr + : u"\u2610"_ustr; + } + assert(pBM->GetFieldname() == ODF_FORMDROPDOWN); + const IFieldmark::parameter_map_t* const pParameters = pBM->GetParameters(); + sal_Int32 nCurrentIdx = 0; + const IFieldmark::parameter_map_t::const_iterator pResult = pParameters->find(ODF_FORMDROPDOWN_RESULT); + if(pResult != pParameters->end()) + pResult->second >>= nCurrentIdx; + + const IFieldmark::parameter_map_t::const_iterator pListEntries = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY); + if (pListEntries != pParameters->end()) + { + uno::Sequence< OUString > vListEntries; + pListEntries->second >>= vListEntries; + if (nCurrentIdx < vListEntries.getLength()) + return vListEntries[nCurrentIdx]; + } + + static constexpr OUStringLiteral vEnSpaces = u"\u2002\u2002\u2002\u2002\u2002"; + return vEnSpaces; + } +} + +SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const +{ + SwTextPortion *pPor = nullptr; + if( GetFnt()->IsTox() ) + { + pPor = new SwToxPortion; + } + else if ( GetFnt()->IsInputField() ) + { + if (rInf.GetOpt().IsFieldName()) + { + OUString aFieldName = SwFieldType::GetTypeStr(SwFieldTypesEnum::Input); + // assume this is only the *first* portion and follows will be created elsewhere => input field must start at Idx + assert(rInf.GetText()[sal_Int32(rInf.GetIdx())] == CH_TXT_ATR_INPUTFIELDSTART); + TextFrameIndex nFieldLen(-1); + for (TextFrameIndex i = rInf.GetIdx() + TextFrameIndex(1); ; ++i) + { + assert(rInf.GetText()[sal_Int32(i)] != CH_TXT_ATR_INPUTFIELDSTART); // can't nest + if (rInf.GetText()[sal_Int32(i)] == CH_TXT_ATR_INPUTFIELDEND) + { + nFieldLen = i + TextFrameIndex(1) - rInf.GetIdx(); + break; + } + } + assert(2 <= sal_Int32(nFieldLen)); + pPor = new SwFieldPortion(aFieldName, nullptr, nFieldLen); + } + else + { + pPor = new SwTextInputFieldPortion(); + } + } + else + { + if( GetFnt()->IsRef() ) + pPor = new SwRefPortion; + else if (GetFnt()->IsMeta()) + { + auto pMetaPor = new SwMetaPortion; + + // set custom LO_EXT_SHADING color, if it exists + SwTextFrame const*const pFrame(rInf.GetTextFrame()); + SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx())); + SwPaM aPam(aPosition); + uno::Reference<text::XTextContent> const xRet( + SwUnoCursorHelper::GetNestedTextContent( + *aPam.GetPointNode().GetTextNode(), aPosition.GetContentIndex(), false) ); + if (xRet.is()) + { + const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc(); + static uno::Reference< uno::XComponentContext > xContext( + ::comphelper::getProcessComponentContext()); + + static uno::Reference< rdf::XURI > xODF_SHADING( + rdf::URI::createKnown(xContext, rdf::URIs::LO_EXT_SHADING), uno::UNO_SET_THROW); + + uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess( + rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY); + + const css::uno::Reference<css::rdf::XResource> xSubject(xRet, uno::UNO_QUERY); + const uno::Reference<rdf::XRepository>& xRepository = + xDocumentMetadataAccess->getRDFRepository(); + const uno::Reference<container::XEnumeration> xEnum( + xRepository->getStatements(xSubject, xODF_SHADING, nullptr), uno::UNO_SET_THROW); + + 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; + } + else if (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 + + auto const ch(rInf.GetChar(rInf.GetIdx())); + SwTextFrame const*const pFrame(rInf.GetTextFrame()); + SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx())); + sw::mark::IFieldmark *pBM = pFrame->GetDoc().getIDocumentMarkAccess()->getInnerFieldmarkFor(aPosition); + if(pBM != nullptr && pBM->GetFieldname( ) == ODF_FORMDATE) + { + if (ch == CH_TXT_ATR_FIELDSTART) + pPor = new SwFieldFormDatePortion(pBM, true); + else if (ch == CH_TXT_ATR_FIELDSEP) + pPor = new SwFieldMarkPortion(); // it's added in DateFieldmark? + else if (ch == CH_TXT_ATR_FIELDEND) + pPor = new SwFieldFormDatePortion(pBM, false); + } + else if (ch == CH_TXT_ATR_FIELDSTART) + pPor = new SwFieldMarkPortion(); + else if (ch == CH_TXT_ATR_FIELDSEP) + pPor = new SwFieldMarkPortion(); + else if (ch == CH_TXT_ATR_FIELDEND) + pPor = new SwFieldMarkPortion(); + else if (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(); + } + else if (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. + */ + else if (pBM->GetFieldname( ) == ODF_FORMTEXT) + { + pPor = new SwFieldMarkPortion(); + } + } + } + if( !pPor ) + { + if( !rInf.X() && !m_pCurr->GetNextPortion() && !m_pCurr->GetLen() ) + pPor = m_pCurr; + else + pPor = new SwTextPortion; + } + } + } + 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 ); + + // until next attribute change: + const TextFrameIndex nNextAttr = GetNextAttr(); + TextFrameIndex nNextChg = std::min(nNextAttr, TextFrameIndex(rInf.GetText().getLength())); + + // end of script type: + const TextFrameIndex nNextScript = m_pScriptInfo->NextScriptChg(rInf.GetIdx()); + nNextChg = std::min( nNextChg, nNextScript ); + + // end of direction: + const TextFrameIndex nNextDir = m_pScriptInfo->NextDirChg(rInf.GetIdx()); + nNextChg = std::min( nNextChg, nNextDir ); + + // hidden change (potentially via bookmark): + const TextFrameIndex nNextHidden = m_pScriptInfo->NextHiddenChg(rInf.GetIdx()); + nNextChg = std::min( nNextChg, nNextHidden ); + + // bookmarks + const TextFrameIndex nNextBookmark = m_pScriptInfo->NextBookmark(rInf.GetIdx()); + nNextChg = std::min(nNextChg, nNextBookmark); + + // 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). + + pPor->SetLen(TextFrameIndex(1)); + CalcAscent( rInf, pPor ); + + const SwFont* pTmpFnt = rInf.GetFont(); + sal_Int32 nExpect = std::min( sal_Int32( pTmpFnt->GetHeight() ), + sal_Int32( pPor->GetAscent() ) ) / 8; + if ( !nExpect ) + nExpect = 1; + nExpect = sal_Int32(rInf.GetIdx()) + (rInf.GetLineWidth() / nExpect); + if (TextFrameIndex(nExpect) > rInf.GetIdx() && nNextChg > TextFrameIndex(nExpect)) + nNextChg = TextFrameIndex(std::min(nExpect, rInf.GetText().getLength())); + + // we keep an invariant during method calls: + // there are no portion ending characters like hard spaces + // or tabs in [ nLeftScanIdx, nRightScanIdx ] + if ( m_nLeftScanIdx <= rInf.GetIdx() && rInf.GetIdx() <= m_nRightScanIdx ) + { + if ( nNextChg > m_nRightScanIdx ) + nNextChg = m_nRightScanIdx = + rInf.ScanPortionEnd( m_nRightScanIdx, nNextChg ); + } + else + { + m_nLeftScanIdx = rInf.GetIdx(); + nNextChg = m_nRightScanIdx = + rInf.ScanPortionEnd( rInf.GetIdx(), nNextChg ); + } + + pPor->SetLen( nNextChg - rInf.GetIdx() ); + rInf.SetLen( pPor->GetLen() ); + return pPor; +} + +// first portions have no length +SwLinePortion *SwTextFormatter::WhichFirstPortion(SwTextFormatInfo &rInf) +{ + SwLinePortion *pPor = nullptr; + + if( rInf.GetRest() ) + { + // Tabs and fields + if( '\0' != rInf.GetHookChar() ) + return nullptr; + + pPor = rInf.GetRest(); + if( pPor->IsErgoSumPortion() ) + rInf.SetErgoDone(true); + else + if( pPor->IsFootnoteNumPortion() ) + rInf.SetFootnoteDone(true); + else + if( pPor->InNumberGrp() ) + rInf.SetNumDone(true); + + rInf.SetRest(nullptr); + m_pCurr->SetRest( true ); + return pPor; + } + + // We can stand in the follow, it's crucial that + // pFrame->GetOffset() == 0! + if( rInf.GetIdx() ) + { + // We now too can elongate FootnotePortions and ErgoSumPortions + + // 1. The ErgoSumTexts + if( !rInf.IsErgoDone() ) + { + if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() ) + pPor = NewErgoSumPortion( rInf ); + rInf.SetErgoDone( true ); + } + + // 2. Arrow portions + if( !pPor && !rInf.IsArrowDone() ) + { + if( m_pFrame->GetOffset() && !m_pFrame->IsFollow() && + rInf.GetIdx() == m_pFrame->GetOffset() ) + pPor = new SwArrowPortion( *m_pCurr ); + rInf.SetArrowDone( true ); + } + + // 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" ); + + const bool bFootnoteNum = m_pFrame->IsFootnoteNumFrame(); + rInf.GetParaPortion()->SetFootnoteNum( bFootnoteNum ); + if( bFootnoteNum ) + pPor = NewFootnoteNumPortion( rInf ); + rInf.SetFootnoteDone( true ); + } + + // 6. The ErgoSumTexts of course also exist in the TextMaster, + // it's crucial whether the SwFootnoteFrame is aFollow + if( !rInf.IsErgoDone() && !pPor && ! rInf.IsMulti() ) + { + if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() ) + pPor = NewErgoSumPortion( rInf ); + rInf.SetErgoDone( true ); + } + + // 7. The numbering + if( !rInf.IsNumDone() && !pPor ) + { + 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()) + { + const auto& bookmark = m_pScriptInfo->GetBookmarks(rInf.GetIdx()); + if (!bookmark.empty()) + { + // only for character width, maybe replaced with ] later + sal_Unicode mark = '['; + + pPor = new SwBookmarkPortion(mark, bookmark); + } + } + + return pPor; +} + +static bool lcl_OldFieldRest( const SwLineLayout* pCurr ) +{ + if( !pCurr->GetNext() ) + return false; + const SwLinePortion *pPor = pCurr->GetNext()->GetNextPortion(); + bool bRet = false; + while( pPor && !bRet ) + { + bRet = (pPor->InFieldGrp() && static_cast<const SwFieldPortion*>(pPor)->IsFollow()) || + (pPor->IsMultiPortion() && static_cast<const SwMultiPortion*>(pPor)->IsFollowField()); + if( !pPor->GetLen() ) + break; + pPor = pPor->GetNextPortion(); + } + return bRet; +} + +/* 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; + } + + // Underflow takes precedence + rInf.SetStopUnderflow( false ); + if( rInf.GetUnderflow() ) + { + OSL_ENSURE( rInf.IsFull(), "SwTextFormatter::NewPortion: underflow but not full" ); + return Underflow( rInf ); + } + + // 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 ( SwMultiCreatorId::Bidi == pCreate->nId ) + pTmp = new SwBidiPortion( nEnd, pCreate->nLevel ); + else if ( SwMultiCreatorId::Ruby == pCreate->nId ) + { + pTmp = new SwRubyPortion( *pCreate, *rInf.GetFont(), + GetTextFrame()->GetDoc().getIDocumentSettingAccess(), + nEnd, TextFrameIndex(0), rInf ); + } + else if( SwMultiCreatorId::Rotate == pCreate->nId ) + { + pTmp = new SwRotatedPortion( *pCreate, nEnd, + GetTextFrame()->IsRightToLeft() ); + GetTextFrame()->SetHasRotatedPortions(true); + } + else + pTmp = new SwDoubleLinePortion( *pCreate, nEnd ); + + pCreate.reset(); + CalcFlyWidth( rInf ); + + return pTmp; + } + } + // Tabs and Fields + sal_Unicode cChar = rInf.GetHookChar(); + + 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() ); + } + + switch( cChar ) + { + case CH_TAB: + pPor = NewTabPortion( rInf, false ); break; + + 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 sal_uInt16 nWidthOfPortionsUpToDecimalPosition = o3tl::narrowing<sal_uInt16>(rInf.X() - pLastTabPortion->GetFix() ); + static_cast<SwTabDecimalPortion*>(pLastTabPortion)->SetWidthOfPortionsUpToDecimalPosition( nWidthOfPortionsUpToDecimalPosition ); + rInf.SetTabDecimal( 0 ); + } + else + rInf.SetFull( rInf.GetLastTab()->Format( rInf ) ); + } + + if( rInf.GetRest() ) + { + if( rInf.IsFull() ) + { + rInf.SetNewLine(true); + return nullptr; + } + pPor = rInf.GetRest(); + rInf.SetRest(nullptr); + } + else + { + if( rInf.IsFull() ) + return nullptr; + pPor = NewTextPortion( rInf ); + } + break; + } + } + + // 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)); + + // robust: + if( !pPor || rInf.IsStop() ) + { + delete pPor; + return nullptr; + } + } + + 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(); + + if ( pTextFootnote ) + { + SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(pTextFootnote->GetFootnote()); + const SwDoc *const pDoc = &rInf.GetTextFrame()->GetDoc(); + const SwEndNoteInfo* pInfo; + if( rFootnote.IsEndNote() ) + pInfo = &pDoc->GetEndNoteInfo(); + else + pInfo = &pDoc->GetFootnoteInfo(); + const SwAttrSet& rSet = pInfo->GetAnchorCharFormat(const_cast<SwDoc&>(*pDoc))->GetAttrSet(); + + Degree10 nDir(0); + if( const SvxCharRotateItem* pItem = rSet.GetItemIfSet( RES_CHRATR_ROTATE ) ) + nDir = pItem->GetValue(); + + if ( nDir ) + { + delete pPor; + pPor = new SwRotatedPortion(rInf.GetIdx() + TextFrameIndex(1), + 900_deg10 == nDir + ? DIR_BOTTOM2TOP + : DIR_TOP2BOTTOM ); + } + } + } + else if ( pPor->InNumberGrp() ) + { + const SwFont* pNumFnt = static_cast<SwFieldPortion*>(pPor)->GetFont(); + + if ( pNumFnt ) + { + Degree10 nDir = pNumFnt->GetOrientation( rInf.GetTextFrame()->IsVertical() ); + if ( nDir ) + { + delete pPor; + pPor = new SwRotatedPortion(TextFrameIndex(0), 900_deg10 == nDir + ? DIR_BOTTOM2TOP + : DIR_TOP2BOTTOM ); + + rInf.SetNumDone( false ); + rInf.SetFootnoteDone( false ); + } + } + } + } + + // 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() ); + } + + OSL_ENSURE(pPor->Height(), "SwTextFormatter::NewPortion: something went wrong"); + if( pPor->IsPostItsPortion() && rInf.X() >= rInf.Width() && rInf.GetFly() ) + { + delete pPor; + pPor = rInf.GetFly(); + } + return pPor; +} + +TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) +{ + OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(), + "SwTextFormatter::FormatLine( nStartPos ) with unswapped frame" ); + + // 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())); + + bool bBuild = true; + SetFlyInCntBase( false ); + GetInfo().SetLineHeight( 0 ); + GetInfo().SetLineNetHeight( 0 ); + + // 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(); + + m_pCurr->SetEndHyph( false ); + m_pCurr->SetMidHyph( false ); + + // 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; + + if ( pField && pField->InFieldGrp() && !pField->IsFootnotePortion() ) + xSaveField.reset(new SwFieldPortion( *static_cast<SwFieldPortion*>(pField) )); + + // for an optimal repaint rectangle, we want to compare fly portions + // before and after the BuildPortions call + const bool 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 ); + + nPOfst += pPor->Width(); + pPor = pPor->GetNextPortion(); + } + } + + // Here soon the underflow check follows. + while( bBuild ) + { + GetInfo().SetFootnoteInside( false ); + GetInfo().SetOtherThanFootnoteInside( false ); + + // These values must not be reset by FormatReset(); + const bool bOldNumDone = GetInfo().IsNumDone(); + const bool bOldFootnoteDone = GetInfo().IsFootnoteDone(); + const bool bOldArrowDone = GetInfo().IsArrowDone(); + const bool bOldErgoDone = GetInfo().IsErgoDone(); + + // besides other things, this sets the repaint offset to 0 + FormatReset( GetInfo() ); + + GetInfo().SetNumDone( bOldNumDone ); + GetInfo().SetFootnoteDone(bOldFootnoteDone); + GetInfo().SetArrowDone( bOldArrowDone ); + GetInfo().SetErgoDone( bOldErgoDone ); + + // build new portions for this line + BuildPortions( GetInfo() ); + + if( GetInfo().IsStop() ) + { + m_pCurr->SetLen(TextFrameIndex(0)); + m_pCurr->Height( GetFrameRstHeight() + 1, false ); + m_pCurr->SetRealHeight( GetFrameRstHeight() + 1 ); + + // 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()); + } + + m_pCurr->Width(0); + m_pCurr->Truncate(); + return nStartPos; + } + else if( GetInfo().IsDropInit() ) + { + DropInit(); + GetInfo().SetDropInit( false ); + } + + m_pCurr->CalcLine( *this, GetInfo() ); + CalcRealHeight( GetInfo().IsNewLine() ); + + //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(); + + // delete old rest + if ( GetInfo().GetRest() ) + { + delete GetInfo().GetRest(); + GetInfo().SetRest( nullptr ); + } + + // set original rest portion + if ( xSaveField ) + GetInfo().SetRest( new SwFieldPortion( *xSaveField ) ); + + m_pCurr->SetLen(TextFrameIndex(0)); + m_pCurr->Width(0); + m_pCurr->Truncate(); + } + } + } + + // 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)) + { + sal_uInt16 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(); + + TextFrameIndex const nNewStart = nStartPos + m_pCurr->GetLen(); + + // adjust text if kana compression is enabled + if ( GetInfo().CompressLine() ) + { + SwTwips nRepaintOfst = CalcKanaAdj( m_pCurr ); + + // adjust repaint offset + if ( nRepaintOfst < GetInfo().GetPaintOfst() ) + GetInfo().SetPaintOfst( nRepaintOfst ); + } + + CalcAdjustLine( m_pCurr ); + + 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 ); + } + + if ( IsFlyInCntBase() && !IsQuick() ) + UpdatePos( m_pCurr, GetTopLeft(), GetStart() ); + + return nNewStart; +} + +void SwTextFormatter::RecalcRealHeight() +{ + do + { + CalcRealHeight(); + } while (Next()); +} + +void SwTextFormatter::CalcRealHeight( bool bNewLine ) +{ + SwTwips nLineHeight = m_pCurr->Height(); + m_pCurr->SetClipping( false ); + + SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); + if ( pGrid && GetInfo().SnapToGrid() ) + { + const sal_uInt16 nGridWidth = pGrid->GetBaseHeight(); + const sal_uInt16 nRubyHeight = pGrid->GetRubyHeight(); + const bool bRubyTop = ! pGrid->GetRubyTextBelow(); + + nLineHeight = nGridWidth + nRubyHeight; + const sal_uInt16 nAmpRatio = (m_pCurr->Height() + nLineHeight - 1)/nLineHeight; + nLineHeight *= nAmpRatio; + + const sal_uInt16 nAsc = m_pCurr->GetAscent() + + ( bRubyTop ? + ( nLineHeight - m_pCurr->Height() + nRubyHeight ) / 2 : + ( nLineHeight - m_pCurr->Height() - nRubyHeight ) / 2 ); + + m_pCurr->Height( nLineHeight, false ); + m_pCurr->SetAscent( nAsc ); + m_pInf->GetParaPortion()->SetFixLineHeight(); + + // we ignore any line spacing options except from ... + const SvxLineSpacingItem* pSpace = m_aLineInf.GetLineSpacing(); + 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; + sal_uInt16 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 sal_uInt16 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; + + // 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; + break; + } + case SvxInterLineSpaceRule::Fix: + { + nLineHeight = nLineHeight + pSpace->GetInterLineSpace(); + break; + } + default: OSL_FAIL( ": unknown InterLineSpaceRule" ); + } + } + + if( IsRegisterOn() ) + { + SwTwips nTmpY = Y() + m_pCurr->GetAscent() + nLineHeight - m_pCurr->Height(); + SwRectFnSet aRectFnSet(m_pFrame); + if ( aRectFnSet.IsVert() ) + nTmpY = m_pFrame->SwitchHorizontalToVertical( nTmpY ); + nTmpY = aRectFnSet.YDiff( nTmpY, RegStart() ); + const sal_uInt16 nDiff = sal_uInt16( nTmpY % RegDiff() ); + if( nDiff ) + nLineHeight += RegDiff() - nDiff; + } + } + m_pCurr->SetRealHeight( nLineHeight ); +} + +void SwTextFormatter::FeedInf( SwTextFormatInfo &rInf ) const +{ + // delete Fly in any case! + ClearFly( rInf ); + rInf.Init(); + + rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() ); + rInf.SetRoot( m_pCurr ); + rInf.SetLineStart( m_nStart ); + rInf.SetIdx( m_nStart ); + rInf.Left( Left() ); + rInf.Right( Right() ); + rInf.First( FirstLeft() ); + rInf.LeftMargin(GetLeftMargin()); + + rInf.RealWidth( sal_uInt16(rInf.Right() - GetLeftMargin()) ); + rInf.Width( rInf.RealWidth() ); + if( const_cast<SwTextFormatter*>(this)->GetRedln() ) + { + const_cast<SwTextFormatter*>(this)->GetRedln()->Clear( const_cast<SwTextFormatter*>(this)->GetFnt() ); + const_cast<SwTextFormatter*>(this)->GetRedln()->Reset(); + } +} + +void SwTextFormatter::FormatReset( SwTextFormatInfo &rInf ) +{ + m_pFirstOfBorderMerge = nullptr; + m_pCurr->Truncate(); + m_pCurr->Init(); + + // delete pSpaceAdd and pKanaComp + m_pCurr->FinishSpaceAdd(); + m_pCurr->FinishKanaComp(); + m_pCurr->ResetFlags(); + FeedInf( rInf ); +} + +bool SwTextFormatter::CalcOnceMore() +{ + if( m_pDropFormat ) + { + const sal_uInt16 nOldDrop = GetDropHeight(); + CalcDropHeight( m_pDropFormat->GetLines() ); + m_bOnceMore = nOldDrop != GetDropHeight(); + } + else + m_bOnceMore = false; + return m_bOnceMore; +} + +SwTwips SwTextFormatter::CalcBottomLine() const +{ + SwTwips nRet = Y() + GetLineHeight(); + SwTwips nMin = GetInfo().GetTextFly().GetMinBottom(); + if( nMin && ++nMin > nRet ) + { + SwTwips nDist = m_pFrame->getFrameArea().Height() - m_pFrame->getFramePrintArea().Height() + - m_pFrame->getFramePrintArea().Top(); + if( nRet + nDist < nMin ) + { + const bool bRepaint = HasTruncLines() && + GetInfo().GetParaPortion()->GetRepaint().Bottom() == nRet-1; + nRet = nMin - nDist; + if( bRepaint ) + { + const_cast<SwRepaint&>(GetInfo().GetParaPortion() + ->GetRepaint()).Bottom( nRet-1 ); + const_cast<SwTextFormatInfo&>(GetInfo()).SetPaintOfst( 0 ); + } + } + } + return nRet; +} + +// 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: ; + } + } + + // Again another special case: invisible SoftHyphs + const TextFrameIndex nReformat = GetInfo().GetReformatStart(); + if (bOptimizeRepaint && TextFrameIndex(COMPLETE_STRING) != nReformat) + { + const sal_Unicode cCh = nReformat >= TextFrameIndex(GetInfo().GetText().getLength()) + ? 0 + : GetInfo().GetText()[ sal_Int32(nReformat) ]; + bOptimizeRepaint = ( CH_TXTATR_BREAKWORD != cCh && CH_TXTATR_INWORD != cCh ) + || ! GetInfo().HasHint( nReformat ); + } + + return bOptimizeRepaint; +} + +void SwTextFormatter::CalcUnclipped( SwTwips& rTop, SwTwips& rBottom ) +{ + OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(), + "SwTextFormatter::CalcUnclipped with unswapped frame" ); + + SwTwips nFlyAsc, nFlyDesc; + m_pCurr->MaxAscentDescent( rTop, rBottom, nFlyAsc, nFlyDesc ); + rTop = Y() + GetCurr()->GetAscent(); + rBottom = rTop + nFlyDesc; + rTop -= nFlyAsc; +} + +void SwTextFormatter::UpdatePos( SwLineLayout *pCurrent, Point aStart, + TextFrameIndex const nStartIdx, bool bAlways) const +{ + OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(), + "SwTextFormatter::UpdatePos with unswapped frame" ); + + if( GetInfo().IsTest() ) + return; + SwLinePortion *pFirst = pCurrent->GetFirstPortion(); + SwLinePortion *pPos = pFirst; + SwTextPaintInfo aTmpInf( GetInfo() ); + aTmpInf.SetpSpaceAdd( pCurrent->GetpLLSpaceAdd() ); + aTmpInf.ResetSpaceIdx(); + aTmpInf.SetKanaComp( pCurrent->GetpKanaComp() ); + aTmpInf.ResetKanaIdx(); + + // The frame's size + aTmpInf.SetIdx( nStartIdx ); + aTmpInf.SetPos( aStart ); + + SwTwips nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc; + pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc ); + + const SwTwips nTmpHeight = pCurrent->GetRealHeight(); + SwTwips nAscent = pCurrent->GetAscent() + nTmpHeight - pCurrent->Height(); + AsCharFlags nFlags = AsCharFlags::UlSpace; + if( GetMulti() ) + { + aTmpInf.SetDirection( GetMulti()->GetDirection() ); + if( GetMulti()->HasRotation() ) + { + nFlags |= AsCharFlags::Rotate; + if( GetMulti()->IsRevers() ) + { + nFlags |= AsCharFlags::Reverse; + aTmpInf.X( aTmpInf.X() - nAscent ); + } + else + aTmpInf.X( aTmpInf.X() + nAscent ); + } + else + { + if ( GetMulti()->IsBidi() ) + nFlags |= AsCharFlags::Bidi; + aTmpInf.Y( aTmpInf.Y() + nAscent ); + } + } + else + aTmpInf.Y( aTmpInf.Y() + nAscent ); + + 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 ); + + if( pPos->IsGrfNumPortion() ) + { + if( !nFlyAsc && !nFlyDesc ) + { + nTmpAscent = nAscent; + nFlyAsc = nAscent; + nTmpDescent = nTmpHeight - nAscent; + nFlyDesc = nTmpDescent; + } + static_cast<SwGrfNumPortion*>(pPos)->SetBase( nTmpAscent, nTmpDescent, + nFlyAsc, nFlyDesc ); + } + else + { + Point aBase( aTmpInf.GetPos() ); + if ( GetInfo().GetTextFrame()->IsVertical() ) + GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aBase ); + + static_cast<SwFlyCntPortion*>(pPos)->SetBase( *aTmpInf.GetTextFrame(), + aBase, nTmpAscent, nTmpDescent, nFlyAsc, + nFlyDesc, nFlags ); + } + } + if( pPos->IsMultiPortion() && static_cast<SwMultiPortion*>(pPos)->HasFlyInContent() ) + { + OSL_ENSURE( !GetMulti(), "Too much multi" ); + const_cast<SwTextFormatter*>(this)->m_pMulti = static_cast<SwMultiPortion*>(pPos); + SwLineLayout *pLay = &GetMulti()->GetRoot(); + Point aSt( aTmpInf.X(), aStart.Y() ); + + if ( GetMulti()->HasBrackets() ) + { + OSL_ENSURE( GetMulti()->IsDouble(), "Brackets only for doubles"); + aSt.AdjustX(static_cast<SwDoubleLinePortion*>(GetMulti())->PreWidth() ); + } + else if( GetMulti()->HasRotation() ) + { + aSt.AdjustY(pCurrent->GetAscent() - GetMulti()->GetAscent() ); + if( GetMulti()->IsRevers() ) + aSt.AdjustX(GetMulti()->Width() ); + else + aSt.AdjustY(GetMulti()->Height() ); + } + else if ( GetMulti()->IsBidi() ) + // jump to end of the bidi portion + aSt.AdjustX(pLay->Width() ); + + TextFrameIndex nStIdx = aTmpInf.GetIdx(); + do + { + UpdatePos( pLay, aSt, nStIdx, bAlways ); + nStIdx = nStIdx + pLay->GetLen(); + aSt.AdjustY(pLay->Height() ); + pLay = pLay->GetNext(); + } while ( pLay ); + const_cast<SwTextFormatter*>(this)->m_pMulti = nullptr; + } + pPos->Move( aTmpInf ); + pPos = pPos->GetNextPortion(); + } +} + +void SwTextFormatter::AlignFlyInCntBase( tools::Long nBaseLine ) const +{ + OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(), + "SwTextFormatter::AlignFlyInCntBase with unswapped frame" ); + + if( GetInfo().IsTest() ) + return; + SwLinePortion *pFirst = m_pCurr->GetFirstPortion(); + SwLinePortion *pPos = pFirst; + AsCharFlags nFlags = AsCharFlags::None; + if( GetMulti() && GetMulti()->HasRotation() ) + { + nFlags |= AsCharFlags::Rotate; + if( GetMulti()->IsRevers() ) + nFlags |= AsCharFlags::Reverse; + } + + SwTwips nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc; + + while( pPos ) + { + if( pPos->IsFlyCntPortion() || pPos->IsGrfNumPortion() ) + { + m_pCurr->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, pPos ); + + if( pPos->IsGrfNumPortion() ) + static_cast<SwGrfNumPortion*>(pPos)->SetBase( nTmpAscent, nTmpDescent, + nFlyAsc, nFlyDesc ); + else + { + Point aBase; + if ( GetInfo().GetTextFrame()->IsVertical() ) + { + nBaseLine = GetInfo().GetTextFrame()->SwitchHorizontalToVertical( nBaseLine ); + aBase = Point( nBaseLine, static_cast<SwFlyCntPortion*>(pPos)->GetRefPoint().Y() ); + } + else + aBase = Point( static_cast<SwFlyCntPortion*>(pPos)->GetRefPoint().X(), nBaseLine ); + + static_cast<SwFlyCntPortion*>(pPos)->SetBase( *GetInfo().GetTextFrame(), aBase, nTmpAscent, nTmpDescent, + nFlyAsc, nFlyDesc, nFlags ); + } + } + pPos = pPos->GetNextPortion(); + } +} + +bool SwTextFormatter::ChkFlyUnderflow( SwTextFormatInfo &rInf ) const +{ + OSL_ENSURE( rInf.GetTextFly().IsOn(), "SwTextFormatter::ChkFlyUnderflow: why?" ); + if( GetCurr() ) + { + // First we check, whether a fly overlaps with the line. + // = GetLineHeight() + const sal_uInt16 nHeight = GetCurr()->GetRealHeight(); + SwRect aLine( GetLeftMargin(), Y(), rInf.RealWidth(), nHeight ); + + SwRect aLineVert( aLine ); + if ( m_pFrame->IsVertical() ) + m_pFrame->SwitchHorizontalToVertical( aLineVert ); + SwRect aInter( rInf.GetTextFly().GetFrame( aLineVert ) ); + if ( m_pFrame->IsVertical() ) + m_pFrame->SwitchVerticalToHorizontal( aInter ); + + if( !aInter.HasArea() ) + return false; + + // 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() ); + + while( pPos ) + { + aLine.Width( pPos->Width() ); + + aLineVert = aLine; + if ( m_pFrame->IsVertical() ) + m_pFrame->SwitchHorizontalToVertical( aLineVert ); + aInter = rInf.GetTextFly().GetFrame( aLineVert ); + if ( m_pFrame->IsVertical() ) + m_pFrame->SwitchVerticalToHorizontal( aInter ); + + // 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() ); + return true; + } + } + } + else + { + // The fly portion is not intersected by a fly anymore + if ( ! aInter.Overlaps( aLine ) ) + { + rInf.SetLineHeight( nHeight ); + rInf.SetLineNetHeight( m_pCurr->Height() ); + return true; + } + 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() ); + return true; + } + } + } + + aLine.Left( aLine.Left() + pPos->Width() ); + pPos = pPos->GetNextPortion(); + } + } + return false; +} + +void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf ) +{ + if( GetMulti() || rInf.GetFly() ) + return; + + SwTextFly& rTextFly = rInf.GetTextFly(); + if( !rTextFly.IsOn() || rInf.IsIgnoreFly() ) + return; + + const SwLinePortion *pLast = rInf.GetLast(); + + tools::Long nAscent; + tools::Long nTop = Y(); + tools::Long nHeight; + + 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(); + + nAscent = pLast->GetAscent(); + nHeight = pLast->Height(); + + if ( m_pCurr->GetRealHeight() > nHeight ) + nTop += m_pCurr->GetRealHeight() - nHeight; + else + // Important for fixed space between lines + nHeight = m_pCurr->GetRealHeight(); + } + + const tools::Long nLeftMar = GetLeftMargin(); + const tools::Long nLeftMin = (rInf.X() || GetDropLeft()) ? nLeftMar : GetLeftMin(); + + SwRect aLine( rInf.X() + nLeftMin, nTop, rInf.RealWidth() - rInf.X() + + nLeftMar - nLeftMin , nHeight ); + + 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); + } + } + + SwRect aLineVert( aLine ); + if ( m_pFrame->IsRightToLeft() ) + m_pFrame->SwitchLTRtoRTL( aLineVert ); + + 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 (const auto& 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); + } + } + + tools::Long nAddMar = 0; + if (GetTextFrame()->IsRightToLeft()) + { + nAddMar = GetTextFrame()->getFrameArea().Right() - Right(); + if ( nAddMar < 0 ) + nAddMar = 0; + } + else + nAddMar = nLeftMar - nFrameLeft; + + 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; + } + + bFullLine = std::abs(aLine.Left() - aInter.Left()) < nLimit + && std::abs(aLine.Right() - aInter.Right()) < nLimit; + } + + // 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); + } + + // aInter becomes frame-local + aInter.Pos().AdjustX( -nLeftMar ); + SwFlyPortion *pFly = new SwFlyPortion( aInter ); + if( bForced ) + { + m_pCurr->SetForcedLeftMargin(); + rInf.ForcedLeftMargin( o3tl::narrowing<sal_uInt16>(aInter.Width()) ); + } + + 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() ); + } + } + + rInf.SetFly( pFly ); + + if( pFly->GetFix() < rInf.Width() ) + rInf.Width( pFly->GetFix() ); + + SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); + if ( !pGrid ) + return; + + const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame(); + const SwLayoutFrame* pBody = pPageFrame->FindBodyCont(); + + SwRectFnSet aRectFnSet(pPageFrame); + + const tools::Long nGridOrigin = pBody ? + aRectFnSet.GetPrtLeft(*pBody) : + aRectFnSet.GetPrtLeft(*pPageFrame); + + const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc(); + const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, rDoc); + + SwTwips nStartX = GetLeftMargin(); + if ( aRectFnSet.IsVert() ) + { + Point aPoint( nStartX, 0 ); + m_pFrame->SwitchHorizontalToVertical( aPoint ); + nStartX = aPoint.Y(); + } + + const SwTwips nOfst = nStartX - nGridOrigin; + const SwTwips nTmpWidth = rInf.Width() + nOfst; + + const SwTwips i = nTmpWidth / nGridWidth + 1; + + const SwTwips nNewWidth = ( i - 1 ) * nGridWidth - nOfst; + if ( nNewWidth > 0 ) + rInf.Width( nNewWidth ); + else + rInf.Width( 0 ); + + +} + +SwFlyCntPortion *SwTextFormatter::NewFlyCntPortion( SwTextFormatInfo &rInf, + SwTextAttr *pHint ) const +{ + const SwFrame *pFrame = m_pFrame; + + 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; + + const bool bTextFrameVertical = GetInfo().GetTextFrame()->IsVertical(); + + const bool bUseFlyAscent = pFly && pFly->isFrameAreaPositionValid() && + 0 != ( bTextFrameVertical ? + pFly->GetRefPoint().X() : + pFly->GetRefPoint().Y() ); + + if ( bUseFlyAscent ) + nAscent = std::abs( int( bTextFrameVertical ? + pFly->GetRelPos().X() : + pFly->GetRelPos().Y() ) ); + + // Check if be prefer to use the ascent of the last portion: + if ( IsQuick() || + !bUseFlyAscent || + nAscent < rInf.GetLast()->GetAscent() ) + { + nAscent = rInf.GetLast()->GetAscent(); + } + else if( nAscent > nFlyAsc ) + nFlyAsc = nAscent; + + Point aBase( GetLeftMargin() + rInf.X(), Y() + nAscent ); + AsCharFlags nMode = IsQuick() ? AsCharFlags::Quick : AsCharFlags::None; + if( GetMulti() && GetMulti()->HasRotation() ) + { + nMode |= AsCharFlags::Rotate; + if( GetMulti()->IsRevers() ) + nMode |= AsCharFlags::Reverse; + } + + 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 ); + + pRet->SetBase( *rInf.GetTextFrame(), aTmpBase, nTmpAscent, + nTmpDescent, nFlyAsc, nFlyDesc, nMode ); + } + } + } + else + { + pRet = sw::DrawFlyCntPortion::Create(*rInf.GetTextFrame(), *pFrameFormat, aTmpBase, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, nMode); + } + return pRet; +} + +/* 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; + + SwDropPortionPart* pCurrPart = rPortion.GetPart(); + while( pCurrPart ) + { + if( pCurrPart->GetFollow() && + ::lcl_HasSameBorder(pCurrPart->GetFont(), pCurrPart->GetFollow()->GetFont()) ) + { + pCurrPart->SetJoinBorderWithNext(true); + pCurrPart->GetFollow()->SetJoinBorderWithPrev(true); + } + pCurrPart = pCurrPart->GetFollow(); + } +} + +void SwTextFormatter::MergeCharacterBorder( SwLinePortion& rPortion, SwLinePortion const *pPrev, SwTextFormatInfo& rInf ) +{ + const SwFont aCurFont = *rInf.GetFont(); + if( !aCurFont.HasBorder() ) + 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 = false; + if (!rInf.IsFull() && // Not the last portion of the line (in case of line break) + rInf.GetIdx() + rPortion.GetLen() != TextFrameIndex(rInf.GetText().getLength())) // Not the last portion of the paragraph + { + 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; + sal_uInt16 nMaxAscent = 0; + sal_uInt16 nMaxHeight = 0; + bool bReachCurrent = false; + while( pActPor ) + { + if( nMaxHeight < pActPor->Height() ) + nMaxHeight = pActPor->Height(); + if( nMaxAscent < pActPor->GetAscent() ) + nMaxAscent = pActPor->GetAscent(); + + pActPor = pActPor->GetNextPortion(); + if( !pActPor && !bReachCurrent ) + { + pActPor = &rPortion; + bReachCurrent = true; + } + } + + // Change all portion's height and ascent + pActPor = m_pFirstOfBorderMerge; + bReachCurrent = false; + while( pActPor ) + { + if( nMaxHeight > pActPor->Height() ) + pActPor->Height(nMaxHeight); + if( nMaxAscent > pActPor->GetAscent() ) + pActPor->SetAscent(nMaxAscent); + + pActPor = pActPor->GetNextPortion(); + if( !pActPor && !bReachCurrent ) + { + pActPor = &rPortion; + bReachCurrent = true; + } + } + m_pFirstOfBorderMerge = nullptr; + } + } + Seek(rInf.GetIdx()); +} + +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; + + TextFrameIndex nReformat = std::min(txtFormatInfo.GetReformatStart(), nOldLineEnd); + + // 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!) + static const 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; + + nCnt++; + } + nX = nX + pPor->Width(); + nIdx = nIdx + pPor->GetLen(); + pPor = pPor->GetNextPortion(); + } + + return nPOfst + rThis.GetLeftMargin(); + } + } + + // 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() ) + const bool bShowInDocView = rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar(); + const bool bShowForPrinting = rInf.GetOpt().IsShowHiddenChar( true ) && rInf.GetOpt().IsPrinting(); + if (bShowInDocView || bShowForPrinting) + return false; + + const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo(); + TextFrameIndex nHiddenStart; + TextFrameIndex nHiddenEnd; + rSI.GetBoundsOfHiddenRange( rPos, nHiddenStart, nHiddenEnd ); + if ( nHiddenEnd ) + { + rPos = nHiddenEnd; + return true; + } + + return false; + } + + bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond) + { + return + rFirst.GetTopBorder() == rSecond.GetTopBorder() && + rFirst.GetBottomBorder() == rSecond.GetBottomBorder() && + rFirst.GetLeftBorder() == rSecond.GetLeftBorder() && + rFirst.GetRightBorder() == rSecond.GetRightBorder() && + rFirst.GetTopBorderDist() == rSecond.GetTopBorderDist() && + rFirst.GetBottomBorderDist() == rSecond.GetBottomBorderDist() && + rFirst.GetLeftBorderDist() == rSecond.GetLeftBorderDist() && + rFirst.GetRightBorderDist() == rSecond.GetRightBorderDist() && + rFirst.GetOrientation() == rSecond.GetOrientation() && + rFirst.GetShadowColor() == rSecond.GetShadowColor() && + rFirst.GetShadowWidth() == rSecond.GetShadowWidth() && + rFirst.GetShadowLocation() == rSecond.GetShadowLocation(); + } + +} //end unnamed namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |