/* -*- 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 .
*/
// A SwMultiPortion is not a simple portion, // it's a container, which contains almost a SwLineLayoutPortion. // This SwLineLayout could be followed by other textportions via pPortion // and by another SwLineLayout via pNext to realize a doubleline portion.
SwMultiPortion::~SwMultiPortion()
{
}
// Summarize the internal lines to calculate the (external) size. // The internal line has to calculate first. void SwMultiPortion::CalcSize( SwTextFormatter& rLine, SwTextFormatInfo &rInf )
{
Width( 0 );
Height( 0 );
SetAscent( 0 );
SetFlyInContent( false );
SwLineLayout *pLay = &GetRoot(); do
{
pLay->CalcLine( rLine, rInf ); if( rLine.IsFlyInCntBase() )
SetFlyInContent( true ); if( IsRuby() && ( OnTop() == ( pLay == &GetRoot() ) ) )
{ // An empty phonetic line don't need an ascent or a height. if( !pLay->Width() )
{
pLay->SetAscent( 0 );
pLay->Height( 0 );
} if( OnTop() )
SetAscent( GetAscent() + pLay->Height() );
} else
SetAscent( GetAscent() + pLay->GetAscent() );
// Increase the line height, except for ruby text on the right. if ( !IsRuby() || !OnRight() || pLay == &GetRoot() )
Height( Height() + pLay->Height() ); else
{ // We already added the width after building the portion, // so no need to add it twice. break;
}
if( Width() < pLay->Width() )
Width( pLay->Width() );
pLay = pLay->GetNext();
} while ( pLay ); if( !HasBrackets() ) return;
void SwMultiPortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
TextFrameIndex& nOffset) const
{
(void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwMultiPortion"));
dumpAsXmlAttributes(pWriter, rText, nOffset); // Intentionally not incrementing nOffset here, one of the child portions will do that.
// This constructor is for the continuation of a doubleline portion // in the next line. // It takes the same brackets and if the original has no content except // brackets, these will be deleted.
SwDoubleLinePortion::SwDoubleLinePortion(
SwDoubleLinePortion& rDouble, TextFrameIndex const nEnd)
: SwMultiPortion(nEnd)
, m_nLineDiff(0)
, m_nBlank1(0)
, m_nBlank2(0)
{
SetDirection( rDouble.GetDirection() );
SetDouble(); if( rDouble.GetBrackets() )
{
SetBrackets( rDouble ); // An empty multiportion needs no brackets. // Notice: GetLen() might be zero, if the multiportion contains // the second part of a field and the width might be zero, if // it contains a note only. In this cases the brackets are okay. // But if the length and the width are both zero, the portion // is really empty. if( rDouble.Width() == rDouble.BracketWidth() )
rDouble.ClearBrackets();
}
}
// This constructor uses the textattribute to get the right brackets. // The textattribute could be a 2-line-attribute or a character- or // internet style, which contains the 2-line-attribute.
SwDoubleLinePortion::SwDoubleLinePortion( const SwMultiCreator& rCreate, TextFrameIndex const nEnd)
: SwMultiPortion(nEnd)
, m_pBracket(new SwBracket)
, m_nLineDiff(0)
, m_nBlank1(0)
, m_nBlank2(0)
{
m_pBracket->nAscent = 0;
m_pBracket->nHeight = 0;
m_pBracket->nPreWidth = 0;
m_pBracket->nPostWidth = 0;
// double line portions have the same direction as the frame directions if ( rCreate.nLevel % 2 )
SetDirection( DIR_RIGHT2LEFT ); else
SetDirection( DIR_LEFT2RIGHT );
}
// paints the wished bracket, // if the multiportion has surrounding brackets. // The X-position of the SwTextPaintInfo will be modified: // the open bracket sets position behind itself, // the close bracket in front of itself. void SwDoubleLinePortion::PaintBracket( SwTextPaintInfo &rInf,
tools::Long nSpaceAdd, bool bOpen ) const
{
sal_Unicode cCh = bOpen ? m_pBracket->cPre : m_pBracket->cPost; if( !cCh ) return; const sal_uInt16 nChWidth = bOpen ? PreWidth() : PostWidth(); if( !nChWidth ) return; if( !bOpen )
rInf.X( rInf.X() + Width() - PostWidth() +
( nSpaceAdd > 0 ? CalcSpacing( nSpaceAdd, rInf ) : 0 ) );
// Merges the spaces for text adjustment from the inner and outer part. // Inside the doubleline portion the wider line has no spaceadd-array, the // smaller line has such an array to reach width of the wider line. // If the surrounding line has text adjustment and the doubleline portion // contains no tabulator, it is necessary to create/manipulate the inner // space arrays. bool SwDoubleLinePortion::ChgSpaceAdd( SwLineLayout* pCurr,
tools::Long nSpaceAdd ) const
{ bool bRet = false; if( !HasTabulator() && nSpaceAdd > 0 )
{ if( !pCurr->IsSpaceAdd() )
{ // The wider line gets the spaceadd from the surrounding line direct
pCurr->CreateSpaceAdd();
pCurr->SetLLSpaceAdd( nSpaceAdd, 0 );
bRet = true;
} else
{
sal_Int32 const nMyBlank = sal_Int32(GetSmallerSpaceCnt());
sal_Int32 const nOther = sal_Int32(GetSpaceCnt());
SwTwips nMultiSpace = pCurr->GetLLSpaceAdd( 0 ) * nMyBlank + nOther * nSpaceAdd;
// RIGHT is designed for horizontal writing mode only. if ( ePos == RubyPosition::RIGHT && pFrame->IsVertical() )
ePos = RubyPosition::ABOVE;
// In grid mode we force the ruby text to the upper or lower line if ( rInf.SnapToGrid() )
{
SwTextGridItem const*const pGrid( GetGridItem(pFrame->FindPageFrame()) ); if ( pGrid )
ePos = pGrid->GetRubyTextBelow() ? RubyPosition::BELOW : RubyPosition::ABOVE;
}
// ruby portions have the same direction as the frame directions if ( rCreate.nLevel % 2 )
{ // switch right and left ruby adjustment in rtl environment if ( css::text::RubyAdjust_LEFT == m_nAdjustment )
m_nAdjustment = css::text::RubyAdjust_RIGHT; elseif ( css::text::RubyAdjust_RIGHT == m_nAdjustment )
m_nAdjustment = css::text::RubyAdjust_LEFT;
// In ruby portion there are different alignments for // the ruby text and the main text. // Left, right, centered and two possibilities of block adjustment // The block adjustment is realized by spacing between the characters, // either with a half space or no space in front of the first letter and // a half space at the end of the last letter. // Notice: the smaller line will be manipulated, normally it's the ruby line, // but it could be the main text, too. // If there is a tabulator in smaller line, no adjustment is possible. void SwRubyPortion::Adjust_( SwTextFormatInfo &rInf )
{
SwTwips nLineDiff = GetRoot().Width() - GetRoot().GetNext()->Width();
TextFrameIndex const nOldIdx = rInf.GetIdx(); if( !nLineDiff ) return;
SwLineLayout *pCurr; if( nLineDiff < 0 )
{ // The first line has to be adjusted. if( GetTab1() ) return;
pCurr = &GetRoot();
nLineDiff = -nLineDiff;
} else
{ // The second line has to be adjusted. if( GetTab2() ) return;
pCurr = GetRoot().GetNext();
rInf.SetIdx( nOldIdx + GetRoot().GetLen() );
}
sal_uInt16 nLeft = 0; // the space in front of the first letter
sal_uInt16 nRight = 0; // the space at the end of the last letter
TextFrameIndex nSub(0); switch ( m_nAdjustment )
{ case css::text::RubyAdjust_CENTER: nRight = o3tl::narrowing<sal_uInt16>(nLineDiff / 2);
[[fallthrough]]; case css::text::RubyAdjust_RIGHT: nLeft = o3tl::narrowing<sal_uInt16>(nLineDiff - nRight); break; case css::text::RubyAdjust_BLOCK: nSub = TextFrameIndex(1);
[[fallthrough]]; case css::text::RubyAdjust_INDENT_BLOCK:
{
TextFrameIndex nCharCnt(0);
SwLinePortion *pPor; for( pPor = pCurr->GetFirstPortion(); pPor; pPor = pPor->GetNextPortion() )
{ if( pPor->InTextGrp() ) static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nCharCnt );
rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
} if( nCharCnt > nSub )
{
SwTwips nCalc = nLineDiff / sal_Int32(nCharCnt - nSub); short nTmp; if( nCalc < SHRT_MAX )
nTmp = -short(nCalc); else
nTmp = SHRT_MIN;
// has to change the nRubyOffset, if there's a fieldportion // in the phonetic line. // The nRubyOffset is the position in the rubystring, where the // next SwRubyPortion has start the displaying of the phonetics. void SwRubyPortion::CalcRubyOffset()
{ const SwLineLayout *pCurr = &GetRoot(); if( !OnTop() )
{
pCurr = pCurr->GetNext(); if( !pCurr ) return;
} const SwLinePortion *pPor = pCurr->GetFirstPortion(); const SwFieldPortion *pField = nullptr; while( pPor )
{ if( pPor->InFieldGrp() )
pField = static_cast<const SwFieldPortion*>(pPor);
pPor = pPor->GetNextPortion();
} if( pField )
{ if( pField->HasFollow() )
m_nRubyOffset = pField->GetNextOffset(); else
m_nRubyOffset = TextFrameIndex(COMPLETE_STRING);
}
}
// A little helper function for GetMultiCreator(..) // It extracts the 2-line-format from a 2-line-attribute or a character style. // The rValue is set to true, if the 2-line-attribute's value is set and // no 2-line-format reference is passed. If there is a 2-line-format reference, // then the rValue is set only, if the 2-line-attribute's value is set _and_ // the 2-line-formats has the same brackets. staticbool lcl_Check2Lines(const SfxPoolItem *const pItem, const SvxTwoLinesItem* &rpRef, bool &rValue)
{ if( pItem )
{
rValue = static_cast<const SvxTwoLinesItem*>(pItem)->GetValue(); if( !rpRef )
rpRef = static_cast<const SvxTwoLinesItem*>(pItem); elseif( static_cast<const SvxTwoLinesItem*>(pItem)->GetEndBracket() !=
rpRef->GetEndBracket() || static_cast<const SvxTwoLinesItem*>(pItem)->GetStartBracket() !=
rpRef->GetStartBracket() )
rValue = false; returntrue;
} returnfalse;
}
// is a little help function for GetMultiCreator(..) // It extracts the charrotation from a charrotate-attribute or a character style. // The rValue is set to true, if the charrotate-attribute's value is set and // no charrotate-format reference is passed. // If there is a charrotate-format reference, then the rValue is set only, // if the charrotate-attribute's value is set _and_ identical // to the charrotate-format's value. staticbool lcl_CheckRotation(const SfxPoolItem *const pItem, const SvxCharRotateItem* &rpRef, bool &rValue)
{ if ( pItem )
{
rValue = static_cast<const SvxCharRotateItem*>(pItem)->GetValue() != 0_deg10; if( !rpRef )
rpRef = static_cast<const SvxCharRotateItem*>(pItem); elseif( static_cast<const SvxCharRotateItem*>(pItem)->GetValue() !=
rpRef->GetValue() )
rValue = false; returntrue;
}
// need to use a very special attribute iterator here that returns // both the hints and the nodes, so that GetMultiCreator() can handle // items in the nodes' set properly class MergedAttrIterMulti
: public MergedAttrIterBase
{ private: bool m_First = true; public:
MergedAttrIterMulti(SwTextFrame const& rFrame) : MergedAttrIterBase(rFrame) {}
SwTextAttr const* NextAttr(SwTextNode const*& rpNode); // can't have operator= because m_pMerged/m_pNode const void Assign(MergedAttrIterMulti const& rOther)
{
assert(m_pMerged == rOther.m_pMerged);
assert(m_pNode == rOther.m_pNode);
m_CurrentExtent = rOther.m_CurrentExtent;
m_CurrentHint = rOther.m_CurrentHint;
m_First = rOther.m_First;
}
};
// If we (e.g. the position rPos) are inside a two-line-attribute or // a ruby-attribute, the attribute will be returned in a SwMultiCreator-struct, // otherwise the function returns zero. // The rPos parameter is set to the end of the multiportion, // normally this is the end of the attribute, // but sometimes it is the start of another attribute, which finished or // interrupts the first attribute. // E.g. a ruby portion interrupts a 2-line-attribute, a 2-line-attribute // with different brackets interrupts another 2-line-attribute.
std::optional<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex &rPos,
SwMultiPortion const * pMulti ) const
{
SwScriptInfo& rSI = const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo();
// get the last embedding level
sal_uInt8 nCurrLevel; if ( pMulti )
{
OSL_ENSURE( pMulti->IsBidi(), "Nested MultiPortion is not BidiPortion" ); // level associated with bidi-portion;
nCurrLevel = static_cast<SwBidiPortion const *>(pMulti)->GetLevel();
} else // no nested bidi portion required
nCurrLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
// check if there is a field at rPos:
sal_uInt8 nNextLevel = nCurrLevel; bool bFieldBidi = false;
for (sw::MergedAttrIterMulti iter = *m_pFrame; ; )
{
SwTextNode const* pNode(nullptr);
SwTextAttr const*const pAttr = iter.NextAttr(pNode); if (!pNode)
{ break;
} if (pAttr)
{
assert(pNode->GetIndex() <= startPos.first->GetIndex()); // should break earlier if (startPos.first->GetIndex() <= pNode->GetIndex())
{ if (startPos.first->GetIndex() != pNode->GetIndex()
|| startPos.second < pAttr->GetStart())
{ break;
} if (startPos.second < pAttr->GetAnyEnd())
{ // sw_redlinehide: ruby *always* splits if (RES_TXTATR_CJK_RUBY == pAttr->Which())
pRuby = pAttr; else
{ const SvxCharRotateItem* pRoTmp = nullptr; if (lcl_HasRotation( *pAttr, pRoTmp, bRot ))
{
pActiveRotateHint = bRot ? pAttr : nullptr;
pActiveRotateItem = pRoTmp;
} const SvxTwoLinesItem* p2Tmp = nullptr; if (lcl_Has2Lines( *pAttr, p2Tmp, bTwo ))
{
pActiveTwoLinesHint = bTwo ? pAttr : nullptr;
pActiveTwoLinesItem = p2Tmp;
}
}
}
}
} // !pAttr && pNode means the node changed if (startPos.first->GetIndex() < pNode->GetIndex())
{ break; // only one node initially
} if (startPos.first->GetIndex() == pNode->GetIndex())
{
iterAtStartOfNode.Assign(iter); if (SfxItemState::SET == pNode->GetSwAttrSet().GetItemState(
RES_CHRATR_ROTATE, true, &pNodeRotateItem) &&
pNodeRotateItem->GetValue())
{
pActiveRotateItem = pNodeRotateItem;
} else
{
pNodeRotateItem = nullptr;
} if (SfxItemState::SET == startPos.first->GetSwAttrSet().GetItemState(
RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem) &&
pNodeTwoLinesItem->GetValue())
{
pActiveTwoLinesItem = pNodeTwoLinesItem;
} else
{
pNodeTwoLinesItem = nullptr;
}
}
} if (!pRuby && !pActiveTwoLinesItem && !pActiveRotateItem) return {};
if( pRuby )
{ // The winner is ... a ruby attribute and so // the end of the multiportion is the end of the ruby attribute.
rPos = m_pFrame->MapModelToView(startPos.first, *pRuby->End());
SwMultiCreator aRet;
aRet.pItem = nullptr;
aRet.pAttr = pRuby;
aRet.nStartOfAttr = m_pFrame->MapModelToView(startPos.first, aRet.pAttr->GetStart());
aRet.nId = SwMultiCreatorId::Ruby;
aRet.nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0; return aRet;
} if (pActiveTwoLinesHint ||
(pNodeTwoLinesItem && SfxPoolItem::areSame(pNodeTwoLinesItem, pActiveTwoLinesItem) &&
rPos < TextFrameIndex(GetText().getLength())))
{ // The winner is a 2-line-attribute, // the end of the multiportion depends on the following attributes...
SwMultiCreator aRet;
// We note the endpositions of the 2-line attributes in aEnd as stack
std::deque<TextFrameIndex> aEnd;
// The bOn flag signs the state of the last 2-line attribute in the // aEnd-stack, it is compatible with the winner-attribute or // it interrupts the other attribute. bool bOn = true;
// pActiveTwoLinesHint is the last 2-line-attribute, which contains // the actual position.
// At this moment we know that at position rPos the "winner"-attribute // causes a 2-line-portion. The end of the attribute is the end of the // portion, if there's no interrupting attribute. // There are two kinds of interrupters: // - ruby attributes stops the 2-line-attribute, the end of the // multiline is the start of the ruby attribute // - 2-line-attributes with value "Off" or with different brackets, // these attributes may interrupt the winner, but they could be // neutralized by another 2-line-attribute starting at the same // position with the same brackets as the winner-attribute.
// In the following loop rPos is the critical position and it will be // evaluated, if at rPos starts an interrupting or a maintaining // continuity attribute.
// iterAtStartOfNode is positioned to the first hint of the node // (if any); the node item itself has already been handled above for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
{
SwTextNode const* pNode(nullptr);
SwTextAttr const*const pTmp = iter.NextAttr(pNode); if (!pNode)
{ break;
}
assert(startPos.first->GetIndex() <= pNode->GetIndex());
TextFrameIndex nTmpStart;
TextFrameIndex nTmpEnd; if (pTmp)
{
nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd()); if (nTmpEnd <= rPos) continue;
nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
} else
{
pNodeTwoLinesItem = pNode->GetSwAttrSet().GetItemIfSet(
RES_CHRATR_TWO_LINES);
nTmpStart = m_pFrame->MapModelToView(pNode, 0);
nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
assert(rPos <= nTmpEnd); // next node must not have smaller index
}
if (rPos < nTmpStart)
{ // If bOn is false and the next attribute starts later than rPos // the winner attribute is interrupted at rPos. // If the start of the next attribute is behind the end of // the last attribute on the aEnd-stack, this is the endposition // on the stack is the end of the 2-line portion. if (!bOn || aEnd.back() < nTmpStart) break; // At this moment, bOn is true and the next attribute starts // behind rPos, so we could move rPos to the next startpoint
rPos = nTmpStart; // We clean up the aEnd-stack, endpositions equal to rPos are // superfluous. while( !aEnd.empty() && aEnd.back() <= rPos )
{
bOn = !bOn;
aEnd.pop_back();
} // If the endstack is empty, we simulate an attribute with // state true and endposition rPos if( aEnd.empty() )
{
aEnd.push_front( rPos );
bOn = true;
}
} // A ruby attribute stops the 2-line immediately if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which()) return aRet; if (pTmp ? lcl_Has2Lines(*pTmp, pActiveTwoLinesItem, bTwo)
: lcl_Check2Lines(pNodeTwoLinesItem, pActiveTwoLinesItem, bTwo))
{ // We have an interesting attribute... if( bTwo == bOn )
{ // .. with the same state, so the last attribute could // be continued. if (aEnd.back() < nTmpEnd)
aEnd.back() = nTmpEnd;
} else
{ // .. with a different state.
bOn = bTwo; // If this is smaller than the last on the stack, we put // it on the stack. If it has the same endposition, the last // could be removed. if (nTmpEnd < aEnd.back())
aEnd.push_back( nTmpEnd ); elseif( aEnd.size() > 1 )
aEnd.pop_back(); else
aEnd.back() = nTmpEnd;
}
}
} if( bOn && !aEnd.empty() )
rPos = aEnd.back(); return aRet;
} if (pActiveRotateHint ||
(pNodeRotateItem && SfxPoolItem::areSame(pNodeRotateItem, pActiveRotateItem) &&
rPos < TextFrameIndex(GetText().getLength())))
{ // The winner is a rotate-attribute, // the end of the multiportion depends on the following attributes...
SwMultiCreator aRet;
aRet.nId = SwMultiCreatorId::Rotate;
// We note the endpositions of the 2-line attributes in aEnd as stack
std::deque<TextFrameIndex> aEnd;
// The bOn flag signs the state of the last 2-line attribute in the // aEnd-stack, which could interrupts the winning rotation attribute. bool bOn = pNodeTwoLinesItem != nullptr;
aEnd.push_front(TextFrameIndex(GetText().getLength()));
// first, search for the start position of the next TWOLINE portion // because the ROTATE portion must end there at the latest
TextFrameIndex n2Start = rPos; for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
{
SwTextNode const* pNode(nullptr);
SwTextAttr const*const pTmp = iter.NextAttr(pNode); if (!pNode)
{ break;
}
assert(startPos.first->GetIndex() <= pNode->GetIndex());
TextFrameIndex nTmpStart;
TextFrameIndex nTmpEnd; if (pTmp)
{
nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd()); if (nTmpEnd <= n2Start) continue;
nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
} else
{
pNodeTwoLinesItem = pNode->GetSwAttrSet().GetItemIfSet(
RES_CHRATR_TWO_LINES);
nTmpStart = m_pFrame->MapModelToView(pNode, 0);
nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
assert(n2Start <= nTmpEnd); // next node must not have smaller index
}
// A little helper class to manage the spaceadd-arrays of the text adjustment // during a PaintMultiPortion. // The constructor prepares the array for the first line of multiportion, // the SecondLine-function restores the values for the first line and prepares // the second line. // The destructor restores the values of the last manipulation. class SwSpaceManipulator
{
SwTextPaintInfo& m_rInfo;
SwMultiPortion& m_rMulti;
std::vector<tools::Long>* m_pOldSpaceAdd;
sal_uInt16 m_nOldSpaceIndex;
tools::Long m_nSpaceAdd; bool m_bSpaceChg;
sal_uInt8 m_nOldDir;
// Manages the paint for a SwMultiPortion. // External, for the calling function, it seems to be a normal Paint-function, // internal it is like a SwTextFrame::PaintSwFrame with multiple DrawTextLines void SwTextPainter::PaintMultiPortion( const SwRect &rPaint,
SwMultiPortion& rMulti, const SwMultiPortion* pEnvPor )
{
SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); constbool bHasGrid = pGrid && GetInfo().SnapToGrid();
sal_uInt16 nRubyHeight = 0; bool bRubyTop = true;
if ( bRubyInGrid && pGrid->IsSquaredMode() )
rMulti.Height( nOldHeight );
// do we have to repaint a post it portion? if( GetInfo().OnWin() && rMulti.GetNextPortion() &&
! rMulti.GetNextPortion()->Width() )
rMulti.GetNextPortion()->PrePaint( GetInfo(), &rMulti );
// old values must be saved and restored at the end
TextFrameIndex const nOldLen = GetInfo().GetLen(); const SwTwips nOldX = GetInfo().X(); const SwTwips nOldY = GetInfo().Y();
TextFrameIndex const nOldIdx = GetInfo().GetIdx();
SwLineLayout* pLay = &rMulti.GetRoot();// the first line of the multiportion
SwLinePortion* pPor = pLay->GetFirstPortion();//first portion of these line
SwTwips nOfst = 0;
// GetInfo().Y() is the baseline from the surrounding line. We must switch // this temporary to the baseline of the inner lines of the multiportion. if( rMulti.HasRotation() )
{ if( rMulti.IsRevers() )
{
GetInfo().Y( nOldY - rMulti.GetAscent() );
nOfst = nTmpX + rMulti.Width();
} else
{
GetInfo().Y( nOldY - rMulti.GetAscent() + rMulti.Height() );
nOfst = nTmpX;
}
} elseif ( rMulti.IsBidi() )
{ // does the current bidi portion has the same direction // as its environment? if ( bEnvDir != bThisDir )
{ // different directions, we have to adjust the x coordinate
SwTwips nMultiWidth = rMulti.Width() +
rMulti.CalcSpacing( GetInfo().GetSpaceAdd(), GetInfo() );
if ( rMulti.IsBidi() )
{ // we do not allow any rotation inside a bidi portion
SwFont* pTmpFont = GetInfo().GetFont();
pTmpFont->SetVertical( 0_deg10, GetInfo().GetTextFrame()->IsVertical() );
}
if( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() )
{ // but we do allow nested bidi portions
OSL_ENSURE( rMulti.IsBidi(), "Only nesting of bidi portions is allowed" );
PaintMultiPortion( rPaint, static_cast<SwMultiPortion&>(*pPor), &rMulti );
} else
{
Por_Info const por(*pPor, *this, 0);
SwTaggedPDFHelper const tag(nullptr, nullptr, &por, *GetInfo().GetOut());
pPor->Paint( GetInfo() );
}
if (GetFnt()->IsURL() && pPor->InTextGrp())
GetInfo().NotifyURL(*pPor);
// If there's no portion left, we go to the next line if( !pPor && pLay->GetNext() )
{
pLay = pLay->GetNext();
pPor = pLay->GetFirstPortion();
bRest = pLay->IsRest();
aManip.SecondLine();
// delete underline font delete GetInfo().GetUnderFnt();
GetInfo().SetUnderFnt( nullptr );
// Determines if any part of the bidi portion fits on the current line namespace
{ enumclass BidiTruncationType
{
None,
Truncate,
Underflow
};
BidiTruncationType lcl_BidiPortionNeedsTruncation(const SwMultiPortion& rMulti, const SwTextFormatInfo& rExternalInf, const SwTextFormatInfo& rLocalInf,
TextFrameIndex const nStartIdx)
{ if (!rLocalInf.IsUnderflow())
{ // Some amount of text fits in the bidi portion without triggering underflow, // so the portion should not be truncated. return BidiTruncationType::None;
}
if (aResult.breakIndex < sal_Int32(nStartIdx))
{ // The bidi portion doesn't fit on the line, and the first break opportunity // is before the bidi portion. Underflow to the preceding text. return BidiTruncationType::Underflow;
}
if (aResult.breakIndex > sal_Int32(nStartIdx)
&& aResult.breakIndex <= sal_Int32(nStartIdx + nCurrLen))
{ // The bidi portion fits on this line, but ended with underflow. return BidiTruncationType::None;
}
// The bidi portion doesn't fit on the line, but a break position exists between the bidi // portion and the preceding text. Truncating is sufficient. return BidiTruncationType::Truncate;
}
}
// If a multi portion completely has to go to the // next line, this function is called to truncate // the rest of the remaining multi portion staticvoid lcl_TruncateMultiPortion(SwMultiPortion& rMulti, SwTextFormatInfo& rInf,
TextFrameIndex const nStartIdx,
BidiTruncationType nBidiTruncType = BidiTruncationType::None)
{
rMulti.GetRoot().Truncate();
rMulti.GetRoot().SetLen(TextFrameIndex(0));
rMulti.GetRoot().Width(0); // rMulti.CalcSize( *this, aInf ); if ( rMulti.GetRoot().GetNext() )
{
rMulti.GetRoot().GetNext()->Truncate();
rMulti.GetRoot().GetNext()->SetLen(TextFrameIndex(0));
rMulti.GetRoot().GetNext()->Width( 0 );
}
rMulti.Width( 0 );
rMulti.SetLen(TextFrameIndex(0));
rInf.SetIdx( nStartIdx );
if (rMulti.IsBidi())
{ // The truncated portion is a bidi portion. Bidi portions contain ordinary text, and may // potentially underflow in the case that none of the text fits on the current line. if (nBidiTruncType == BidiTruncationType::Underflow)
{ // The start of the bidi portion is not a valid break. Instead, a break should be // inserted into a previous text portion on this line.
rInf.SetUnderflow(&rMulti);
}
}
}
// Manages the formatting of a SwMultiPortion. External, for the calling // function, it seems to be a normal Format-function, internal it is like a // SwTextFrame::Format_ with multiple BuildPortions bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf,
SwMultiPortion& rMulti )
{
SwTwips nMaxWidth = rInf.Width();
SwTwips nOldX = 0;
SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() ); if ( rMulti.IsBidi() )
{ // set layout mode
aLayoutModeModifier.Modify( ! rInf.GetTextFrame()->IsRightToLeft() );
}
SwTwips nTmpX = 0;
if( rMulti.HasRotation() )
{ // For nMaxWidth we take the height of the body frame. // #i25067#: If the current frame is inside a table, we restrict // nMaxWidth to the current frame height, unless the frame size // attribute is set to variable size:
// We set nTmpX (which is used for portion calculating) to the // current Y value const SwPageFrame* pPage = m_pFrame->FindPageFrame();
OSL_ENSURE( pPage, "No page in frame!"); const SwLayoutFrame* pUpperFrame = pPage;
if ( m_pFrame->IsInTab() )
{
pUpperFrame = m_pFrame->GetUpper(); while ( pUpperFrame && !pUpperFrame->IsCellFrame() )
pUpperFrame = pUpperFrame->GetUpper();
assert(pUpperFrame); //pFrame is in table but does not have an upper cell frame if (!pUpperFrame) returnfalse; const SwTableLine* pLine = static_cast<const SwRowFrame*>(pUpperFrame->GetUpper())->GetTabLine(); const SwFormatFrameSize& rFrameFormatSize = pLine->GetFrameFormat()->GetFrameSize(); if ( SwFrameSize::Variable == rFrameFormatSize.GetHeightSizeType() )
pUpperFrame = pPage;
} if ( pUpperFrame == pPage && !m_pFrame->IsInFootnote() )
pUpperFrame = pPage->FindBodyCont();
// tdf#157829: Bidi portions contain text; word wrapping should underflow. // By default, the SwTextFormatInfo constructor assumes the current index is the start of // a new line. As a result, Writer cut breaks MultiPortions as if they were wider than the // entire document. This is incorrect behavior for bidi portions. if (rMulti.IsBidi())
{
aInf.SetLineStart(rInf.GetLineStart());
}
// if there's a bookmark at the start of the MultiPortion, it will be // painted with the rotation etc. of the MultiPortion; move it *inside* // so it gets positioned correctly; currently there's no other portion // inserted between the end of WhichFirstPortion() and // BuildMultiPortion() if (rInf.GetLast()->GetWhichPor() == PortionType::Bookmark)
{ autoconst pBookmark(static_cast<SwBookmarkPortion*>(rInf.GetLast())); auto *const pPrevious = pBookmark->FindPrevPortion(rInf.GetRoot());
assert(!pPrevious || pPrevious->GetNextPortion() == pBookmark); if (pPrevious)
{
pPrevious->SetNextPortion(nullptr);
}
rInf.SetLast(pPrevious);
assert(m_pCurr->GetNextPortion() == nullptr);
m_pCurr->SetNextPortion(pBookmark);
}
// in grid mode we temporarily have to disable the grid for the ruby line constbool bOldGridModeAllowed = GetInfo().SnapToGrid(); if ( bHasGrid && aInf.IsRuby() && bRubyTop )
aInf.SetSnapToGrid( false );
// If there's no more rubytext, then buildportion is forbidden if( pFirstRest || !aInf.IsRuby() )
BuildPortions( aInf );
// in grid mode we temporarily have to disable the grid for the ruby line if ( bHasGrid && aTmp.IsRuby() && ! bRubyTop )
aTmp.SetSnapToGrid( false );
BuildPortions( aTmp );
const SwLinePortion *pRightPortion = rMulti.OnRight() ?
rMulti.GetRoot().GetNext()->GetNextPortion() : nullptr; if (pRightPortion)
{ // The ruby text on the right is vertical. // The width and the height are swapped.
SwTwips nHeight = pRightPortion->Height(); // Keep room for the ruby text.
rMulti.GetRoot().FindLastPortion()->AddPrtWidth( nHeight );
}
aTmp.SetSnapToGrid( bOldGridModeAllowed );
rMulti.CalcSize( *this, aInf );
rMulti.GetRoot().SetRealHeight( rMulti.GetRoot().Height() );
m_pCurr->SetRealHeight( m_pCurr->Height() ); if( rMulti.IsRuby() )
{
pNextSecond = aTmp.GetRest(); if( pNextFirst )
bRet = true;
} else
pNextFirst = aTmp.GetRest(); if( ( !aTmp.IsRuby() && nFirstLen + m_pCurr->GetLen() < nMultiLen )
|| aTmp.GetRest() ) // our guess for width of multiportion was too small, // text did not fit into multiportion
bRet = true;
} if( rMulti.IsRuby() ) break; if( bRet )
{ // our guess for multiportion width was too small, // we set min to act
nMinWidth = nActWidth;
nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4; if ( nActWidth == nMaxWidth && rInf.GetLineStart() == rInf.GetIdx() ) // we have too less space, we must allow break cuts // ( the first multi flag is considered during TextPortion::Format_() )
bFirstMulti = false; if( nActWidth <= nMinWidth ) break;
} else
{ // For Solaris, this optimization can causes trouble: // Setting this to the portion width ( = rMulti.Width() ) // can make GetTextBreak inside SwTextGuess::Guess return too small // values. Therefore we add some extra twips. if( nActWidth > nTmpX + rMulti.Width() + 6 )
nActWidth = nTmpX + rMulti.Width() + 6;
nMaxWidth = nActWidth;
nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4; if( nActWidth >= nMaxWidth ) break; // we do not allow break cuts during formatting
bFirstMulti = true;
} delete pNextFirst;
pNextFirst = nullptr;
} while ( true );
// If the fourth element bSkipKashida of function CalcNewBlock is true, multiportion will be showed in justification. // Kashida (Persian) is a type of justification used in some cursive scripts, particularly Arabic. // In contrast to white-space justification, which increases the length of a line of text by expanding spaces between words or individual letters, // kashida justification is accomplished by elongating characters at certain chosen points. // Kashida justification can be combined with white-space justification to various extents. // The default value of bSkipKashida (the 4th parameter passed to 'CalcNewBlock') is false. // Only when Adjust is SvxAdjust::Block ( alignment is justify ), multiportion will be showed in justification in new code.
CalcNewBlock( pLine, nullptr, rMulti.Width(), GetAdjust() != SvxAdjust::Block );
// line break has to be performed! if( bRet )
{
OSL_ENSURE( !pNextFirst || pNextFirst->InFieldGrp(), "BuildMultiPortion: Surprising restportion, field expected" );
SwMultiPortion *pTmp; if( rMulti.IsDouble() )
pTmp = new SwDoubleLinePortion( static_cast<SwDoubleLinePortion&>(rMulti),
nMultiLen + rInf.GetIdx() ); elseif( rMulti.IsRuby() )
{
OSL_ENSURE( !pNextSecond || pNextSecond->InFieldGrp(), "BuildMultiPortion: Surprising restportion, field expected" );
if ( rInf.GetIdx() == rInf.GetLineStart() )
{ // the ruby portion has to be split in two portions
pTmp = new SwRubyPortion( static_cast<SwRubyPortion&>(rMulti),
nMultiLen + rInf.GetIdx() );
if( pNextSecond )
{
pTmp->GetRoot().SetNext( new SwLineLayout() );
pTmp->GetRoot().GetNext()->SetNextPortion( pNextSecond );
}
pTmp->SetFollowField();
} else
{ // we try to keep our ruby portion together
lcl_TruncateMultiPortion(rMulti, rInf, nStartIdx);
pTmp = nullptr; // A follow field portion may still be waiting. If nobody wants // it, we delete it. delete pNextSecond;
}
} elseif( rMulti.HasRotation() )
{ // we try to keep our rotated portion together
lcl_TruncateMultiPortion(rMulti, rInf, nStartIdx);
pTmp = new SwRotatedPortion( nMultiLen + rInf.GetIdx(),
rMulti.GetDirection() );
} // during a recursion of BuildMultiPortions we may not build // a new SwBidiPortion, this would cause a memory leak elseif( rMulti.IsBidi() && ! m_pMulti )
{ auto nTruncType = lcl_BidiPortionNeedsTruncation(rMulti, rInf, aInf, nStartIdx); if (nTruncType != BidiTruncationType::None)
{
lcl_TruncateMultiPortion(rMulti, rInf, nStartIdx, nTruncType);
}
// If there is a HolePortion at the end of the bidi portion, // it has to be moved behind the bidi portion. Otherwise // the visual cursor travelling gets into trouble.
SwLineLayout& aRoot = rMulti.GetRoot();
SwLinePortion* pPor = aRoot.GetFirstPortion(); while ( pPor )
{ if ( pPor->GetNextPortion() && pPor->GetNextPortion()->IsHolePortion() )
{
SwLinePortion* pHolePor = pPor->GetNextPortion();
pPor->SetNextPortion( nullptr );
aRoot.SetLen( aRoot.GetLen() - pHolePor->GetLen() );
rMulti.SetLen( rMulti.GetLen() - pHolePor->GetLen() );
rMulti.SetNextPortion( pHolePor ); break;
}
pPor = pPor->GetNextPortion();
}
if( pNextFirst && pTmp )
{
pTmp->SetFollowField();
pTmp->GetRoot().SetNextPortion( pNextFirst );
} else // A follow field portion is still waiting. If nobody wants it, // we delete it. delete pNextFirst;
// When a fieldportion at the end of line breaks and needs a following // fieldportion in the next line, then the "restportion" of the formatinfo // has to be set. Normally this happens during the formatting of the first // part of the fieldportion. // But sometimes the formatting starts at the line with the following part, // especially when the following part is on the next page. // In this case the MakeRestPortion-function has to create the following part. // The first parameter is the line that contains possibly a first part // of a field. When the function finds such field part, it creates the right // restportion. This may be a multiportion, e.g. if the field is surrounded by // a doubleline- or ruby-portion. // The second parameter is the start index of the line.
SwLinePortion* SwTextFormatter::MakeRestPortion( const SwLineLayout* pLine,
TextFrameIndex nPosition)
{ if( !nPosition ) return nullptr;
TextFrameIndex nMultiPos = nPosition - pLine->GetLen(); const SwMultiPortion *pTmpMulti = nullptr; const SwMultiPortion *pHelpMulti = nullptr; const SwLinePortion* pPor = pLine->GetFirstPortion();
SwFieldPortion *pField = nullptr; while( pPor )
{ if( pPor->GetLen() && !pHelpMulti )
{
nMultiPos = nMultiPos + pPor->GetLen();
pTmpMulti = nullptr;
} if( pPor->InFieldGrp() )
{ if( !pHelpMulti )
pTmpMulti = nullptr;
pField = const_cast<SwFieldPortion*>(static_cast<const SwFieldPortion*>(pPor));
} elseif( pPor->IsMultiPortion() )
{
OSL_ENSURE( !pHelpMulti || pHelpMulti->IsBidi(), "Nested multiportions are forbidden." );
pField = nullptr;
pTmpMulti = static_cast<const SwMultiPortion*>(pPor);
}
pPor = pPor->GetNextPortion(); // If the last portion is a multi-portion, we enter it // and look for a field portion inside. // If we are already in a multiportion, we could change to the // next line if( !pPor && pTmpMulti )
{ if( pHelpMulti )
{ // We're already inside the multiportion, let's take the second // line, if we are in a double line portion if( !pHelpMulti->IsRuby() )
pPor = pHelpMulti->GetRoot().GetNext();
pTmpMulti = nullptr;
} else
{ // Now we enter a multiportion, in a ruby portion we take the // main line, not the phonetic line, in a doublelineportion we // starts with the first line.
pHelpMulti = pTmpMulti;
nMultiPos = nMultiPos - pHelpMulti->GetLen(); if( pHelpMulti->IsRuby() && pHelpMulti->OnTop() )
pPor = pHelpMulti->GetRoot().GetNext(); else
pPor = pHelpMulti->GetRoot().GetFirstPortion();
}
}
} if( pField && !pField->HasFollow() )
pField = nullptr;
// SwTextCursorSave notes the start and current line of a SwTextCursor, // sets them to the values for GetModelPositionForViewPoint inside a multiportion // and restores them in the destructor.
SwTextCursorSave::SwTextCursorSave( SwTextCursor* pCursor,
SwMultiPortion* pMulti,
SwTwips nY,
SwTwips& nX,
TextFrameIndex const nCurrStart,
tools::Long nSpaceAdd )
: pTextCursor(pCursor),
pCurr(pCursor->m_pCurr),
nStart(pCursor->m_nStart)
{
pCursor->m_nStart = nCurrStart;
pCursor->m_pCurr = &pMulti->GetRoot(); while( pCursor->Y() + pCursor->GetLineHeight() < nY &&
pCursor->Next() )
; // nothing
nWidth = pCursor->m_pCurr->Width();
nOldProp = pCursor->GetPropFont();
// For a BidiPortion we have to calculate the offset from the // end of the portion if ( nX && pMulti->IsBidi() )
nX = pCursor->m_pCurr->Width() - nX;
} else
bSpaceChg = false;
}
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.66Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-05-07)
¤
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.