/* -*- 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 .
*/
void SwTextAdjuster::FormatBlock( )
{ // Block format does not apply to the last line. // And for tabs it doesn't exist out of tradition // If we have Flys we continue.
const SwLinePortion *pFly = nullptr;
bool bSkip = !IsLastBlock() && // don't skip, if the last paragraph line needs space shrinking
m_pCurr->ExtraShrunkWidth() <= m_pCurr->Width() &&
m_nStart + m_pCurr->GetLen() >= TextFrameIndex(GetInfo().GetText().getLength());
// tdf#162725 if the last line is longer, than the paragraph width, // it contains shrinking spaces: don't skip block format here if( bSkip )
{ // sum width of the text portions to calculate the line width without shrinking
tools::Long nBreakWidth = 0; const SwLinePortion *pPos = m_pCurr->GetNextPortion(); while( pPos && bSkip )
{ if( !pPos->InGlueGrp() && // don't calculate with the terminating space, // otherwise it would result justified line mistakenly
( pPos->GetNextPortion() || !pPos->IsHolePortion() ) )
{
nBreakWidth += pPos->Width();
}
// Multi-line fields are tricky, because we need to check whether there are // any other text portions in the paragraph. if( bSkip )
{ const SwLineLayout *pLay = m_pCurr->GetNext(); while( pLay && !pLay->GetLen() )
{ const SwLinePortion *pPor = m_pCurr->GetFirstPortion(); while( pPor && bSkip )
{ if( pPor->InTextGrp() )
bSkip = false;
pPor = pPor->GetNextPortion();
}
pLay = bSkip ? pLay->GetNext() : nullptr;
}
}
// End at the last Fly const SwLinePortion *pPos = m_pCurr->GetFirstPortion(); while( pPos )
{ // Look for the last Fly which has text coming after it: if( pPos->IsFlyPortion() )
pTmpFly = pPos; // Found a Fly elseif ( pTmpFly && pPos->InTextGrp() )
{
pFly = pTmpFly; // A Fly with follow-up text!
pTmpFly = nullptr;
}
pPos = pPos->GetNextPortion();
} // End if we didn't find one if( !pFly )
{ if( IsLastCenter() )
CalcFlyAdjust( m_pCurr );
m_pCurr->FinishSpaceAdd(); return;
}
}
}
std::vector<bool> aValidPositions; while (aScanner.NextWord())
{ const OUString& rWord = aScanner.GetWord();
// Fetch the set of valid positions from VCL, where possible if (SwScriptInfo::IsKashidaScriptText(rInf.GetText(), TextFrameIndex{ aScanner.GetBegin() },
TextFrameIndex{ aScanner.GetLen() }))
{
aValidPositions.clear();
auto stKashidaPos = i18nutil::GetWordKashidaPosition(rWord, aValidPositions); if (stKashidaPos.has_value())
{
TextFrameIndex nNewKashidaPos{ aScanner.GetBegin() + stKashidaPos->nIndex };
// tdf#164098: The above algorithm can return out-of-range kashida positions. This // can happen if, for example, a single word is split across multiple lines, and // the best kashida candidate position is on the first line. if (nNewKashidaPos >= nIdx && nNewKashidaPos < nEnd)
{
aKashidaPositions.push_back(nNewKashidaPos - nLineBaseIndex);
aKashidaWidths.push_back(nFontMinKashida);
nMaxKashidaWidth = std::max(nMaxKashidaWidth, nFontMinKashida);
}
}
}
}
// The line may not have enough extra space for all possible kashida. // Remove them from the beginning of the line to the end.
std::reverse(aKashidaPositions.begin(), aKashidaPositions.end());
std::reverse(aKashidaWidths.begin(), aKashidaWidths.end());
while (nGluePortion && !aKashidaPositions.empty())
{
tools::Long nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion); if (nSpaceAdd / SPACING_PRECISION_FACTOR >= nMaxKashidaWidth)
{ break;
}
// CalcNewBlock() must only be called _after_ CalcLine()! // We always span between two RandPortions or FixPortions (Tabs and Flys). // We count the Glues and call ExpandBlock. void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent, const SwLinePortion *pStopAt, SwTwips nReal, bool bSkipKashida )
{
OSL_ENSURE( GetInfo().IsMulti() || SvxAdjust::Block == GetAdjust(), "CalcNewBlock: Why?" );
OSL_ENSURE( pCurrent->Height(), "SwTextAdjuster::CalcBlockAdjust: missing CalcLine()" );
switch ( pPos->GetWhichPor() )
{ case PortionType::TabCenter : case PortionType::TabRight : case PortionType::TabDecimal :
bDoNotJustifyTab = true; break; case PortionType::TabLeft : case PortionType::Break:
bDoNotJustifyTab = false; break; default: break;
}
if ( pPos->InTextGrp() )
nGluePortion = nGluePortion + static_cast<SwTextPortion*>(pPos)->GetSpaceCnt( GetInfo(), nCharCnt ); elseif( pPos->IsMultiPortion() )
{
SwMultiPortion* pMulti = static_cast<SwMultiPortion*>(pPos); // a multiportion with a tabulator inside breaks the text adjustment // a ruby portion will not be stretched by text adjustment // a double line portion takes additional space for each blank // in the wider line if( pMulti->HasTabulator() )
{ if ( nSpaceIdx == pCurrent->GetLLSpaceAddCount() )
pCurrent->SetLLSpaceAdd( 0, nSpaceIdx );
if (rSI.ParagraphContainsKashidaScript() && !bSkipKashida)
{ if (!lcl_ComputeKashidaPositions(aInf, aItr, nGluePortion, nGluePortionWidth,
pCurrent, nLineBase))
{ // no kashidas left // do regular blank justification
pCurrent->FinishSpaceAdd();
GetInfo().SetIdx(m_nStart);
CalcNewBlock(pCurrent, pStopAt, nReal, true); return;
}
}
if( nGluePortion )
{
tools::Long nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion); // shrink, if not shrunk line width exceed the set line width // i.e. if pCurrent->ExtraShrunkWidth() > 0 // tdf#163720 but at hyphenated lines, still nBreakWidth contains the correct // not shrunk line width (ExtraShrunkWidth + hyphen length), so use that if ( pCurrent->ExtraShrunkWidth() > nBreakWidth )
nBreakWidth = pCurrent->ExtraShrunkWidth(); // shrink, if portions exceed the line width
tools::Long nSpaceSub = ( nBreakWidth > pCurrent->Width() )
? (nBreakWidth - pCurrent->Width()) * SPACING_PRECISION_FACTOR /
sal_Int32(nGluePortion) + LONG_MAX/2
: ( nSpaceAdd < 0 ) // shrink, if portions exceed the line width available before an image
? -nSpaceAdd + LONG_MAX/2
: 0;
// tdf#164140: Rebuild kashida position indices after line adjustment if (rSI.ParagraphContainsKashidaScript())
{
std::vector<TextFrameIndex> aKashidaPositions;
// Do not forget: CalcRightMargin() sets pCurrent->Width() to the line width!
CalcRightMargin( pCurrent );
SwLinePortion* pPos = pCurrent->GetNextPortion();
while( pPos )
{ if ( pPos->InTextGrp() )
{ // get maximum portion width from info structure, calculated // during text formatting
SwTwips nMaxWidthDiff = GetInfo().GetMaxWidthDiff(pPos);
// check, if information is stored under other key if ( !nMaxWidthDiff && pPos == pCurrent->GetFirstPortion() )
nMaxWidthDiff = GetInfo().GetMaxWidthDiff( pCurrent );
// calculate difference between portion width and max. width
nKanaDiffSum += nMaxWidthDiff;
// we store the beginning of the first compressible portion // for repaint if ( nMaxWidthDiff && !nRepaintOfst )
nRepaintOfst = nX + GetLeftMargin();
} elseif( pPos->InGlueGrp() && pPos->InFixMargGrp() )
{ if ( nKanaIdx == pCurrent->GetKanaComp().size() )
pCurrent->GetKanaComp().push_back( nNull );
// for simplifying the handling of left, right ... tabs, // we do expand portions, which are lying behind // those special tabs
bNoCompression = !pPos->IsTabLeftPortion();
} else
{
nRest = ! bNoCompression ? static_cast<SwGluePortion*>(pPos)->GetPrtGlue() :
0;
// pCurrent->Width() is set to the real size, because we attach the // MarginPortions. // This trick gives miraculous results: // If pCurrent->Width() == nRealWidth, then the adjustment gets overruled // implicitly. GetLeftMarginAdjust() and IsJustified() think they have a // line filled with chars.
pCurrent->PrtWidth(nRealWidth); return pRight;
}
void SwTextAdjuster::CalcFlyAdjust( SwLineLayout *pCurrent )
{ // 1) We insert a left margin:
SwMarginPortion *pLeft = pCurrent->CalcLeftMargin();
SwGluePortion *pGlue = pLeft; // the last GluePortion
// 2) We attach a right margin: // CalcRightMargin also calculates a possible overlap with FlyFrames.
CalcRightMargin( pCurrent );
// If we only have one line, the text portion is consecutive and we center, then ... bool bComplete = TextFrameIndex(0) == m_nStart; constbool bTabCompat = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT); bool bMultiTab = false;
while( pPos )
{ if ( pPos->IsMultiPortion() && static_cast<SwMultiPortion*>(pPos)->HasTabulator() )
bMultiTab = true; elseif( pPos->InFixMargGrp() &&
( bTabCompat ? ! pPos->InTabGrp() : ! bMultiTab ) )
{ // in tab compat mode we do not want to change tab portions // in non tab compat mode we do not want to change margins if we // found a multi portion with tabs if( SvxAdjust::Right == GetAdjust() ) static_cast<SwGluePortion*>(pPos)->MoveAllGlue( pGlue ); else
{ // We set the first text portion to right-aligned and the last one // to left-aligned. // The first text portion gets the whole Glue, but only if we have // more than one line. if (bComplete && TextFrameIndex(GetInfo().GetText().getLength()) == nLen) static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue ); else
{ if ( ! bTabCompat )
{ if( pLeft == pGlue )
{ // If we only have a left and right margin, the // margins share the Glue. if( nLen + pPos->GetLen() >= pCurrent->GetLen() ) static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue ); else static_cast<SwGluePortion*>(pPos)->MoveAllGlue( pGlue );
} else
{ // The last text portion retains its Glue. if( !pPos->IsMarginPortion() ) static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue );
}
} else static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue );
}
}
switch( GetAdjust() )
{ case SvxAdjust::Right: case SvxAdjust::Center:
{
CalcFlyAdjust( pCurrent );
pPara->GetRepaint().SetOffset( 0 ); break;
} case SvxAdjust::Block:
{
FormatBlock(); break;
} default : return;
}
}
// This is a quite complicated calculation: nCurrWidth is the width _before_ // adding the word, that still fits onto the line! For this reason the FlyPortion's // width is still correct if we get a deadlock-situation of: // bFirstWord && !WORDFITS
SwFlyPortion *SwTextAdjuster::CalcFlyPortion( const tools::Long nRealWidth, const SwRect &rCurrRect )
{
SwTextFly aTextFly( GetTextFrame() );
SwRect aLineVert( rCurrRect ); if ( GetTextFrame()->IsRightToLeft() )
GetTextFrame()->SwitchLTRtoRTL( aLineVert ); if ( GetTextFrame()->IsVertical() )
GetTextFrame()->SwitchHorizontalToVertical( aLineVert );
// aFlyRect is document-global!
SwRect aFlyRect( aTextFly.GetFrame( aLineVert ) );
if ( GetTextFrame()->IsRightToLeft() )
GetTextFrame()->SwitchRTLtoLTR( aFlyRect ); if ( GetTextFrame()->IsVertical() )
GetTextFrame()->SwitchVerticalToHorizontal( aFlyRect );
// If a Frame overlapps we open a Portion if( aFlyRect.HasArea() )
{ // aLocal is frame-local
SwRect aLocal( aFlyRect );
aLocal.Pos( aLocal.Left() - GetLeftMargin(), aLocal.Top() ); if( nCurrWidth > aLocal.Left() )
aLocal.Left( nCurrWidth );
// If the rect is wider than the line, we adjust it to the right size const tools::Long nLocalWidth = aLocal.Left() + aLocal.Width(); if( nRealWidth < nLocalWidth )
aLocal.Width( nRealWidth - aLocal.Left() );
GetInfo().GetParaPortion()->SetFly();
pFlyPortion = new SwFlyPortion( aLocal );
pFlyPortion->Height(rCurrRect.Height()); // The Width could be smaller than the FixWidth, thus:
pFlyPortion->AdjFixWidth();
} return pFlyPortion;
}
// CalcDropAdjust is called at the end by Format() if needed void SwTextAdjuster::CalcDropAdjust()
{
OSL_ENSURE( 1<GetDropLines() && SvxAdjust::Left!=GetAdjust() && SvxAdjust::Block!=GetAdjust(), "CalcDropAdjust: No reason for DropAdjustment." );
const sal_Int32 nLineNumber = GetLineNr();
// 1) Skip dummies
Top();
if( !m_pCurr->IsDummy() || NextLine() )
{ // Adjust first
GetAdjusted();
SwLinePortion *pPor = m_pCurr->GetFirstPortion();
// 2) Make sure we include the ropPortion // 3) pLeft is the GluePor preceding the DropPor if( pPor->InGlueGrp() && pPor->GetNextPortion()
&& pPor->GetNextPortion()->IsDropPortion() )
{ const SwLinePortion *pDropPor = pPor->GetNextPortion();
SwGluePortion *pLeft = static_cast<SwGluePortion*>( pPor );
// 4) pRight: Find the GluePor coming after the DropPor
pPor = pPor->GetNextPortion(); while( pPor && !pPor->InFixMargGrp() )
pPor = pPor->GetNextPortion();
SwGluePortion *pRight = ( pPor && pPor->InGlueGrp() ) ? static_cast<SwGluePortion*>(pPor) : nullptr; if( pRight && pRight != pLeft )
{ // 5) Calculate nMinLeft. Who is the most to left? constauto nDropLineStart =
GetLineStart() + pLeft->Width() + pDropPor->Width(); auto nMinLeft = nDropLineStart; for( sal_Int32 i = 1; i < GetDropLines(); ++i )
{ if( NextLine() )
{ // Adjust first
GetAdjusted();
// 6) Distribute the Glue anew between pLeft and pRight if( nMinLeft < nDropLineStart )
{ // The Glue is always passed from pLeft to pRight, so that // the text moves to the left. constauto nGlue = nDropLineStart - nMinLeft; if( !nMinLeft )
pLeft->MoveAllGlue( pRight ); else
pLeft->MoveGlue( pRight, nGlue );
}
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.