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