summaryrefslogtreecommitdiffstats
path: root/sw/source/core/text/itradj.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sw/source/core/text/itradj.cxx')
-rw-r--r--sw/source/core/text/itradj.cxx841
1 files changed, 841 insertions, 0 deletions
diff --git a/sw/source/core/text/itradj.cxx b/sw/source/core/text/itradj.cxx
new file mode 100644
index 000000000..a952ce764
--- /dev/null
+++ b/sw/source/core/text/itradj.cxx
@@ -0,0 +1,841 @@
+/* -*- 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 <sal/config.h>
+
+#include <o3tl/safeint.hxx>
+
+#include <IDocumentSettingAccess.hxx>
+#include <doc.hxx>
+
+#include "itrtxt.hxx"
+#include "porglue.hxx"
+#include "porlay.hxx"
+#include "porfly.hxx"
+#include "pormulti.hxx"
+#include "portab.hxx"
+#include <memory>
+
+#define MIN_TAB_WIDTH 60
+
+using namespace ::com::sun::star;
+
+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() &&
+ m_nStart + m_pCurr->GetLen() >= TextFrameIndex(GetInfo().GetText().getLength());
+
+ // 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;
+ }
+ }
+
+ if( bSkip )
+ {
+ if( !GetInfo().GetParaPortion()->HasFly() )
+ {
+ if( IsLastCenter() )
+ CalcFlyAdjust( m_pCurr );
+ m_pCurr->FinishSpaceAdd();
+ return;
+ }
+ else
+ {
+ const SwLinePortion *pTmpFly = 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
+ else if ( 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;
+ }
+ }
+ }
+
+ const TextFrameIndex nOldIdx = GetInfo().GetIdx();
+ GetInfo().SetIdx( m_nStart );
+ CalcNewBlock( m_pCurr, pFly );
+ GetInfo().SetIdx( nOldIdx );
+ GetInfo().GetParaPortion()->GetRepaint().SetOffset(0);
+}
+
+static bool lcl_CheckKashidaPositions( SwScriptInfo& rSI, SwTextSizeInfo& rInf, SwTextIter& rItr,
+ sal_Int32& rKashidas, TextFrameIndex& nGluePortion)
+{
+ // i60594 validate Kashida justification
+ TextFrameIndex nIdx = rItr.GetStart();
+ TextFrameIndex nEnd = rItr.GetEnd();
+
+ // Note on calling KashidaJustify():
+ // Kashida positions may be marked as invalid. Therefore KashidaJustify may return the clean
+ // total number of kashida positions, or the number of kashida positions after some positions
+ // have been dropped.
+ // Here we want the clean total, which is OK: We have called ClearKashidaInvalid() before.
+ rKashidas = rSI.KashidaJustify(nullptr, rItr.GetStart(), rItr.GetLength());
+
+ if (rKashidas <= 0) // nothing to do
+ return true;
+
+ // kashida positions found in SwScriptInfo are not necessarily valid in every font
+ // if two characters are replaced by a ligature glyph, there will be no place for a kashida
+ std::vector<TextFrameIndex> aKashidaPos;
+ rSI.GetKashidaPositions(nIdx, rItr.GetLength(), aKashidaPos);
+ assert(aKashidaPos.size() >= o3tl::make_unsigned(rKashidas));
+ std::vector<TextFrameIndex> aKashidaPosDropped(aKashidaPos.size());
+ sal_Int32 nKashidaIdx = 0;
+ while ( rKashidas && nIdx < nEnd )
+ {
+ rItr.SeekAndChgAttrIter( nIdx, rInf.GetOut() );
+ TextFrameIndex nNext = rItr.GetNextAttr();
+
+ // is there also a script change before?
+ // if there is, nNext should point to the script change
+ TextFrameIndex const nNextScript = rSI.NextScriptChg( nIdx );
+ if( nNextScript < nNext )
+ nNext = nNextScript;
+
+ if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd)
+ nNext = nEnd;
+ sal_Int32 nKashidasInAttr = rSI.KashidaJustify(nullptr, nIdx, nNext - nIdx);
+ if (nKashidasInAttr > 0)
+ {
+ // Kashida glyph looks suspicious, skip Kashida justification
+ if ( rInf.GetOut()->GetMinKashida() <= 0 )
+ {
+ return false;
+ }
+
+ sal_Int32 nKashidasDropped = 0;
+ if ( !SwScriptInfo::IsArabicText( rInf.GetText(), nIdx, nNext - nIdx ) )
+ {
+ nKashidasDropped = nKashidasInAttr;
+ rKashidas -= nKashidasDropped;
+ }
+ else
+ {
+ vcl::text::ComplexTextLayoutFlags nOldLayout = rInf.GetOut()->GetLayoutMode();
+ rInf.GetOut()->SetLayoutMode ( nOldLayout | vcl::text::ComplexTextLayoutFlags::BiDiRtl );
+ nKashidasDropped = rInf.GetOut()->ValidateKashidas(
+ rInf.GetText(), sal_Int32(nIdx), sal_Int32(nNext - nIdx),
+ nKashidasInAttr,
+ reinterpret_cast<sal_Int32*>(aKashidaPos.data() + nKashidaIdx),
+ reinterpret_cast<sal_Int32*>(aKashidaPosDropped.data()));
+ rInf.GetOut()->SetLayoutMode ( nOldLayout );
+ if ( nKashidasDropped )
+ {
+ rSI.MarkKashidasInvalid(nKashidasDropped, aKashidaPosDropped.data());
+ rKashidas -= nKashidasDropped;
+ nGluePortion -= TextFrameIndex(nKashidasDropped);
+ }
+ }
+ nKashidaIdx += nKashidasInAttr;
+ }
+ nIdx = nNext;
+ }
+
+ // return false if all kashidas have been eliminated
+ return (rKashidas > 0);
+}
+
+static bool lcl_CheckKashidaWidth ( SwScriptInfo& rSI, SwTextSizeInfo& rInf, SwTextIter& rItr, sal_Int32& rKashidas,
+ TextFrameIndex& nGluePortion, const tools::Long nGluePortionWidth, tools::Long& nSpaceAdd )
+{
+ // check kashida width
+ // if width is smaller than minimal kashida width allowed by fonts in the current line
+ // drop one kashida after the other until kashida width is OK
+ while (rKashidas)
+ {
+ bool bAddSpaceChanged = false;
+ TextFrameIndex nIdx = rItr.GetStart();
+ TextFrameIndex nEnd = rItr.GetEnd();
+ while ( nIdx < nEnd )
+ {
+ rItr.SeekAndChgAttrIter( nIdx, rInf.GetOut() );
+ TextFrameIndex nNext = rItr.GetNextAttr();
+
+ // is there also a script change before?
+ // if there is, nNext should point to the script change
+ TextFrameIndex const nNextScript = rSI.NextScriptChg( nIdx );
+ if( nNextScript < nNext )
+ nNext = nNextScript;
+
+ if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd)
+ nNext = nEnd;
+ sal_Int32 nKashidasInAttr = rSI.KashidaJustify(nullptr, nIdx, nNext - nIdx);
+
+ tools::Long nFontMinKashida = rInf.GetOut()->GetMinKashida();
+ if ( nFontMinKashida && nKashidasInAttr > 0 && SwScriptInfo::IsArabicText( rInf.GetText(), nIdx, nNext - nIdx ) )
+ {
+ sal_Int32 nKashidasDropped = 0;
+ while ( rKashidas && nGluePortion && nKashidasInAttr > 0 &&
+ nSpaceAdd / SPACING_PRECISION_FACTOR < nFontMinKashida )
+ {
+ --nGluePortion;
+ --rKashidas;
+ --nKashidasInAttr;
+ ++nKashidasDropped;
+ if( !rKashidas || !nGluePortion ) // nothing left, return false to
+ return false; // do regular blank justification
+
+ nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion);
+ bAddSpaceChanged = true;
+ }
+ if( nKashidasDropped )
+ rSI.MarkKashidasInvalid( nKashidasDropped, nIdx, nNext - nIdx );
+ }
+ if ( bAddSpaceChanged )
+ break; // start all over again
+ nIdx = nNext;
+ }
+ if ( !bAddSpaceChanged )
+ break; // everything was OK
+ }
+ return true;
+}
+
+// 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()" );
+
+ pCurrent->InitSpaceAdd();
+ TextFrameIndex nGluePortion(0);
+ TextFrameIndex nCharCnt(0);
+ sal_uInt16 nSpaceIdx = 0;
+
+ // i60591: hennerdrews
+ SwScriptInfo& rSI = GetInfo().GetParaPortion()->GetScriptInfo();
+ SwTextSizeInfo aInf ( GetTextFrame() );
+ SwTextIter aItr ( GetTextFrame(), &aInf );
+
+ if ( rSI.CountKashida() )
+ {
+ while (aItr.GetCurr() != pCurrent && aItr.GetNext())
+ aItr.Next();
+
+ if( bSkipKashida )
+ {
+ rSI.SetNoKashidaLine ( aItr.GetStart(), aItr.GetLength());
+ }
+ else
+ {
+ rSI.ClearKashidaInvalid ( aItr.GetStart(), aItr.GetLength() );
+ rSI.ClearNoKashidaLine( aItr.GetStart(), aItr.GetLength() );
+ }
+ }
+
+ // Do not forget: CalcRightMargin() sets pCurrent->Width() to the line width!
+ if (!bSkipKashida)
+ CalcRightMargin( pCurrent, nReal );
+
+ // #i49277#
+ const bool bDoNotJustifyLinesWithManualBreak =
+ GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::DO_NOT_JUSTIFY_LINES_WITH_MANUAL_BREAK);
+ bool bDoNotJustifyTab = false;
+
+ SwLinePortion *pPos = pCurrent->GetNextPortion();
+
+ while( pPos )
+ {
+ if ( ( bDoNotJustifyLinesWithManualBreak || bDoNotJustifyTab ) &&
+ pPos->IsBreakPortion() && !IsLastBlock() )
+ {
+ pCurrent->FinishSpaceAdd();
+ break;
+ }
+
+ 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 );
+ else if( 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 );
+
+ nSpaceIdx++;
+ nGluePortion = TextFrameIndex(0);
+ nCharCnt = TextFrameIndex(0);
+ }
+ else if( pMulti->IsDouble() )
+ nGluePortion = nGluePortion + static_cast<SwDoubleLinePortion*>(pMulti)->GetSpaceCnt();
+ else if ( pMulti->IsBidi() )
+ nGluePortion = nGluePortion + static_cast<SwBidiPortion*>(pMulti)->GetSpaceCnt( GetInfo() ); // i60594
+ }
+
+ if( pPos->InGlueGrp() )
+ {
+ if( pPos->InFixMargGrp() )
+ {
+ if ( nSpaceIdx == pCurrent->GetLLSpaceAddCount() )
+ pCurrent->SetLLSpaceAdd( 0, nSpaceIdx );
+
+ const tools::Long nGluePortionWidth = static_cast<SwGluePortion*>(pPos)->GetPrtGlue() *
+ SPACING_PRECISION_FACTOR;
+
+ sal_Int32 nKashidas = 0;
+ if( nGluePortion && rSI.CountKashida() && !bSkipKashida )
+ {
+ // kashida positions found in SwScriptInfo are not necessarily valid in every font
+ // if two characters are replaced by a ligature glyph, there will be no place for a kashida
+ if ( !lcl_CheckKashidaPositions ( rSI, aInf, aItr, nKashidas, nGluePortion ))
+ {
+ // all kashida positions are invalid
+ // 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);
+
+ // i60594
+ if( rSI.CountKashida() && !bSkipKashida )
+ {
+ if( !lcl_CheckKashidaWidth( rSI, aInf, aItr, nKashidas, nGluePortion, nGluePortionWidth, nSpaceAdd ))
+ {
+ // no kashidas left
+ // do regular blank justification
+ pCurrent->FinishSpaceAdd();
+ GetInfo().SetIdx( m_nStart );
+ CalcNewBlock( pCurrent, pStopAt, nReal, true );
+ return;
+ }
+ }
+
+ pCurrent->SetLLSpaceAdd( nSpaceAdd , nSpaceIdx );
+ pPos->Width( static_cast<SwGluePortion*>(pPos)->GetFixWidth() );
+ }
+ else if (IsOneBlock() && nCharCnt > TextFrameIndex(1))
+ {
+ const tools::Long nSpaceAdd = - nGluePortionWidth / (sal_Int32(nCharCnt) - 1);
+ pCurrent->SetLLSpaceAdd( nSpaceAdd, nSpaceIdx );
+ pPos->Width( static_cast<SwGluePortion*>(pPos)->GetFixWidth() );
+ }
+
+ nSpaceIdx++;
+ nGluePortion = TextFrameIndex(0);
+ nCharCnt = TextFrameIndex(0);
+ }
+ else
+ ++nGluePortion;
+ }
+ GetInfo().SetIdx( GetInfo().GetIdx() + pPos->GetLen() );
+ if ( pPos == pStopAt )
+ {
+ pCurrent->SetLLSpaceAdd( 0, nSpaceIdx );
+ break;
+ }
+ pPos = pPos->GetNextPortion();
+ }
+}
+
+SwTwips SwTextAdjuster::CalcKanaAdj( SwLineLayout* pCurrent )
+{
+ OSL_ENSURE( pCurrent->Height(), "SwTextAdjuster::CalcBlockAdjust: missing CalcLine()" );
+ OSL_ENSURE( !pCurrent->GetpKanaComp(), "pKanaComp already exists!!" );
+
+ pCurrent->SetKanaComp( std::make_unique<std::deque<sal_uInt16>>() );
+
+ const sal_uInt16 nNull = 0;
+ size_t nKanaIdx = 0;
+ tools::Long nKanaDiffSum = 0;
+ SwTwips nRepaintOfst = 0;
+ SwTwips nX = 0;
+ bool bNoCompression = false;
+
+ // 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
+ sal_uInt16 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();
+ }
+ else if( pPos->InGlueGrp() && pPos->InFixMargGrp() )
+ {
+ if ( nKanaIdx == pCurrent->GetKanaComp().size() )
+ pCurrent->GetKanaComp().push_back( nNull );
+
+ tools::Long nRest;
+
+ if ( pPos->InTabGrp() )
+ {
+ nRest = ! bNoCompression &&
+ ( pPos->Width() > MIN_TAB_WIDTH ) ?
+ pPos->Width() - MIN_TAB_WIDTH :
+ 0;
+
+ // 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;
+
+ bNoCompression = false;
+ }
+
+ if( nKanaDiffSum )
+ {
+ sal_uLong nCompress = ( 10000 * nRest ) / nKanaDiffSum;
+
+ if ( nCompress >= 10000 )
+ // kanas can be expanded to 100%, and there is still
+ // some space remaining
+ nCompress = 0;
+
+ else
+ nCompress = 10000 - nCompress;
+
+ ( pCurrent->GetKanaComp() )[ nKanaIdx ] = o3tl::narrowing<sal_uInt16>(nCompress);
+ nKanaDiffSum = 0;
+ }
+
+ nKanaIdx++;
+ }
+
+ nX += pPos->Width();
+ pPos = pPos->GetNextPortion();
+ }
+
+ // set portion width
+ nKanaIdx = 0;
+ sal_uInt16 nCompress = ( pCurrent->GetKanaComp() )[ nKanaIdx ];
+ pPos = pCurrent->GetNextPortion();
+ tools::Long nDecompress = 0;
+
+ while( pPos )
+ {
+ if ( pPos->InTextGrp() )
+ {
+ const SwTwips nMinWidth = pPos->Width();
+
+ // 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 );
+ pPos->Width( nMinWidth +
+ ( ( 10000 - nCompress ) * nMaxWidthDiff ) / 10000 );
+ nDecompress += pPos->Width() - nMinWidth;
+ }
+ else if( pPos->InGlueGrp() && pPos->InFixMargGrp() )
+ {
+ pPos->Width(pPos->Width() - nDecompress);
+
+ if ( pPos->InTabGrp() )
+ // set fix width to width
+ static_cast<SwTabPortion*>(pPos)->SetFixWidth( pPos->Width() );
+
+ if ( ++nKanaIdx < pCurrent->GetKanaComp().size() )
+ nCompress = ( pCurrent->GetKanaComp() )[ nKanaIdx ];
+
+ nDecompress = 0;
+ }
+ pPos = pPos->GetNextPortion();
+ }
+
+ return nRepaintOfst;
+}
+
+SwMarginPortion *SwTextAdjuster::CalcRightMargin( SwLineLayout *pCurrent,
+ SwTwips nReal )
+{
+ tools::Long nRealWidth;
+ const sal_uInt16 nRealHeight = GetLineHeight();
+ const sal_uInt16 nLineHeight = pCurrent->Height();
+
+ sal_uInt16 nPrtWidth = pCurrent->PrtWidth();
+ SwLinePortion *pLast = pCurrent->FindLastPortion();
+
+ if( GetInfo().IsMulti() )
+ nRealWidth = nReal;
+ else
+ {
+ nRealWidth = GetLineWidth();
+ // For each FlyFrame extending into the right margin, we create a FlyPortion.
+ const tools::Long nLeftMar = GetLeftMargin();
+ SwRect aCurrRect( nLeftMar + nPrtWidth, Y() + nRealHeight - nLineHeight,
+ nRealWidth - nPrtWidth, nLineHeight );
+
+ SwFlyPortion *pFly = CalcFlyPortion( nRealWidth, aCurrRect );
+ while( pFly && tools::Long( nPrtWidth )< nRealWidth )
+ {
+ pLast->Append( pFly );
+ pLast = pFly;
+ if( pFly->GetFix() > nPrtWidth )
+ pFly->Width( ( pFly->GetFix() - nPrtWidth) + pFly->Width() + 1);
+ nPrtWidth += pFly->Width() + 1;
+ aCurrRect.Left( nLeftMar + nPrtWidth );
+ pFly = CalcFlyPortion( nRealWidth, aCurrRect );
+ }
+ delete pFly;
+ }
+
+ SwMarginPortion *pRight = new SwMarginPortion;
+ pLast->Append( pRight );
+
+ if( tools::Long( nPrtWidth )< nRealWidth )
+ pRight->PrtWidth( sal_uInt16( nRealWidth - nPrtWidth ) );
+
+ // 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( sal_uInt16( 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 );
+
+ SwLinePortion *pPos = pLeft->GetNextPortion();
+ TextFrameIndex nLen(0);
+
+ // If we only have one line, the text portion is consecutive and we center, then ...
+ bool bComplete = TextFrameIndex(0) == m_nStart;
+ const bool bTabCompat = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT);
+ bool bMultiTab = false;
+
+ while( pPos )
+ {
+ if ( pPos->IsMultiPortion() && static_cast<SwMultiPortion*>(pPos)->HasTabulator() )
+ bMultiTab = true;
+ else if( 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 );
+ }
+ }
+
+ pGlue = static_cast<SwGluePortion*>(pPos);
+ bComplete = false;
+ }
+ nLen = nLen + pPos->GetLen();
+ pPos = pPos->GetNextPortion();
+ }
+
+ if( ! bTabCompat && ! bMultiTab && SvxAdjust::Right == GetAdjust() )
+ // portions are moved to the right if possible
+ pLeft->AdjustRight( pCurrent );
+}
+
+void SwTextAdjuster::CalcAdjLine( SwLineLayout *pCurrent )
+{
+ OSL_ENSURE( pCurrent->IsFormatAdj(), "CalcAdjLine: Why?" );
+
+ pCurrent->SetFormatAdj(false);
+
+ SwParaPortion* pPara = GetInfo().GetParaPortion();
+
+ 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() );
+
+ const sal_uInt16 nCurrWidth = m_pCurr->PrtWidth();
+ SwFlyPortion *pFlyPortion = nullptr;
+
+ 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( sal_uInt16( 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_uInt16 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?
+ const auto nDropLineStart =
+ GetLineStart() + pLeft->Width() + pDropPor->Width();
+ auto nMinLeft = nDropLineStart;
+ for( sal_uInt16 i = 1; i < GetDropLines(); ++i )
+ {
+ if( NextLine() )
+ {
+ // Adjust first
+ GetAdjusted();
+
+ pPor = m_pCurr->GetFirstPortion();
+ const SwMarginPortion *pMar = pPor->IsMarginPortion() ?
+ static_cast<SwMarginPortion*>(pPor) : nullptr;
+ if( !pMar )
+ nMinLeft = 0;
+ else
+ {
+ const auto nLineStart =
+ GetLineStart() + pMar->Width();
+ if( nMinLeft > nLineStart )
+ nMinLeft = nLineStart;
+ }
+ }
+ }
+
+ // 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.
+ const auto nGlue = nDropLineStart - nMinLeft;
+ if( !nMinLeft )
+ pLeft->MoveAllGlue( pRight );
+ else
+ pLeft->MoveGlue( pRight, nGlue );
+ }
+ }
+ }
+ }
+
+ if( nLineNumber != GetLineNr() )
+ {
+ Top();
+ while( nLineNumber != GetLineNr() && Next() )
+ ;
+ }
+}
+
+void SwTextAdjuster::CalcDropRepaint()
+{
+ Top();
+ SwRepaint &rRepaint = GetInfo().GetParaPortion()->GetRepaint();
+ if( rRepaint.Top() > Y() )
+ rRepaint.Top( Y() );
+ for( sal_uInt16 i = 1; i < GetDropLines(); ++i )
+ NextLine();
+ const SwTwips nBottom = Y() + GetLineHeight() - 1;
+ if( rRepaint.Bottom() < nBottom )
+ rRepaint.Bottom( nBottom );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */