1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
23 #include <hintids.hxx>
25 #include <editeng/twolinesitem.hxx>
26 #include <editeng/charrotateitem.hxx>
27 #include <vcl/outdev.hxx>
28 #include <txatbase.hxx>
29 #include <fmtruby.hxx>
31 #include <charfmt.hxx>
33 #include <SwPortionHandler.hxx>
34 #include <EnhancedPDFExportHelper.hxx>
35 #include <com/sun/star/i18n/BreakType.hpp>
36 #include <com/sun/star/i18n/XBreakIterator.hpp>
37 #include <breakit.hxx>
38 #include "pormulti.hxx"
40 #include "itrpaint.hxx"
41 #include <viewopt.hxx>
42 #include "itrform2.hxx"
44 #include "porglue.hxx"
46 #include <pagefrm.hxx>
48 #include <tgrditem.hxx>
49 #include <swtable.hxx>
50 #include <fmtfsize.hxx>
53 using namespace ::com::sun::star
;
55 // A SwMultiPortion is not a simple portion,
56 // it's a container, which contains almost a SwLineLayoutPortion.
57 // This SwLineLayout could be followed by other textportions via pPortion
58 // and by another SwLineLayout via pNext to realize a doubleline portion.
59 SwMultiPortion::~SwMultiPortion()
63 void SwMultiPortion::Paint( const SwTextPaintInfo
& ) const
65 OSL_FAIL( "Don't try SwMultiPortion::Paint, try SwTextPainter::PaintMultiPortion" );
68 // Summarize the internal lines to calculate the (external) size.
69 // The internal line has to calculate first.
70 void SwMultiPortion::CalcSize( SwTextFormatter
& rLine
, SwTextFormatInfo
&rInf
)
75 SetFlyInContent( false );
76 SwLineLayout
*pLay
= &GetRoot();
79 pLay
->CalcLine( rLine
, rInf
);
80 if( rLine
.IsFlyInCntBase() )
81 SetFlyInContent( true );
82 if( IsRuby() && ( OnTop() == ( pLay
== &GetRoot() ) ) )
84 // An empty phonetic line don't need an ascent or a height.
91 SetAscent( GetAscent() + pLay
->Height() );
94 SetAscent( GetAscent() + pLay
->GetAscent() );
96 // Increase the line height, except for ruby text on the right.
97 if ( !IsRuby() || !OnRight() || pLay
== &GetRoot() )
98 Height( Height() + pLay
->Height() );
101 // We already added the width after building the portion,
102 // so no need to add it twice.
106 if( Width() < pLay
->Width() )
107 Width( pLay
->Width() );
108 pLay
= pLay
->GetNext();
113 sal_uInt16 nTmp
= static_cast<SwDoubleLinePortion
*>(this)->GetBrackets()->nHeight
;
114 if( nTmp
> Height() )
116 const sal_uInt16 nAdd
= ( nTmp
- Height() ) / 2;
117 GetRoot().SetAscent( GetRoot().GetAscent() + nAdd
);
118 GetRoot().Height( GetRoot().Height() + nAdd
);
121 nTmp
= static_cast<SwDoubleLinePortion
*>(this)->GetBrackets()->nAscent
;
122 if( nTmp
> GetAscent() )
126 SwTwips
SwMultiPortion::CalcSpacing( tools::Long
, const SwTextSizeInfo
& ) const
131 bool SwMultiPortion::ChgSpaceAdd( SwLineLayout
*, tools::Long
) const
136 void SwMultiPortion::HandlePortion( SwPortionHandler
& rPH
) const
138 rPH
.Text( GetLen(), GetWhichPor() );
141 void SwMultiPortion::dumpAsXml(xmlTextWriterPtr pWriter
, const OUString
& rText
,
142 TextFrameIndex
& nOffset
) const
144 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("SwMultiPortion"));
145 dumpAsXmlAttributes(pWriter
, rText
, nOffset
);
146 // Intentionally not incrementing nOffset here, one of the child portions will do that.
148 const SwLineLayout
* pLine
= &GetRoot();
151 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("SwLineLayout"));
152 pLine
->dumpAsXmlAttributes(pWriter
, rText
, nOffset
);
153 const SwLinePortion
* pPor
= pLine
->GetFirstPortion();
156 pPor
->dumpAsXml(pWriter
, rText
, nOffset
);
157 pPor
= pPor
->GetNextPortion();
159 (void)xmlTextWriterEndElement(pWriter
);
160 pLine
= pLine
->GetNext();
163 (void)xmlTextWriterEndElement(pWriter
);
166 // sets the tabulator-flag, if there's any tabulator-portion inside.
167 void SwMultiPortion::ActualizeTabulator()
169 SwLinePortion
* pPor
= GetRoot().GetFirstPortion();
171 for( m_bTab1
= m_bTab2
= false; pPor
; pPor
= pPor
->GetNextPortion() )
172 if( pPor
->InTabGrp() )
174 if( GetRoot().GetNext() )
177 pPor
= GetRoot().GetNext()->GetFirstPortion();
180 if( pPor
->InTabGrp() )
182 pPor
= pPor
->GetNextPortion();
187 SwRotatedPortion::SwRotatedPortion( const SwMultiCreator
& rCreate
,
188 TextFrameIndex
const nEnd
, bool bRTL
)
189 : SwMultiPortion( nEnd
)
191 const SvxCharRotateItem
* pRot
= static_cast<const SvxCharRotateItem
*>(rCreate
.pItem
);
194 const SwTextAttr
& rAttr
= *rCreate
.pAttr
;
195 const SfxPoolItem
*const pItem
=
196 CharFormat::GetItem(rAttr
, RES_CHRATR_ROTATE
);
199 pRot
= static_cast<const SvxCharRotateItem
*>(pItem
);
206 nDir
= pRot
->IsBottomToTop() ? 3 : 1;
208 nDir
= pRot
->IsBottomToTop() ? 1 : 3;
210 SetDirection( nDir
);
214 SwBidiPortion::SwBidiPortion(TextFrameIndex
const nEnd
, sal_uInt8 nLv
)
215 : SwMultiPortion( nEnd
), m_nLevel( nLv
)
220 SetDirection( DIR_RIGHT2LEFT
);
222 SetDirection( DIR_LEFT2RIGHT
);
225 SwTwips
SwBidiPortion::CalcSpacing( tools::Long nSpaceAdd
, const SwTextSizeInfo
& rInf
) const
227 nSpaceAdd
= nSpaceAdd
> LONG_MAX
/2 ? LONG_MAX
/2 - nSpaceAdd
: nSpaceAdd
;
228 return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt(rInf
)) * nSpaceAdd
/ SPACING_PRECISION_FACTOR
;
231 bool SwBidiPortion::ChgSpaceAdd( SwLineLayout
* pCurr
, tools::Long nSpaceAdd
) const
233 if( !HasTabulator() && nSpaceAdd
> 0 && !pCurr
->IsSpaceAdd() )
235 pCurr
->CreateSpaceAdd();
236 pCurr
->SetLLSpaceAdd( nSpaceAdd
, 0 );
243 TextFrameIndex
SwBidiPortion::GetSpaceCnt(const SwTextSizeInfo
&rInf
) const
245 // Calculate number of blanks for justified alignment
246 TextFrameIndex nTmpStart
= rInf
.GetIdx();
247 TextFrameIndex
nNull(0);
248 TextFrameIndex
nBlanks(0);
250 for (SwLinePortion
* pPor
= GetRoot().GetFirstPortion(); pPor
; pPor
= pPor
->GetNextPortion())
252 if( pPor
->InTextGrp() )
253 nBlanks
= nBlanks
+ static_cast<SwTextPortion
*>(pPor
)->GetSpaceCnt( rInf
, nNull
);
254 else if ( pPor
->IsMultiPortion() &&
255 static_cast<SwMultiPortion
*>(pPor
)->IsBidi() )
256 nBlanks
= nBlanks
+ static_cast<SwBidiPortion
*>(pPor
)->GetSpaceCnt( rInf
);
258 const_cast<SwTextSizeInfo
&>(rInf
).SetIdx( rInf
.GetIdx() + pPor
->GetLen() );
260 const_cast<SwTextSizeInfo
&>(rInf
).SetIdx( nTmpStart
);
264 // This constructor is for the continuation of a doubleline portion
266 // It takes the same brackets and if the original has no content except
267 // brackets, these will be deleted.
268 SwDoubleLinePortion::SwDoubleLinePortion(
269 SwDoubleLinePortion
& rDouble
, TextFrameIndex
const nEnd
)
270 : SwMultiPortion(nEnd
)
275 SetDirection( rDouble
.GetDirection() );
277 if( rDouble
.GetBrackets() )
279 SetBrackets( rDouble
);
280 // An empty multiportion needs no brackets.
281 // Notice: GetLen() might be zero, if the multiportion contains
282 // the second part of a field and the width might be zero, if
283 // it contains a note only. In this cases the brackets are okay.
284 // But if the length and the width are both zero, the portion
286 if( rDouble
.Width() == rDouble
.BracketWidth() )
287 rDouble
.ClearBrackets();
291 // This constructor uses the textattribute to get the right brackets.
292 // The textattribute could be a 2-line-attribute or a character- or
293 // internet style, which contains the 2-line-attribute.
294 SwDoubleLinePortion::SwDoubleLinePortion(
295 const SwMultiCreator
& rCreate
, TextFrameIndex
const nEnd
)
296 : SwMultiPortion(nEnd
)
297 , m_pBracket(new SwBracket
)
302 m_pBracket
->nAscent
= 0;
303 m_pBracket
->nHeight
= 0;
304 m_pBracket
->nPreWidth
= 0;
305 m_pBracket
->nPostWidth
= 0;
308 const SvxTwoLinesItem
* pTwo
= static_cast<const SvxTwoLinesItem
*>(rCreate
.pItem
);
310 m_pBracket
->nStart
= TextFrameIndex(0);
313 const SwTextAttr
& rAttr
= *rCreate
.pAttr
;
314 m_pBracket
->nStart
= rCreate
.nStartOfAttr
;
316 const SfxPoolItem
* const pItem
=
317 CharFormat::GetItem( rAttr
, RES_CHRATR_TWO_LINES
);
320 pTwo
= static_cast<const SvxTwoLinesItem
*>(pItem
);
325 m_pBracket
->cPre
= pTwo
->GetStartBracket();
326 m_pBracket
->cPost
= pTwo
->GetEndBracket();
330 m_pBracket
->cPre
= 0;
331 m_pBracket
->cPost
= 0;
333 SwFontScript nTmp
= SW_SCRIPTS
;
334 if( m_pBracket
->cPre
> 255 )
336 OUString
aText(m_pBracket
->cPre
);
337 nTmp
= SwScriptInfo::WhichFont(0, aText
);
339 m_pBracket
->nPreScript
= nTmp
;
341 if( m_pBracket
->cPost
> 255 )
343 OUString
aText(m_pBracket
->cPost
);
344 nTmp
= SwScriptInfo::WhichFont(0, aText
);
346 m_pBracket
->nPostScript
= nTmp
;
348 if( !m_pBracket
->cPre
&& !m_pBracket
->cPost
)
353 // double line portions have the same direction as the frame directions
354 if ( rCreate
.nLevel
% 2 )
355 SetDirection( DIR_RIGHT2LEFT
);
357 SetDirection( DIR_LEFT2RIGHT
);
360 // paints the wished bracket,
361 // if the multiportion has surrounding brackets.
362 // The X-position of the SwTextPaintInfo will be modified:
363 // the open bracket sets position behind itself,
364 // the close bracket in front of itself.
365 void SwDoubleLinePortion::PaintBracket( SwTextPaintInfo
&rInf
,
366 tools::Long nSpaceAdd
,
369 sal_Unicode cCh
= bOpen
? m_pBracket
->cPre
: m_pBracket
->cPost
;
372 const sal_uInt16 nChWidth
= bOpen
? PreWidth() : PostWidth();
376 rInf
.X( rInf
.X() + Width() - PostWidth() +
377 ( nSpaceAdd
> 0 ? CalcSpacing( nSpaceAdd
, rInf
) : 0 ) );
379 SwBlankPortion
aBlank( cCh
, true );
380 aBlank
.SetAscent( m_pBracket
->nAscent
);
381 aBlank
.Width( nChWidth
);
382 aBlank
.Height( m_pBracket
->nHeight
);
384 SwFont
aTmpFnt( *rInf
.GetFont() );
385 SwFontScript nAct
= bOpen
? m_pBracket
->nPreScript
: m_pBracket
->nPostScript
;
386 if( SW_SCRIPTS
> nAct
)
387 aTmpFnt
.SetActual( nAct
);
388 aTmpFnt
.SetProportion( 100 );
389 SwFontSave
aSave( rInf
, &aTmpFnt
);
390 aBlank
.Paint( rInf
);
393 rInf
.X( rInf
.X() + PreWidth() );
396 // creates the bracket-structure
397 // and fills it, if not both characters are 0x00.
398 void SwDoubleLinePortion::SetBrackets( const SwDoubleLinePortion
& rDouble
)
400 if( rDouble
.m_pBracket
)
402 m_pBracket
.reset( new SwBracket
);
403 m_pBracket
->cPre
= rDouble
.m_pBracket
->cPre
;
404 m_pBracket
->cPost
= rDouble
.m_pBracket
->cPost
;
405 m_pBracket
->nPreScript
= rDouble
.m_pBracket
->nPreScript
;
406 m_pBracket
->nPostScript
= rDouble
.m_pBracket
->nPostScript
;
407 m_pBracket
->nStart
= rDouble
.m_pBracket
->nStart
;
411 // calculates the size of the brackets => pBracket,
412 // reduces the nMaxWidth-parameter ( minus bracket-width )
413 // and moves the rInf-x-position behind the opening bracket.
414 void SwDoubleLinePortion::FormatBrackets( SwTextFormatInfo
&rInf
, SwTwips
& nMaxWidth
)
416 nMaxWidth
-= rInf
.X();
417 SwFont
aTmpFnt( *rInf
.GetFont() );
418 aTmpFnt
.SetProportion( 100 );
419 m_pBracket
->nAscent
= 0;
420 m_pBracket
->nHeight
= 0;
421 if( m_pBracket
->cPre
)
423 OUString
aStr( m_pBracket
->cPre
);
424 SwFontScript nActualScr
= aTmpFnt
.GetActual();
425 if( SW_SCRIPTS
> m_pBracket
->nPreScript
)
426 aTmpFnt
.SetActual( m_pBracket
->nPreScript
);
427 SwFontSave
aSave( rInf
, &aTmpFnt
);
428 SwPosSize aSize
= rInf
.GetTextSize( aStr
);
429 m_pBracket
->nAscent
= rInf
.GetAscent();
430 m_pBracket
->nHeight
= aSize
.Height();
431 aTmpFnt
.SetActual( nActualScr
);
432 if( nMaxWidth
> aSize
.Width() )
434 m_pBracket
->nPreWidth
= aSize
.Width();
435 nMaxWidth
-= aSize
.Width();
436 rInf
.X( rInf
.X() + aSize
.Width() );
440 m_pBracket
->nPreWidth
= 0;
445 m_pBracket
->nPreWidth
= 0;
446 if( m_pBracket
->cPost
)
448 OUString
aStr( m_pBracket
->cPost
);
449 if( SW_SCRIPTS
> m_pBracket
->nPostScript
)
450 aTmpFnt
.SetActual( m_pBracket
->nPostScript
);
451 SwFontSave
aSave( rInf
, &aTmpFnt
);
452 SwPosSize aSize
= rInf
.GetTextSize( aStr
);
453 const sal_uInt16 nTmpAsc
= rInf
.GetAscent();
454 if( nTmpAsc
> m_pBracket
->nAscent
)
456 m_pBracket
->nHeight
+= nTmpAsc
- m_pBracket
->nAscent
;
457 m_pBracket
->nAscent
= nTmpAsc
;
459 if( aSize
.Height() > m_pBracket
->nHeight
)
460 m_pBracket
->nHeight
= aSize
.Height();
461 if( nMaxWidth
> aSize
.Width() )
463 m_pBracket
->nPostWidth
= aSize
.Width();
464 nMaxWidth
-= aSize
.Width();
468 m_pBracket
->nPostWidth
= 0;
473 m_pBracket
->nPostWidth
= 0;
474 nMaxWidth
+= rInf
.X();
477 // calculates the number of blanks in each line and
478 // the difference of the width of the two lines.
479 // These results are used from the text adjustment.
480 void SwDoubleLinePortion::CalcBlanks( SwTextFormatInfo
&rInf
)
482 SwLinePortion
* pPor
= GetRoot().GetFirstPortion();
483 TextFrameIndex
nNull(0);
484 TextFrameIndex nStart
= rInf
.GetIdx();
487 for (m_nBlank1
= TextFrameIndex(0); pPor
; pPor
= pPor
->GetNextPortion())
489 if( pPor
->InTextGrp() )
490 m_nBlank1
= m_nBlank1
+ static_cast<SwTextPortion
*>(pPor
)->GetSpaceCnt( rInf
, nNull
);
491 rInf
.SetIdx( rInf
.GetIdx() + pPor
->GetLen() );
492 if( pPor
->InTabGrp() )
495 m_nLineDiff
= GetRoot().Width();
496 if( GetRoot().GetNext() )
498 pPor
= GetRoot().GetNext()->GetFirstPortion();
499 m_nLineDiff
-= GetRoot().GetNext()->Width();
501 for (m_nBlank2
= TextFrameIndex(0); pPor
; pPor
= pPor
->GetNextPortion())
503 if( pPor
->InTextGrp() )
504 m_nBlank2
= m_nBlank2
+ static_cast<SwTextPortion
*>(pPor
)->GetSpaceCnt( rInf
, nNull
);
505 rInf
.SetIdx( rInf
.GetIdx() + pPor
->GetLen() );
506 if( pPor
->InTabGrp() )
509 rInf
.SetIdx( nStart
);
512 SwTwips
SwDoubleLinePortion::CalcSpacing( tools::Long nSpaceAdd
, const SwTextSizeInfo
& ) const
514 nSpaceAdd
= nSpaceAdd
> LONG_MAX
/2 ? LONG_MAX
/2 - nSpaceAdd
: nSpaceAdd
;
515 return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt()) * nSpaceAdd
/ SPACING_PRECISION_FACTOR
;
518 // Merges the spaces for text adjustment from the inner and outer part.
519 // Inside the doubleline portion the wider line has no spaceadd-array, the
520 // smaller line has such an array to reach width of the wider line.
521 // If the surrounding line has text adjustment and the doubleline portion
522 // contains no tabulator, it is necessary to create/manipulate the inner
524 bool SwDoubleLinePortion::ChgSpaceAdd( SwLineLayout
* pCurr
,
525 tools::Long nSpaceAdd
) const
528 if( !HasTabulator() && nSpaceAdd
> 0 )
530 if( !pCurr
->IsSpaceAdd() )
532 // The wider line gets the spaceadd from the surrounding line direct
533 pCurr
->CreateSpaceAdd();
534 pCurr
->SetLLSpaceAdd( nSpaceAdd
, 0 );
539 sal_Int32
const nMyBlank
= sal_Int32(GetSmallerSpaceCnt());
540 sal_Int32
const nOther
= sal_Int32(GetSpaceCnt());
541 SwTwips nMultiSpace
= pCurr
->GetLLSpaceAdd( 0 ) * nMyBlank
+ nOther
* nSpaceAdd
;
544 nMultiSpace
/= sal_Int32(nMyBlank
);
546 // pCurr->SetLLSpaceAdd( nMultiSpace, 0 );
547 // #i65711# SetLLSpaceAdd replaces the first value,
548 // instead we want to insert a new first value:
549 std::vector
<tools::Long
>* pVec
= pCurr
->GetpLLSpaceAdd();
550 pVec
->insert( pVec
->begin(), nMultiSpace
);
556 // cancels the manipulation from SwDoubleLinePortion::ChangeSpaceAdd(..)
557 void SwDoubleLinePortion::ResetSpaceAdd( SwLineLayout
* pCurr
)
559 pCurr
->RemoveFirstLLSpaceAdd();
560 if( !pCurr
->GetLLSpaceAddCount() )
561 pCurr
->FinishSpaceAdd();
564 SwDoubleLinePortion::~SwDoubleLinePortion()
568 // constructs a ruby portion, i.e. an additional text is displayed
569 // beside the main text, e.g. phonetic characters.
570 SwRubyPortion::SwRubyPortion(const SwRubyPortion
& rRuby
, TextFrameIndex
const nEnd
)
571 : SwMultiPortion( nEnd
)
572 , m_nRubyOffset( rRuby
.GetRubyOffset() )
573 , m_nAdjustment( rRuby
.GetAdjustment() )
575 SetDirection( rRuby
.GetDirection() );
576 SetRubyPosition( rRuby
.GetRubyPosition() );
580 // constructs a ruby portion, i.e. an additional text is displayed
581 // beside the main text, e.g. phonetic characters.
582 SwRubyPortion::SwRubyPortion( const SwMultiCreator
& rCreate
, const SwFont
& rFnt
,
583 const IDocumentSettingAccess
& rIDocumentSettingAccess
,
584 TextFrameIndex
const nEnd
, TextFrameIndex
const nOffs
,
585 const SwTextSizeInfo
&rInf
)
586 : SwMultiPortion( nEnd
)
589 OSL_ENSURE( SwMultiCreatorId::Ruby
== rCreate
.nId
, "Ruby expected" );
590 OSL_ENSURE( RES_TXTATR_CJK_RUBY
== rCreate
.pAttr
->Which(), "Wrong attribute" );
591 const SwFormatRuby
& rRuby
= rCreate
.pAttr
->GetRuby();
592 m_nAdjustment
= rRuby
.GetAdjustment();
593 m_nRubyOffset
= nOffs
;
595 const SwTextFrame
*pFrame
= rInf
.GetTextFrame();
596 RubyPosition ePos
= static_cast<RubyPosition
>( rRuby
.GetPosition() );
598 // RIGHT is designed for horizontal writing mode only.
599 if ( ePos
== RubyPosition::RIGHT
&& pFrame
->IsVertical() )
600 ePos
= RubyPosition::ABOVE
;
602 // In grid mode we force the ruby text to the upper or lower line
603 if ( rInf
.SnapToGrid() )
605 SwTextGridItem
const*const pGrid( GetGridItem(pFrame
->FindPageFrame()) );
607 ePos
= pGrid
->GetRubyTextBelow() ? RubyPosition::BELOW
: RubyPosition::ABOVE
;
610 SetRubyPosition( ePos
);
612 const SwCharFormat
*const pFormat
=
613 static_txtattr_cast
<SwTextRuby
const*>(rCreate
.pAttr
)->GetCharFormat();
614 std::unique_ptr
<SwFont
> pRubyFont
;
617 const SwAttrSet
& rSet
= pFormat
->GetAttrSet();
618 pRubyFont
.reset(new SwFont( rFnt
));
619 pRubyFont
->SetDiffFnt( &rSet
, &rIDocumentSettingAccess
);
621 // we do not allow a vertical font for the ruby text
622 pRubyFont
->SetVertical( rFnt
.GetOrientation() , OnRight() );
625 OUString aStr
= rRuby
.GetText().copy( sal_Int32(nOffs
) );
626 SwFieldPortion
*pField
= new SwFieldPortion( std::move(aStr
), std::move(pRubyFont
) );
627 pField
->SetNextOffset( nOffs
);
628 pField
->SetFollow( true );
631 GetRoot().SetNextPortion( pField
);
634 GetRoot().SetNext( new SwLineLayout() );
635 GetRoot().GetNext()->SetNextPortion( pField
);
638 // ruby portions have the same direction as the frame directions
639 if ( rCreate
.nLevel
% 2 )
641 // switch right and left ruby adjustment in rtl environment
642 if ( css::text::RubyAdjust_LEFT
== m_nAdjustment
)
643 m_nAdjustment
= css::text::RubyAdjust_RIGHT
;
644 else if ( css::text::RubyAdjust_RIGHT
== m_nAdjustment
)
645 m_nAdjustment
= css::text::RubyAdjust_LEFT
;
647 SetDirection( DIR_RIGHT2LEFT
);
650 SetDirection( DIR_LEFT2RIGHT
);
653 // In ruby portion there are different alignments for
654 // the ruby text and the main text.
655 // Left, right, centered and two possibilities of block adjustment
656 // The block adjustment is realized by spacing between the characters,
657 // either with a half space or no space in front of the first letter and
658 // a half space at the end of the last letter.
659 // Notice: the smaller line will be manipulated, normally it's the ruby line,
660 // but it could be the main text, too.
661 // If there is a tabulator in smaller line, no adjustment is possible.
662 void SwRubyPortion::Adjust_( SwTextFormatInfo
&rInf
)
664 SwTwips nLineDiff
= GetRoot().Width() - GetRoot().GetNext()->Width();
665 TextFrameIndex
const nOldIdx
= rInf
.GetIdx();
670 { // The first line has to be adjusted.
674 nLineDiff
= -nLineDiff
;
677 { // The second line has to be adjusted.
680 pCurr
= GetRoot().GetNext();
681 rInf
.SetIdx( nOldIdx
+ GetRoot().GetLen() );
683 sal_uInt16 nLeft
= 0; // the space in front of the first letter
684 sal_uInt16 nRight
= 0; // the space at the end of the last letter
685 TextFrameIndex
nSub(0);
686 switch ( m_nAdjustment
)
688 case css::text::RubyAdjust_CENTER
: nRight
= o3tl::narrowing
<sal_uInt16
>(nLineDiff
/ 2);
690 case css::text::RubyAdjust_RIGHT
: nLeft
= o3tl::narrowing
<sal_uInt16
>(nLineDiff
- nRight
); break;
691 case css::text::RubyAdjust_BLOCK
: nSub
= TextFrameIndex(1);
693 case css::text::RubyAdjust_INDENT_BLOCK
:
695 TextFrameIndex
nCharCnt(0);
697 for( pPor
= pCurr
->GetFirstPortion(); pPor
; pPor
= pPor
->GetNextPortion() )
699 if( pPor
->InTextGrp() )
700 static_cast<SwTextPortion
*>(pPor
)->GetSpaceCnt( rInf
, nCharCnt
);
701 rInf
.SetIdx( rInf
.GetIdx() + pPor
->GetLen() );
703 if( nCharCnt
> nSub
)
705 SwTwips nCalc
= nLineDiff
/ sal_Int32(nCharCnt
- nSub
);
707 if( nCalc
< SHRT_MAX
)
708 nTmp
= -short(nCalc
);
712 pCurr
->CreateSpaceAdd( SPACING_PRECISION_FACTOR
* nTmp
);
713 nLineDiff
-= nCalc
* (sal_Int32(nCharCnt
) - 1);
717 nRight
= o3tl::narrowing
<sal_uInt16
>(nLineDiff
/ 2);
718 nLeft
= o3tl::narrowing
<sal_uInt16
>(nLineDiff
- nRight
);
722 default: OSL_FAIL( "New ruby adjustment" );
724 if( nLeft
|| nRight
)
726 if( !pCurr
->GetNextPortion() )
727 pCurr
->SetNextPortion(SwTextPortion::CopyLinePortion(*pCurr
));
730 SwMarginPortion
*pMarg
= new SwMarginPortion
;
731 pMarg
->AddPrtWidth( nLeft
);
732 pMarg
->SetNextPortion( pCurr
->GetNextPortion() );
733 pCurr
->SetNextPortion( pMarg
);
737 SwMarginPortion
*pMarg
= new SwMarginPortion
;
738 pMarg
->AddPrtWidth( nRight
);
739 pCurr
->FindLastPortion()->Append( pMarg
);
743 pCurr
->Width( Width() );
744 rInf
.SetIdx( nOldIdx
);
747 // has to change the nRubyOffset, if there's a fieldportion
748 // in the phonetic line.
749 // The nRubyOffset is the position in the rubystring, where the
750 // next SwRubyPortion has start the displaying of the phonetics.
751 void SwRubyPortion::CalcRubyOffset()
753 const SwLineLayout
*pCurr
= &GetRoot();
756 pCurr
= pCurr
->GetNext();
760 const SwLinePortion
*pPor
= pCurr
->GetFirstPortion();
761 const SwFieldPortion
*pField
= nullptr;
764 if( pPor
->InFieldGrp() )
765 pField
= static_cast<const SwFieldPortion
*>(pPor
);
766 pPor
= pPor
->GetNextPortion();
770 if( pField
->HasFollow() )
771 m_nRubyOffset
= pField
->GetNextOffset();
773 m_nRubyOffset
= TextFrameIndex(COMPLETE_STRING
);
777 // A little helper function for GetMultiCreator(..)
778 // It extracts the 2-line-format from a 2-line-attribute or a character style.
779 // The rValue is set to true, if the 2-line-attribute's value is set and
780 // no 2-line-format reference is passed. If there is a 2-line-format reference,
781 // then the rValue is set only, if the 2-line-attribute's value is set _and_
782 // the 2-line-formats has the same brackets.
783 static bool lcl_Check2Lines(const SfxPoolItem
*const pItem
,
784 const SvxTwoLinesItem
* &rpRef
, bool &rValue
)
788 rValue
= static_cast<const SvxTwoLinesItem
*>(pItem
)->GetValue();
790 rpRef
= static_cast<const SvxTwoLinesItem
*>(pItem
);
791 else if( static_cast<const SvxTwoLinesItem
*>(pItem
)->GetEndBracket() !=
792 rpRef
->GetEndBracket() ||
793 static_cast<const SvxTwoLinesItem
*>(pItem
)->GetStartBracket() !=
794 rpRef
->GetStartBracket() )
801 static bool lcl_Has2Lines(const SwTextAttr
& rAttr
,
802 const SvxTwoLinesItem
* &rpRef
, bool &rValue
)
804 const SfxPoolItem
* pItem
= CharFormat::GetItem(rAttr
, RES_CHRATR_TWO_LINES
);
805 return lcl_Check2Lines(pItem
, rpRef
, rValue
);
808 // is a little help function for GetMultiCreator(..)
809 // It extracts the charrotation from a charrotate-attribute or a character style.
810 // The rValue is set to true, if the charrotate-attribute's value is set and
811 // no charrotate-format reference is passed.
812 // If there is a charrotate-format reference, then the rValue is set only,
813 // if the charrotate-attribute's value is set _and_ identical
814 // to the charrotate-format's value.
815 static bool lcl_CheckRotation(const SfxPoolItem
*const pItem
,
816 const SvxCharRotateItem
* &rpRef
, bool &rValue
)
820 rValue
= static_cast<const SvxCharRotateItem
*>(pItem
)->GetValue() != 0_deg10
;
822 rpRef
= static_cast<const SvxCharRotateItem
*>(pItem
);
823 else if( static_cast<const SvxCharRotateItem
*>(pItem
)->GetValue() !=
832 static bool lcl_HasRotation(const SwTextAttr
& rAttr
,
833 const SvxCharRotateItem
* &rpRef
, bool &rValue
)
835 const SfxPoolItem
* pItem
= CharFormat::GetItem( rAttr
, RES_CHRATR_ROTATE
);
836 return lcl_CheckRotation(pItem
, rpRef
, rValue
);
842 // need to use a very special attribute iterator here that returns
843 // both the hints and the nodes, so that GetMultiCreator() can handle
844 // items in the nodes' set properly
845 class MergedAttrIterMulti
846 : public MergedAttrIterBase
851 MergedAttrIterMulti(SwTextFrame
const& rFrame
) : MergedAttrIterBase(rFrame
) {}
852 SwTextAttr
const* NextAttr(SwTextNode
const*& rpNode
);
853 // can't have operator= because m_pMerged/m_pNode const
854 void Assign(MergedAttrIterMulti
const& rOther
)
856 assert(m_pMerged
== rOther
.m_pMerged
);
857 assert(m_pNode
== rOther
.m_pNode
);
858 m_CurrentExtent
= rOther
.m_CurrentExtent
;
859 m_CurrentHint
= rOther
.m_CurrentHint
;
860 m_First
= rOther
.m_First
;
866 SwTextAttr
const* MergedAttrIterMulti::NextAttr(SwTextNode
const*& rpNode
)
872 ? !m_pMerged
->extents
.empty()
873 ? m_pMerged
->extents
[0].pNode
874 : m_pMerged
->pFirstNode
880 const auto nExtentsSize
= m_pMerged
->extents
.size();
881 while (m_CurrentExtent
< nExtentsSize
)
883 sw::Extent
const& rExtent(m_pMerged
->extents
[m_CurrentExtent
]);
884 if (SwpHints
const*const pHints
= rExtent
.pNode
->GetpSwpHints())
886 auto nHintsCount
= pHints
->Count();
887 while (m_CurrentHint
< nHintsCount
)
889 SwTextAttr
const*const pHint(pHints
->Get(m_CurrentHint
));
890 if (rExtent
.nEnd
< pHint
->GetStart())
895 if (rExtent
.nStart
<= pHint
->GetStart())
897 rpNode
= rExtent
.pNode
;
903 if (m_CurrentExtent
< nExtentsSize
&&
904 rExtent
.pNode
!= m_pMerged
->extents
[m_CurrentExtent
].pNode
)
906 m_CurrentHint
= 0; // reset
907 rpNode
= m_pMerged
->extents
[m_CurrentExtent
].pNode
;
915 SwpHints
const*const pHints(m_pNode
->GetpSwpHints());
918 if (m_CurrentHint
< pHints
->Count())
920 SwTextAttr
const*const pHint(pHints
->Get(m_CurrentHint
));
931 // If we (e.g. the position rPos) are inside a two-line-attribute or
932 // a ruby-attribute, the attribute will be returned in a SwMultiCreator-struct,
933 // otherwise the function returns zero.
934 // The rPos parameter is set to the end of the multiportion,
935 // normally this is the end of the attribute,
936 // but sometimes it is the start of another attribute, which finished or
937 // interrupts the first attribute.
938 // E.g. a ruby portion interrupts a 2-line-attribute, a 2-line-attribute
939 // with different brackets interrupts another 2-line-attribute.
940 std::optional
<SwMultiCreator
> SwTextSizeInfo::GetMultiCreator(TextFrameIndex
&rPos
,
941 SwMultiPortion
const * pMulti
) const
943 SwScriptInfo
& rSI
= const_cast<SwParaPortion
*>(GetParaPortion())->GetScriptInfo();
945 // get the last embedding level
946 sal_uInt8 nCurrLevel
;
949 OSL_ENSURE( pMulti
->IsBidi(), "Nested MultiPortion is not BidiPortion" );
950 // level associated with bidi-portion;
951 nCurrLevel
= static_cast<SwBidiPortion
const *>(pMulti
)->GetLevel();
954 // no nested bidi portion required
955 nCurrLevel
= GetTextFrame()->IsRightToLeft() ? 1 : 0;
957 // check if there is a field at rPos:
958 sal_uInt8 nNextLevel
= nCurrLevel
;
959 bool bFieldBidi
= false;
961 if (rPos
< TextFrameIndex(GetText().getLength()) && CH_TXTATR_BREAKWORD
== GetChar(rPos
))
966 nNextLevel
= rSI
.DirType( rPos
);
968 if (TextFrameIndex(GetText().getLength()) != rPos
&& nNextLevel
> nCurrLevel
)
970 rPos
= bFieldBidi
? rPos
+ TextFrameIndex(1) : rSI
.NextDirChg(rPos
, &nCurrLevel
);
971 if (TextFrameIndex(COMPLETE_STRING
) == rPos
)
974 aRet
.pItem
= nullptr;
975 aRet
.pAttr
= nullptr;
976 aRet
.nStartOfAttr
= TextFrameIndex(-1);
977 aRet
.nId
= SwMultiCreatorId::Bidi
;
978 aRet
.nLevel
= nCurrLevel
+ 1;
982 // a bidi portion can only contain other bidi portions
986 // need the node that contains input rPos
987 std::pair
<SwTextNode
const*, sal_Int32
> startPos(m_pFrame
->MapViewToModel(rPos
));
988 const SvxCharRotateItem
* pActiveRotateItem(nullptr);
989 const SvxCharRotateItem
* pNodeRotateItem(nullptr);
990 const SvxTwoLinesItem
* pActiveTwoLinesItem(nullptr);
991 const SvxTwoLinesItem
* pNodeTwoLinesItem(nullptr);
992 SwTextAttr
const* pActiveTwoLinesHint(nullptr);
993 SwTextAttr
const* pActiveRotateHint(nullptr);
994 const SwTextAttr
*pRuby
= nullptr;
995 sw::MergedAttrIterMulti
iterAtStartOfNode(*m_pFrame
);
999 for (sw::MergedAttrIterMulti iter
= *m_pFrame
; ; )
1001 SwTextNode
const* pNode(nullptr);
1002 SwTextAttr
const*const pAttr
= iter
.NextAttr(pNode
);
1009 assert(pNode
->GetIndex() <= startPos
.first
->GetIndex()); // should break earlier
1010 if (startPos
.first
->GetIndex() <= pNode
->GetIndex())
1012 if (startPos
.first
->GetIndex() != pNode
->GetIndex()
1013 || startPos
.second
< pAttr
->GetStart())
1017 if (startPos
.second
< pAttr
->GetAnyEnd())
1019 // sw_redlinehide: ruby *always* splits
1020 if (RES_TXTATR_CJK_RUBY
== pAttr
->Which())
1024 const SvxCharRotateItem
* pRoTmp
= nullptr;
1025 if (lcl_HasRotation( *pAttr
, pRoTmp
, bRot
))
1027 pActiveRotateHint
= bRot
? pAttr
: nullptr;
1028 pActiveRotateItem
= pRoTmp
;
1030 const SvxTwoLinesItem
* p2Tmp
= nullptr;
1031 if (lcl_Has2Lines( *pAttr
, p2Tmp
, bTwo
))
1033 pActiveTwoLinesHint
= bTwo
? pAttr
: nullptr;
1034 pActiveTwoLinesItem
= p2Tmp
;
1040 // !pAttr && pNode means the node changed
1041 if (startPos
.first
->GetIndex() < pNode
->GetIndex())
1043 break; // only one node initially
1045 if (startPos
.first
->GetIndex() == pNode
->GetIndex())
1047 iterAtStartOfNode
.Assign(iter
);
1048 if (SfxItemState::SET
== pNode
->GetSwAttrSet().GetItemState(
1049 RES_CHRATR_ROTATE
, true, &pNodeRotateItem
) &&
1050 pNodeRotateItem
->GetValue())
1052 pActiveRotateItem
= pNodeRotateItem
;
1056 pNodeRotateItem
= nullptr;
1058 if (SfxItemState::SET
== startPos
.first
->GetSwAttrSet().GetItemState(
1059 RES_CHRATR_TWO_LINES
, true, &pNodeTwoLinesItem
) &&
1060 pNodeTwoLinesItem
->GetValue())
1062 pActiveTwoLinesItem
= pNodeTwoLinesItem
;
1066 pNodeTwoLinesItem
= nullptr;
1070 if (!pRuby
&& !pActiveTwoLinesItem
&& !pActiveRotateItem
)
1074 { // The winner is ... a ruby attribute and so
1075 // the end of the multiportion is the end of the ruby attribute.
1076 rPos
= m_pFrame
->MapModelToView(startPos
.first
, *pRuby
->End());
1077 SwMultiCreator aRet
;
1078 aRet
.pItem
= nullptr;
1080 aRet
.nStartOfAttr
= m_pFrame
->MapModelToView(startPos
.first
, aRet
.pAttr
->GetStart());
1081 aRet
.nId
= SwMultiCreatorId::Ruby
;
1082 aRet
.nLevel
= GetTextFrame()->IsRightToLeft() ? 1 : 0;
1085 if (pActiveTwoLinesHint
||
1086 (pNodeTwoLinesItem
&& SfxPoolItem::areSame(pNodeTwoLinesItem
, pActiveTwoLinesItem
) &&
1087 rPos
< TextFrameIndex(GetText().getLength())))
1088 { // The winner is a 2-line-attribute,
1089 // the end of the multiportion depends on the following attributes...
1090 SwMultiCreator aRet
;
1092 // We note the endpositions of the 2-line attributes in aEnd as stack
1093 std::deque
<TextFrameIndex
> aEnd
;
1095 // The bOn flag signs the state of the last 2-line attribute in the
1096 // aEnd-stack, it is compatible with the winner-attribute or
1097 // it interrupts the other attribute.
1100 if (pActiveTwoLinesHint
)
1102 aRet
.pItem
= nullptr;
1103 aRet
.pAttr
= pActiveTwoLinesHint
;
1104 aRet
.nStartOfAttr
= m_pFrame
->MapModelToView(startPos
.first
, aRet
.pAttr
->GetStart());
1105 if (pNodeTwoLinesItem
)
1107 aEnd
.push_front(m_pFrame
->MapModelToView(startPos
.first
, startPos
.first
->Len()));
1108 bOn
= pNodeTwoLinesItem
->GetEndBracket() ==
1109 pActiveTwoLinesItem
->GetEndBracket() &&
1110 pNodeTwoLinesItem
->GetStartBracket() ==
1111 pActiveTwoLinesItem
->GetStartBracket();
1115 aEnd
.push_front(m_pFrame
->MapModelToView(startPos
.first
, *aRet
.pAttr
->End()));
1120 aRet
.pItem
= pNodeTwoLinesItem
;
1121 aRet
.pAttr
= nullptr;
1122 aRet
.nStartOfAttr
= TextFrameIndex(-1);
1123 aEnd
.push_front(m_pFrame
->MapModelToView(startPos
.first
, startPos
.first
->Len()));
1125 aRet
.nId
= SwMultiCreatorId::Double
;
1126 aRet
.nLevel
= GetTextFrame()->IsRightToLeft() ? 1 : 0;
1128 // pActiveTwoLinesHint is the last 2-line-attribute, which contains
1129 // the actual position.
1131 // At this moment we know that at position rPos the "winner"-attribute
1132 // causes a 2-line-portion. The end of the attribute is the end of the
1133 // portion, if there's no interrupting attribute.
1134 // There are two kinds of interrupters:
1135 // - ruby attributes stops the 2-line-attribute, the end of the
1136 // multiline is the start of the ruby attribute
1137 // - 2-line-attributes with value "Off" or with different brackets,
1138 // these attributes may interrupt the winner, but they could be
1139 // neutralized by another 2-line-attribute starting at the same
1140 // position with the same brackets as the winner-attribute.
1142 // In the following loop rPos is the critical position and it will be
1143 // evaluated, if at rPos starts an interrupting or a maintaining
1144 // continuity attribute.
1146 // iterAtStartOfNode is positioned to the first hint of the node
1147 // (if any); the node item itself has already been handled above
1148 for (sw::MergedAttrIterMulti iter
= iterAtStartOfNode
; ; )
1150 SwTextNode
const* pNode(nullptr);
1151 SwTextAttr
const*const pTmp
= iter
.NextAttr(pNode
);
1156 assert(startPos
.first
->GetIndex() <= pNode
->GetIndex());
1157 TextFrameIndex nTmpStart
;
1158 TextFrameIndex nTmpEnd
;
1161 nTmpEnd
= m_pFrame
->MapModelToView(pNode
, pTmp
->GetAnyEnd());
1162 if (nTmpEnd
<= rPos
)
1164 nTmpStart
= m_pFrame
->MapModelToView(pNode
, pTmp
->GetStart());
1168 pNodeTwoLinesItem
= pNode
->GetSwAttrSet().GetItemIfSet(
1169 RES_CHRATR_TWO_LINES
);
1170 nTmpStart
= m_pFrame
->MapModelToView(pNode
, 0);
1171 nTmpEnd
= m_pFrame
->MapModelToView(pNode
, pNode
->Len());
1172 assert(rPos
<= nTmpEnd
); // next node must not have smaller index
1175 if (rPos
< nTmpStart
)
1177 // If bOn is false and the next attribute starts later than rPos
1178 // the winner attribute is interrupted at rPos.
1179 // If the start of the next attribute is behind the end of
1180 // the last attribute on the aEnd-stack, this is the endposition
1181 // on the stack is the end of the 2-line portion.
1182 if (!bOn
|| aEnd
.back() < nTmpStart
)
1184 // At this moment, bOn is true and the next attribute starts
1185 // behind rPos, so we could move rPos to the next startpoint
1187 // We clean up the aEnd-stack, endpositions equal to rPos are
1189 while( !aEnd
.empty() && aEnd
.back() <= rPos
)
1194 // If the endstack is empty, we simulate an attribute with
1195 // state true and endposition rPos
1198 aEnd
.push_front( rPos
);
1202 // A ruby attribute stops the 2-line immediately
1203 if (pTmp
&& RES_TXTATR_CJK_RUBY
== pTmp
->Which())
1205 if (pTmp
? lcl_Has2Lines(*pTmp
, pActiveTwoLinesItem
, bTwo
)
1206 : lcl_Check2Lines(pNodeTwoLinesItem
, pActiveTwoLinesItem
, bTwo
))
1207 { // We have an interesting attribute...
1209 { // .. with the same state, so the last attribute could
1211 if (aEnd
.back() < nTmpEnd
)
1212 aEnd
.back() = nTmpEnd
;
1215 { // .. with a different state.
1217 // If this is smaller than the last on the stack, we put
1218 // it on the stack. If it has the same endposition, the last
1219 // could be removed.
1220 if (nTmpEnd
< aEnd
.back())
1221 aEnd
.push_back( nTmpEnd
);
1222 else if( aEnd
.size() > 1 )
1225 aEnd
.back() = nTmpEnd
;
1229 if( bOn
&& !aEnd
.empty() )
1233 if (pActiveRotateHint
||
1234 (pNodeRotateItem
&& SfxPoolItem::areSame(pNodeRotateItem
, pActiveRotateItem
) &&
1235 rPos
< TextFrameIndex(GetText().getLength())))
1236 { // The winner is a rotate-attribute,
1237 // the end of the multiportion depends on the following attributes...
1238 SwMultiCreator aRet
;
1239 aRet
.nId
= SwMultiCreatorId::Rotate
;
1241 // We note the endpositions of the 2-line attributes in aEnd as stack
1242 std::deque
<TextFrameIndex
> aEnd
;
1244 // The bOn flag signs the state of the last 2-line attribute in the
1245 // aEnd-stack, which could interrupts the winning rotation attribute.
1246 bool bOn
= pNodeTwoLinesItem
!= nullptr;
1247 aEnd
.push_front(TextFrameIndex(GetText().getLength()));
1249 // first, search for the start position of the next TWOLINE portion
1250 // because the ROTATE portion must end there at the latest
1251 TextFrameIndex n2Start
= rPos
;
1252 for (sw::MergedAttrIterMulti iter
= iterAtStartOfNode
; ; )
1254 SwTextNode
const* pNode(nullptr);
1255 SwTextAttr
const*const pTmp
= iter
.NextAttr(pNode
);
1260 assert(startPos
.first
->GetIndex() <= pNode
->GetIndex());
1261 TextFrameIndex nTmpStart
;
1262 TextFrameIndex nTmpEnd
;
1265 nTmpEnd
= m_pFrame
->MapModelToView(pNode
, pTmp
->GetAnyEnd());
1266 if (nTmpEnd
<= n2Start
)
1268 nTmpStart
= m_pFrame
->MapModelToView(pNode
, pTmp
->GetStart());
1272 pNodeTwoLinesItem
= pNode
->GetSwAttrSet().GetItemIfSet(
1273 RES_CHRATR_TWO_LINES
);
1274 nTmpStart
= m_pFrame
->MapModelToView(pNode
, 0);
1275 nTmpEnd
= m_pFrame
->MapModelToView(pNode
, pNode
->Len());
1276 assert(n2Start
<= nTmpEnd
); // next node must not have smaller index
1279 if (n2Start
< nTmpStart
)
1281 if (bOn
|| aEnd
.back() < nTmpStart
)
1283 n2Start
= nTmpStart
;
1284 while( !aEnd
.empty() && aEnd
.back() <= n2Start
)
1291 aEnd
.push_front( n2Start
);
1295 // A ruby attribute stops immediately
1296 if (pTmp
&& RES_TXTATR_CJK_RUBY
== pTmp
->Which())
1301 const SvxTwoLinesItem
* p2Lines
= nullptr;
1302 if (pTmp
? lcl_Has2Lines(*pTmp
, p2Lines
, bTwo
)
1303 : lcl_Check2Lines(pNodeTwoLinesItem
, p2Lines
, bTwo
))
1307 if (aEnd
.back() < nTmpEnd
)
1308 aEnd
.back() = nTmpEnd
;
1313 if (nTmpEnd
< aEnd
.back())
1314 aEnd
.push_back( nTmpEnd
);
1315 else if( aEnd
.size() > 1 )
1318 aEnd
.back() = nTmpEnd
;
1322 if( !bOn
&& !aEnd
.empty() )
1323 n2Start
= aEnd
.back();
1327 // now, search for the end of the ROTATE portion, similar to above
1329 if (pActiveRotateHint
)
1331 aRet
.pItem
= nullptr;
1332 aRet
.pAttr
= pActiveRotateHint
;
1333 aRet
.nStartOfAttr
= m_pFrame
->MapModelToView(startPos
.first
, aRet
.pAttr
->GetStart());
1334 if (pNodeRotateItem
)
1336 aEnd
.push_front(m_pFrame
->MapModelToView(startPos
.first
, startPos
.first
->Len()));
1337 bOn
= pNodeRotateItem
->GetValue() ==
1338 pActiveRotateItem
->GetValue();
1342 aEnd
.push_front(m_pFrame
->MapModelToView(startPos
.first
, *aRet
.pAttr
->End()));
1347 aRet
.pItem
= pNodeRotateItem
;
1348 aRet
.pAttr
= nullptr;
1349 aRet
.nStartOfAttr
= TextFrameIndex(-1);
1350 aEnd
.push_front(m_pFrame
->MapModelToView(startPos
.first
, startPos
.first
->Len()));
1352 for (sw::MergedAttrIterMulti iter
= iterAtStartOfNode
; ; )
1354 SwTextNode
const* pNode(nullptr);
1355 SwTextAttr
const*const pTmp
= iter
.NextAttr(pNode
);
1360 assert(startPos
.first
->GetIndex() <= pNode
->GetIndex());
1361 TextFrameIndex nTmpStart
;
1362 TextFrameIndex nTmpEnd
;
1365 nTmpEnd
= m_pFrame
->MapModelToView(pNode
, pTmp
->GetAnyEnd());
1366 if (nTmpEnd
<= rPos
)
1368 nTmpStart
= m_pFrame
->MapModelToView(pNode
, pTmp
->GetStart());
1372 pNodeRotateItem
= pNode
->GetSwAttrSet().GetItemIfSet(
1374 nTmpStart
= m_pFrame
->MapModelToView(pNode
, 0);
1375 nTmpEnd
= m_pFrame
->MapModelToView(pNode
, pNode
->Len());
1376 assert(rPos
<= nTmpEnd
); // next node must not have smaller index
1379 if (rPos
< nTmpStart
)
1381 if (!bOn
|| aEnd
.back() < nTmpStart
)
1384 while( !aEnd
.empty() && aEnd
.back() <= rPos
)
1391 aEnd
.push_front( rPos
);
1395 if (pTmp
&& RES_TXTATR_CJK_RUBY
== pTmp
->Which())
1400 // TODO why does this use bTwo, not bRot ???
1401 if (pTmp
? lcl_HasRotation(*pTmp
, pActiveRotateItem
, bTwo
)
1402 : lcl_CheckRotation(pNodeRotateItem
, pActiveRotateItem
, bTwo
))
1406 if (aEnd
.back() < nTmpEnd
)
1407 aEnd
.back() = nTmpEnd
;
1412 if (nTmpEnd
< aEnd
.back())
1413 aEnd
.push_back( nTmpEnd
);
1414 else if( aEnd
.size() > 1 )
1417 aEnd
.back() = nTmpEnd
;
1421 if( bOn
&& !aEnd
.empty() )
1423 if( rPos
> n2Start
)
1432 // A little helper class to manage the spaceadd-arrays of the text adjustment
1433 // during a PaintMultiPortion.
1434 // The constructor prepares the array for the first line of multiportion,
1435 // the SecondLine-function restores the values for the first line and prepares
1437 // The destructor restores the values of the last manipulation.
1438 class SwSpaceManipulator
1440 SwTextPaintInfo
& m_rInfo
;
1441 SwMultiPortion
& m_rMulti
;
1442 std::vector
<tools::Long
>* m_pOldSpaceAdd
;
1443 sal_uInt16 m_nOldSpaceIndex
;
1444 tools::Long m_nSpaceAdd
;
1446 sal_uInt8 m_nOldDir
;
1449 SwSpaceManipulator( SwTextPaintInfo
& rInf
, SwMultiPortion
& rMult
);
1450 ~SwSpaceManipulator();
1452 tools::Long
GetSpaceAdd() const { return m_nSpaceAdd
; }
1457 SwSpaceManipulator::SwSpaceManipulator(SwTextPaintInfo
& rInf
, SwMultiPortion
& rMult
)
1462 m_pOldSpaceAdd
= m_rInfo
.GetpSpaceAdd();
1463 m_nOldSpaceIndex
= m_rInfo
.GetSpaceIdx();
1464 m_nOldDir
= m_rInfo
.GetDirection();
1465 m_rInfo
.SetDirection(m_rMulti
.GetDirection());
1466 m_bSpaceChg
= false;
1468 if (m_rMulti
.IsDouble())
1470 m_nSpaceAdd
= (m_pOldSpaceAdd
&& !m_rMulti
.HasTabulator()) ? m_rInfo
.GetSpaceAdd() : 0;
1471 if (m_rMulti
.GetRoot().IsSpaceAdd())
1473 m_rInfo
.SetpSpaceAdd(m_rMulti
.GetRoot().GetpLLSpaceAdd());
1474 m_rInfo
.ResetSpaceIdx();
1475 m_bSpaceChg
= m_rMulti
.ChgSpaceAdd(&m_rMulti
.GetRoot(), m_nSpaceAdd
);
1477 else if (m_rMulti
.HasTabulator())
1478 m_rInfo
.SetpSpaceAdd(nullptr);
1480 else if (!m_rMulti
.IsBidi())
1482 m_rInfo
.SetpSpaceAdd(m_rMulti
.GetRoot().GetpLLSpaceAdd());
1483 m_rInfo
.ResetSpaceIdx();
1487 void SwSpaceManipulator::SecondLine()
1491 m_rInfo
.RemoveFirstSpaceAdd();
1492 m_bSpaceChg
= false;
1494 SwLineLayout
* pLay
= m_rMulti
.GetRoot().GetNext();
1495 if( pLay
->IsSpaceAdd() )
1497 m_rInfo
.SetpSpaceAdd(pLay
->GetpLLSpaceAdd());
1498 m_rInfo
.ResetSpaceIdx();
1499 m_bSpaceChg
= m_rMulti
.ChgSpaceAdd(pLay
, m_nSpaceAdd
);
1503 m_rInfo
.SetpSpaceAdd((!m_rMulti
.IsDouble() || m_rMulti
.HasTabulator()) ? nullptr
1505 m_rInfo
.SetSpaceIdx(m_nOldSpaceIndex
);
1509 SwSpaceManipulator::~SwSpaceManipulator()
1513 m_rInfo
.RemoveFirstSpaceAdd();
1514 m_bSpaceChg
= false;
1516 m_rInfo
.SetpSpaceAdd(m_pOldSpaceAdd
);
1517 m_rInfo
.SetSpaceIdx(m_nOldSpaceIndex
);
1518 m_rInfo
.SetDirection(m_nOldDir
);
1521 // Manages the paint for a SwMultiPortion.
1522 // External, for the calling function, it seems to be a normal Paint-function,
1523 // internal it is like a SwTextFrame::PaintSwFrame with multiple DrawTextLines
1524 void SwTextPainter::PaintMultiPortion( const SwRect
&rPaint
,
1525 SwMultiPortion
& rMulti
, const SwMultiPortion
* pEnvPor
)
1527 SwTextGridItem
const*const pGrid(GetGridItem(m_pFrame
->FindPageFrame()));
1528 const bool bHasGrid
= pGrid
&& GetInfo().SnapToGrid();
1529 sal_uInt16 nRubyHeight
= 0;
1530 bool bRubyTop
= true;
1532 if ( bHasGrid
&& pGrid
->IsSquaredMode() )
1534 nRubyHeight
= pGrid
->GetRubyHeight();
1535 bRubyTop
= ! pGrid
->GetRubyTextBelow();
1538 // do not allow grid mode for first line in ruby portion
1539 const bool bRubyInGrid
= bHasGrid
&& rMulti
.IsRuby();
1541 const sal_uInt16 nOldHeight
= rMulti
.Height();
1542 const bool bOldGridModeAllowed
= GetInfo().SnapToGrid();
1546 GetInfo().SetSnapToGrid( ! bRubyTop
);
1547 if (pGrid
->IsSquaredMode())
1548 rMulti
.Height( m_pCurr
->Height() );
1551 SwLayoutModeModifier
aLayoutModeModifier( *GetInfo().GetOut() );
1552 bool bEnvDir
= false;
1553 bool bThisDir
= false;
1554 bool bFrameDir
= false;
1555 if ( rMulti
.IsBidi() )
1557 // these values are needed for the calculation of the x coordinate
1558 // and the layout mode
1559 OSL_ENSURE( ! pEnvPor
|| pEnvPor
->IsBidi(),
1560 "Oh no, I expected a BidiPortion" );
1561 bFrameDir
= GetInfo().GetTextFrame()->IsRightToLeft();
1562 bEnvDir
= pEnvPor
? ((static_cast<const SwBidiPortion
*>(pEnvPor
)->GetLevel() % 2) != 0) : bFrameDir
;
1563 bThisDir
= (static_cast<SwBidiPortion
&>(rMulti
).GetLevel() % 2) != 0;
1566 #if OSL_DEBUG_LEVEL > 1
1567 // only paint first level bidi portions
1568 if( rMulti
.Width() > 1 && ! pEnvPor
)
1569 GetInfo().DrawViewOpt( rMulti
, PortionType::Field
);
1572 if ( bRubyInGrid
&& pGrid
->IsSquaredMode() )
1573 rMulti
.Height( nOldHeight
);
1575 // do we have to repaint a post it portion?
1576 if( GetInfo().OnWin() && rMulti
.GetNextPortion() &&
1577 ! rMulti
.GetNextPortion()->Width() )
1578 rMulti
.GetNextPortion()->PrePaint( GetInfo(), &rMulti
);
1580 // old values must be saved and restored at the end
1581 TextFrameIndex
const nOldLen
= GetInfo().GetLen();
1582 const SwTwips nOldX
= GetInfo().X();
1583 const SwTwips nOldY
= GetInfo().Y();
1584 TextFrameIndex
const nOldIdx
= GetInfo().GetIdx();
1586 SwSpaceManipulator
aManip( GetInfo(), rMulti
);
1588 std::optional
<SwFontSave
> oFontSave
;
1589 std::unique_ptr
<SwFont
> pTmpFnt
;
1591 if( rMulti
.IsDouble() )
1593 pTmpFnt
.reset(new SwFont( *GetInfo().GetFont() ));
1594 if( rMulti
.IsDouble() )
1597 pTmpFnt
->SetProportion( GetPropFont() );
1599 oFontSave
.emplace( GetInfo(), pTmpFnt
.get(), this );
1606 if( rMulti
.HasBrackets() )
1609 Por_Info
const por(rMulti
, *this, 1);
1610 SwTaggedPDFHelper
const tag(nullptr, nullptr, &por
, *GetInfo().GetOut());
1612 TextFrameIndex
const nTmpOldIdx
= GetInfo().GetIdx();
1613 GetInfo().SetIdx(static_cast<SwDoubleLinePortion
&>(rMulti
).GetBrackets()->nStart
);
1614 SeekAndChg( GetInfo() );
1615 static_cast<SwDoubleLinePortion
&>(rMulti
).PaintBracket( GetInfo(), 0, true );
1616 GetInfo().SetIdx( nTmpOldIdx
);
1619 const SwTwips nTmpX
= GetInfo().X();
1621 SwLineLayout
* pLay
= &rMulti
.GetRoot();// the first line of the multiportion
1622 SwLinePortion
* pPor
= pLay
->GetFirstPortion();//first portion of these line
1625 // GetInfo().Y() is the baseline from the surrounding line. We must switch
1626 // this temporary to the baseline of the inner lines of the multiportion.
1627 if( rMulti
.HasRotation() )
1629 if( rMulti
.IsRevers() )
1631 GetInfo().Y( nOldY
- rMulti
.GetAscent() );
1632 nOfst
= nTmpX
+ rMulti
.Width();
1636 GetInfo().Y( nOldY
- rMulti
.GetAscent() + rMulti
.Height() );
1640 else if ( rMulti
.IsBidi() )
1642 // does the current bidi portion has the same direction
1643 // as its environment?
1644 if ( bEnvDir
!= bThisDir
)
1646 // different directions, we have to adjust the x coordinate
1647 SwTwips nMultiWidth
= rMulti
.Width() +
1648 rMulti
.CalcSpacing( GetInfo().GetSpaceAdd(), GetInfo() );
1650 if ( bFrameDir
== bThisDir
)
1651 GetInfo().X( GetInfo().X() - nMultiWidth
);
1653 GetInfo().X( GetInfo().X() + nMultiWidth
);
1656 nOfst
= nOldY
- rMulti
.GetAscent();
1659 aLayoutModeModifier
.Modify( bThisDir
);
1662 nOfst
= nOldY
- rMulti
.GetAscent();
1664 bool bRest
= pLay
->IsRest();
1667 OSL_ENSURE( nullptr == GetInfo().GetUnderFnt() || rMulti
.IsBidi(),
1668 " Only BiDi portions are allowed to use the common underlining font" );
1670 ::std::optional
<SwTaggedPDFHelper
> oTag
;
1671 if (rMulti
.IsDouble())
1673 Por_Info
const por(rMulti
, *this, 2);
1674 oTag
.emplace(nullptr, nullptr, &por
, *GetInfo().GetOut());
1676 else if (rMulti
.IsRuby())
1678 Por_Info
const por(rMulti
, *this, bRubyTop
? 1 : 2);
1679 oTag
.emplace(nullptr, nullptr, &por
, *GetInfo().GetOut());
1680 GetInfo().SetRuby( rMulti
.OnTop() );
1685 if ( bHasGrid
&& pGrid
->IsSquaredMode() )
1687 if( rMulti
.HasRotation() )
1689 const sal_uInt16 nAdjustment
= ( pLay
->Height() - pPor
->Height() ) / 2 +
1691 if( rMulti
.IsRevers() )
1692 GetInfo().X( nOfst
- nAdjustment
);
1694 GetInfo().X( nOfst
+ nAdjustment
);
1698 // special treatment for ruby portions in grid mode
1699 SwTwips nAdjustment
= 0;
1700 if ( rMulti
.IsRuby() )
1702 if ( bRubyTop
!= ( pLay
== &rMulti
.GetRoot() ) )
1704 nAdjustment
= ( m_pCurr
->Height() - nRubyHeight
- pPor
->Height() ) / 2;
1705 else if ( bRubyTop
)
1706 // adjust upper ruby text
1707 nAdjustment
= nRubyHeight
- pPor
->Height();
1708 // else adjust lower ruby text
1711 GetInfo().Y( nOfst
+ nAdjustment
+ pPor
->GetAscent() );
1714 else if( rMulti
.HasRotation() )
1716 if( rMulti
.IsRevers() )
1717 GetInfo().X( nOfst
- AdjustBaseLine( *pLay
, pPor
, 0, 0, true ) );
1719 GetInfo().X( nOfst
+ AdjustBaseLine( *pLay
, pPor
) );
1721 else if ( rMulti
.IsRuby() && rMulti
.OnRight() && GetInfo().IsRuby() )
1723 SwTwips nLineDiff
= std::max(( rMulti
.GetRoot().Height() - pPor
->Width() ) / 2, static_cast<SwTwips
>(0) );
1724 GetInfo().Y( nOfst
+ nLineDiff
);
1725 // Draw the ruby text on top of the preserved space.
1726 GetInfo().X( GetInfo().X() - pPor
->Height() );
1728 else if (!rMulti
.IsBidi())
1730 GetInfo().Y(nOfst
+ AdjustBaseLine(*pLay
, pPor
));
1733 bool bSeeked
= true;
1734 GetInfo().SetLen( pPor
->GetLen() );
1736 if( bRest
&& pPor
->InFieldGrp() && !pPor
->GetLen() )
1738 if( static_cast<SwFieldPortion
*>(pPor
)->HasFont() )
1741 SeekAndChgBefore( GetInfo() );
1743 else if( pPor
->InTextGrp() || pPor
->InFieldGrp() || pPor
->InTabGrp() )
1744 SeekAndChg( GetInfo() );
1745 else if ( !bFirst
&& pPor
->IsBreakPortion() && GetInfo().GetOpt().IsParagraph() )
1748 SeekAndChg( GetInfo() );
1750 SeekAndChgBefore( GetInfo() );
1755 SwLinePortion
*pNext
= pPor
->GetNextPortion();
1756 if(GetInfo().OnWin() && pNext
&& !pNext
->Width() )
1759 SeekAndChg( GetInfo() );
1760 pNext
->PrePaint( GetInfo(), pPor
);
1763 CheckSpecialUnderline( pPor
);
1764 SwUnderlineFont
* pUnderLineFnt
= GetInfo().GetUnderFnt();
1765 if ( pUnderLineFnt
)
1767 if ( rMulti
.IsDouble() )
1768 pUnderLineFnt
->GetFont().SetProportion( 50 );
1769 pUnderLineFnt
->SetPos( GetInfo().GetPos() );
1772 if ( rMulti
.IsBidi() )
1774 // we do not allow any rotation inside a bidi portion
1775 SwFont
* pTmpFont
= GetInfo().GetFont();
1776 pTmpFont
->SetVertical( 0_deg10
, GetInfo().GetTextFrame()->IsVertical() );
1779 if( pPor
->IsMultiPortion() && static_cast<SwMultiPortion
*>(pPor
)->IsBidi() )
1781 // but we do allow nested bidi portions
1782 OSL_ENSURE( rMulti
.IsBidi(), "Only nesting of bidi portions is allowed" );
1783 PaintMultiPortion( rPaint
, static_cast<SwMultiPortion
&>(*pPor
), &rMulti
);
1787 Por_Info
const por(*pPor
, *this, 0);
1788 SwTaggedPDFHelper
const tag(nullptr, nullptr, &por
, *GetInfo().GetOut());
1790 pPor
->Paint( GetInfo() );
1793 if (GetFnt()->IsURL() && pPor
->InTextGrp())
1794 GetInfo().NotifyURL(*pPor
);
1796 bFirst
&= !pPor
->GetLen();
1797 if( pNext
|| !pPor
->IsMarginPortion() )
1798 pPor
->Move( GetInfo() );
1802 // If there's no portion left, we go to the next line
1803 if( !pPor
&& pLay
->GetNext() )
1805 pLay
= pLay
->GetNext();
1806 pPor
= pLay
->GetFirstPortion();
1807 bRest
= pLay
->IsRest();
1808 aManip
.SecondLine();
1810 // delete underline font
1811 delete GetInfo().GetUnderFnt();
1812 GetInfo().SetUnderFnt( nullptr );
1814 if( rMulti
.HasRotation() )
1816 if( rMulti
.IsRevers() )
1818 nOfst
+= pLay
->Height();
1819 GetInfo().Y( nOldY
- rMulti
.GetAscent() );
1823 nOfst
-= pLay
->Height();
1824 GetInfo().Y( nOldY
- rMulti
.GetAscent() + rMulti
.Height() );
1827 else if ( bHasGrid
&& rMulti
.IsRuby() )
1829 GetInfo().SetSnapToGrid( bRubyTop
);
1830 GetInfo().X( nTmpX
);
1831 if (pGrid
->IsSquaredMode() )
1834 nOfst
+= nRubyHeight
;
1836 nOfst
+= m_pCurr
->Height() - nRubyHeight
;
1840 nOfst
+= rMulti
.GetRoot().Height();
1843 else if ( rMulti
.IsRuby() && rMulti
.OnRight() )
1845 GetInfo().SetDirection( DIR_TOP2BOTTOM
);
1846 GetInfo().SetRuby( true );
1849 GetInfo().X( nTmpX
);
1850 // We switch to the baseline of the next inner line
1851 nOfst
+= rMulti
.GetRoot().Height();
1853 if (rMulti
.IsRuby())
1856 Por_Info
const por(rMulti
, *this, bRubyTop
? 2 : 1);
1857 oTag
.emplace(nullptr, nullptr, &por
, *GetInfo().GetOut());
1862 if (rMulti
.IsDouble())
1868 GetInfo().SetSnapToGrid( bOldGridModeAllowed
);
1870 // delete underline font
1871 if ( ! rMulti
.IsBidi() )
1873 delete GetInfo().GetUnderFnt();
1874 GetInfo().SetUnderFnt( nullptr );
1877 GetInfo().SetIdx( nOldIdx
);
1878 GetInfo().Y( nOldY
);
1880 if( rMulti
.HasBrackets() )
1883 Por_Info
const por(rMulti
, *this, 1);
1884 SwTaggedPDFHelper
const tag(nullptr, nullptr, &por
, *GetInfo().GetOut());
1886 TextFrameIndex
const nTmpOldIdx
= GetInfo().GetIdx();
1887 GetInfo().SetIdx(static_cast<SwDoubleLinePortion
&>(rMulti
).GetBrackets()->nStart
);
1888 SeekAndChg( GetInfo() );
1889 GetInfo().X( nOldX
);
1890 static_cast<SwDoubleLinePortion
&>(rMulti
).PaintBracket( GetInfo(),
1891 aManip
.GetSpaceAdd(), false );
1892 GetInfo().SetIdx( nTmpOldIdx
);
1894 // Restore the saved values
1895 GetInfo().X( nOldX
);
1896 GetInfo().SetLen( nOldLen
);
1902 static bool lcl_ExtractFieldFollow( SwLineLayout
* pLine
, SwLinePortion
* &rpField
)
1904 SwLinePortion
* pLast
= pLine
;
1905 rpField
= pLine
->GetNextPortion();
1906 while( rpField
&& !rpField
->InFieldGrp() )
1909 rpField
= rpField
->GetNextPortion();
1911 bool bRet
= rpField
!= nullptr;
1914 if( static_cast<SwFieldPortion
*>(rpField
)->IsFollow() )
1916 rpField
->Truncate();
1917 pLast
->SetNextPortion( nullptr );
1926 // Determines if any part of the bidi portion fits on the current line
1929 enum class BidiTruncationType
1936 BidiTruncationType
lcl_BidiPortionNeedsTruncation(SwMultiPortion
& rMulti
,
1937 SwTextFormatInfo
& rExternalInf
,
1938 SwTextFormatInfo
& rLocalInf
,
1939 TextFrameIndex
const nStartIdx
)
1941 if (!rLocalInf
.IsUnderflow())
1943 // Some amount of text fits in the bidi portion without triggering underflow,
1944 // so the portion should not be truncated.
1945 return BidiTruncationType::None
;
1948 auto nCurrLen
= rMulti
.GetLen();
1950 css::i18n::LineBreakHyphenationOptions aHyphOptions
;
1951 css::i18n::LineBreakUserOptions aUserOptions
;
1952 css::lang::Locale aLocale
;
1953 auto aResult
= g_pBreakIt
->GetBreakIter()->getLineBreak(
1954 rExternalInf
.GetText(), sal_Int32(nStartIdx
+ nCurrLen
), aLocale
,
1955 sal_Int32(rExternalInf
.GetLineStart()), aHyphOptions
, aUserOptions
);
1957 if (aResult
.breakIndex
< sal_Int32(nStartIdx
))
1959 // The bidi portion doesn't fit on the line, and the first break opportunity
1960 // is before the bidi portion. Underflow to the preceding text.
1961 return BidiTruncationType::Underflow
;
1964 if (aResult
.breakIndex
> sal_Int32(nStartIdx
)
1965 && aResult
.breakIndex
<= sal_Int32(nStartIdx
+ nCurrLen
))
1967 // The bidi portion fits on this line, but ended with underflow.
1968 return BidiTruncationType::None
;
1971 // The bidi portion doesn't fit on the line, but a break position exists between the bidi
1972 // portion and the preceding text. Truncating is sufficient.
1973 return BidiTruncationType::Truncate
;
1977 // If a multi portion completely has to go to the
1978 // next line, this function is called to truncate
1979 // the rest of the remaining multi portion
1980 static void lcl_TruncateMultiPortion(SwMultiPortion
& rMulti
, SwTextFormatInfo
& rInf
,
1981 TextFrameIndex
const nStartIdx
,
1982 BidiTruncationType nBidiTruncType
= BidiTruncationType::None
)
1984 rMulti
.GetRoot().Truncate();
1985 rMulti
.GetRoot().SetLen(TextFrameIndex(0));
1986 rMulti
.GetRoot().Width(0);
1987 // rMulti.CalcSize( *this, aInf );
1988 if ( rMulti
.GetRoot().GetNext() )
1990 rMulti
.GetRoot().GetNext()->Truncate();
1991 rMulti
.GetRoot().GetNext()->SetLen(TextFrameIndex(0));
1992 rMulti
.GetRoot().GetNext()->Width( 0 );
1995 rMulti
.SetLen(TextFrameIndex(0));
1996 rInf
.SetIdx( nStartIdx
);
1998 if (rMulti
.IsBidi())
2000 // The truncated portion is a bidi portion. Bidi portions contain ordinary text, and may
2001 // potentially underflow in the case that none of the text fits on the current line.
2002 if (nBidiTruncType
== BidiTruncationType::Underflow
)
2004 // The start of the bidi portion is not a valid break. Instead, a break should be
2005 // inserted into a previous text portion on this line.
2006 rInf
.SetUnderflow(&rMulti
);
2011 // Manages the formatting of a SwMultiPortion. External, for the calling
2012 // function, it seems to be a normal Format-function, internal it is like a
2013 // SwTextFrame::Format_ with multiple BuildPortions
2014 bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo
&rInf
,
2015 SwMultiPortion
& rMulti
)
2017 SwTwips nMaxWidth
= rInf
.Width();
2020 if( rMulti
.HasBrackets() )
2022 TextFrameIndex
const nOldIdx
= rInf
.GetIdx();
2023 rInf
.SetIdx( static_cast<SwDoubleLinePortion
&>(rMulti
).GetBrackets()->nStart
);
2025 nOldX
= GetInfo().X();
2026 static_cast<SwDoubleLinePortion
&>(rMulti
).FormatBrackets( rInf
, nMaxWidth
);
2027 rInf
.SetIdx( nOldIdx
);
2031 std::optional
<SwFontSave
> oFontSave
;
2032 std::unique_ptr
<SwFont
> xTmpFont
;
2033 if( rMulti
.IsDouble() )
2035 xTmpFont
.reset(new SwFont( *rInf
.GetFont() ));
2036 if( rMulti
.IsDouble() )
2039 xTmpFont
->SetProportion( GetPropFont() );
2041 oFontSave
.emplace(rInf
, xTmpFont
.get(), this);
2044 SwLayoutModeModifier
aLayoutModeModifier( *GetInfo().GetOut() );
2045 if ( rMulti
.IsBidi() )
2048 aLayoutModeModifier
.Modify( ! rInf
.GetTextFrame()->IsRightToLeft() );
2053 if( rMulti
.HasRotation() )
2055 // For nMaxWidth we take the height of the body frame.
2056 // #i25067#: If the current frame is inside a table, we restrict
2057 // nMaxWidth to the current frame height, unless the frame size
2058 // attribute is set to variable size:
2060 // We set nTmpX (which is used for portion calculating) to the
2062 const SwPageFrame
* pPage
= m_pFrame
->FindPageFrame();
2063 OSL_ENSURE( pPage
, "No page in frame!");
2064 const SwLayoutFrame
* pUpperFrame
= pPage
;
2066 if ( m_pFrame
->IsInTab() )
2068 pUpperFrame
= m_pFrame
->GetUpper();
2069 while ( pUpperFrame
&& !pUpperFrame
->IsCellFrame() )
2070 pUpperFrame
= pUpperFrame
->GetUpper();
2071 assert(pUpperFrame
); //pFrame is in table but does not have an upper cell frame
2074 const SwTableLine
* pLine
= static_cast<const SwRowFrame
*>(pUpperFrame
->GetUpper())->GetTabLine();
2075 const SwFormatFrameSize
& rFrameFormatSize
= pLine
->GetFrameFormat()->GetFrameSize();
2076 if ( SwFrameSize::Variable
== rFrameFormatSize
.GetHeightSizeType() )
2077 pUpperFrame
= pPage
;
2079 if ( pUpperFrame
== pPage
&& !m_pFrame
->IsInFootnote() )
2080 pUpperFrame
= pPage
->FindBodyCont();
2082 nMaxWidth
= pUpperFrame
?
2083 ( rInf
.GetTextFrame()->IsVertical() ?
2084 pUpperFrame
->getFramePrintArea().Width() :
2085 pUpperFrame
->getFramePrintArea().Height() ) :
2086 std::numeric_limits
<SwTwips
>::max();
2093 SwMultiPortion
* pOldMulti
= m_pMulti
;
2096 SwLineLayout
*pOldCurr
= m_pCurr
;
2097 TextFrameIndex
const nOldStart
= GetStart();
2098 SwTwips nMinWidth
= nTmpX
+ 1;
2099 SwTwips nActWidth
= nMaxWidth
;
2100 const TextFrameIndex nStartIdx
= rInf
.GetIdx();
2101 TextFrameIndex nMultiLen
= rMulti
.GetLen();
2103 SwLinePortion
*pFirstRest
;
2104 SwLinePortion
*pSecondRest
;
2105 if( rMulti
.IsFormatted() )
2107 if( !lcl_ExtractFieldFollow( &rMulti
.GetRoot(), pFirstRest
)
2108 && rMulti
.IsDouble() && rMulti
.GetRoot().GetNext() )
2109 lcl_ExtractFieldFollow( rMulti
.GetRoot().GetNext(), pFirstRest
);
2110 if( !rMulti
.IsDouble() && rMulti
.GetRoot().GetNext() )
2111 lcl_ExtractFieldFollow( rMulti
.GetRoot().GetNext(), pSecondRest
);
2113 pSecondRest
= nullptr;
2117 pFirstRest
= rMulti
.GetRoot().GetNextPortion();
2118 pSecondRest
= rMulti
.GetRoot().GetNext() ?
2119 rMulti
.GetRoot().GetNext()->GetNextPortion() : nullptr;
2121 rMulti
.GetRoot().SetNextPortion( nullptr );
2123 rMulti
.GetRoot().GetNext()->SetNextPortion( nullptr );
2124 rMulti
.SetFormatted();
2125 nMultiLen
= nMultiLen
- rInf
.GetIdx();
2129 const OUString
* pOldText
= &(rInf
.GetText());
2130 const SwTwips nOldPaintOfst
= rInf
.GetPaintOfst();
2131 std::shared_ptr
<const vcl::text::TextLayoutCache
> const pOldCachedVclData(rInf
.GetCachedVclData());
2132 rInf
.SetCachedVclData(nullptr);
2134 OUString
const aMultiStr( rInf
.GetText().copy(0, sal_Int32(nMultiLen
+ rInf
.GetIdx())) );
2135 rInf
.SetText( aMultiStr
);
2136 SwTextFormatInfo
aInf( rInf
, rMulti
.GetRoot(), nActWidth
);
2137 // Do we allow break cuts? The FirstMulti-Flag is evaluated during
2138 // line break determination.
2139 bool bFirstMulti
= rInf
.GetIdx() != rInf
.GetLineStart();
2141 SwLinePortion
*pNextFirst
= nullptr;
2142 SwLinePortion
*pNextSecond
= nullptr;
2145 SwTextGridItem
const*const pGrid(GetGridItem(m_pFrame
->FindPageFrame()));
2146 const bool bHasGrid
= pGrid
&& SwTextGrid::LinesAndChars
== pGrid
->GetGridType();
2148 bool bRubyTop
= false;
2151 bRubyTop
= ! pGrid
->GetRubyTextBelow();
2155 m_pCurr
= &rMulti
.GetRoot();
2156 m_nStart
= nStartIdx
;
2158 FormatReset( aInf
);
2160 aInf
.Width(nActWidth
);
2161 aInf
.RealWidth(nActWidth
);
2162 aInf
.SetFirstMulti( bFirstMulti
);
2163 aInf
.SetNumDone( rInf
.IsNumDone() );
2164 aInf
.SetFootnoteDone( rInf
.IsFootnoteDone() );
2166 // tdf#157829: Bidi portions contain text; word wrapping should underflow.
2167 // By default, the SwTextFormatInfo constructor assumes the current index is the start of
2168 // a new line. As a result, Writer cut breaks MultiPortions as if they were wider than the
2169 // entire document. This is incorrect behavior for bidi portions.
2170 if (rMulti
.IsBidi())
2172 aInf
.SetLineStart(rInf
.GetLineStart());
2175 // if there's a bookmark at the start of the MultiPortion, it will be
2176 // painted with the rotation etc. of the MultiPortion; move it *inside*
2177 // so it gets positioned correctly; currently there's no other portion
2178 // inserted between the end of WhichFirstPortion() and
2179 // BuildMultiPortion()
2180 if (rInf
.GetLast()->GetWhichPor() == PortionType::Bookmark
)
2182 auto const pBookmark(static_cast<SwBookmarkPortion
*>(rInf
.GetLast()));
2183 auto *const pPrevious
= pBookmark
->FindPrevPortion(rInf
.GetRoot());
2184 assert(!pPrevious
|| pPrevious
->GetNextPortion() == pBookmark
);
2187 pPrevious
->SetNextPortion(nullptr);
2189 rInf
.SetLast(pPrevious
);
2190 assert(m_pCurr
->GetNextPortion() == nullptr);
2191 m_pCurr
->SetNextPortion(pBookmark
);
2196 OSL_ENSURE( pFirstRest
->InFieldGrp(), "BuildMulti: Fieldrest expected");
2197 SwFieldPortion
*pField
=
2198 static_cast<SwFieldPortion
*>(pFirstRest
)->Clone(
2199 static_cast<SwFieldPortion
*>(pFirstRest
)->GetExp() );
2200 pField
->SetFollow( true );
2201 aInf
.SetRest( pField
);
2203 aInf
.SetRuby( rMulti
.IsRuby() && rMulti
.OnTop() );
2205 // in grid mode we temporarily have to disable the grid for the ruby line
2206 const bool bOldGridModeAllowed
= GetInfo().SnapToGrid();
2207 if ( bHasGrid
&& aInf
.IsRuby() && bRubyTop
)
2208 aInf
.SetSnapToGrid( false );
2210 // If there's no more rubytext, then buildportion is forbidden
2211 if( pFirstRest
|| !aInf
.IsRuby() )
2212 BuildPortions( aInf
);
2214 aInf
.SetSnapToGrid( bOldGridModeAllowed
);
2216 rMulti
.CalcSize( *this, aInf
);
2217 m_pCurr
->SetRealHeight( m_pCurr
->Height() );
2219 if( rMulti
.IsBidi() )
2221 pNextFirst
= aInf
.GetRest();
2225 if( rMulti
.HasRotation() && !rMulti
.IsDouble() )
2227 // second line has to be formatted
2228 else if( m_pCurr
->GetLen()<nMultiLen
|| rMulti
.IsRuby() || aInf
.GetRest())
2230 TextFrameIndex
const nFirstLen
= m_pCurr
->GetLen();
2231 delete m_pCurr
->GetNext();
2232 m_pCurr
->SetNext( new SwLineLayout() );
2233 m_pCurr
= m_pCurr
->GetNext();
2234 m_nStart
= aInf
.GetIdx();
2236 SwTextFormatInfo
aTmp( aInf
, *m_pCurr
, nActWidth
);
2237 if( rMulti
.IsRuby() )
2239 aTmp
.SetRuby( !rMulti
.OnTop() );
2240 pNextFirst
= aInf
.GetRest();
2243 OSL_ENSURE( pSecondRest
->InFieldGrp(), "Fieldrest expected");
2244 SwFieldPortion
*pField
= static_cast<SwFieldPortion
*>(pSecondRest
)->Clone(
2245 static_cast<SwFieldPortion
*>(pSecondRest
)->GetExp() );
2246 pField
->SetFollow( true );
2247 aTmp
.SetRest( pField
);
2249 if( !rMulti
.OnTop() && nFirstLen
< nMultiLen
)
2253 aTmp
.SetRest( aInf
.GetRest() );
2254 aInf
.SetRest( nullptr );
2256 // in grid mode we temporarily have to disable the grid for the ruby line
2257 if ( bHasGrid
&& aTmp
.IsRuby() && ! bRubyTop
)
2258 aTmp
.SetSnapToGrid( false );
2260 BuildPortions( aTmp
);
2262 const SwLinePortion
*pRightPortion
= rMulti
.OnRight() ?
2263 rMulti
.GetRoot().GetNext()->GetNextPortion() : nullptr;
2266 // The ruby text on the right is vertical.
2267 // The width and the height are swapped.
2268 SwTwips nHeight
= pRightPortion
->Height();
2269 // Keep room for the ruby text.
2270 rMulti
.GetRoot().FindLastPortion()->AddPrtWidth( nHeight
);
2273 aTmp
.SetSnapToGrid( bOldGridModeAllowed
);
2275 rMulti
.CalcSize( *this, aInf
);
2276 rMulti
.GetRoot().SetRealHeight( rMulti
.GetRoot().Height() );
2277 m_pCurr
->SetRealHeight( m_pCurr
->Height() );
2278 if( rMulti
.IsRuby() )
2280 pNextSecond
= aTmp
.GetRest();
2285 pNextFirst
= aTmp
.GetRest();
2286 if( ( !aTmp
.IsRuby() && nFirstLen
+ m_pCurr
->GetLen() < nMultiLen
)
2288 // our guess for width of multiportion was too small,
2289 // text did not fit into multiportion
2292 if( rMulti
.IsRuby() )
2296 // our guess for multiportion width was too small,
2297 // we set min to act
2298 nMinWidth
= nActWidth
;
2299 nActWidth
= ( 3 * nMaxWidth
+ nMinWidth
+ 3 ) / 4;
2300 if ( nActWidth
== nMaxWidth
&& rInf
.GetLineStart() == rInf
.GetIdx() )
2301 // we have too less space, we must allow break cuts
2302 // ( the first multi flag is considered during TextPortion::Format_() )
2303 bFirstMulti
= false;
2304 if( nActWidth
<= nMinWidth
)
2309 // For Solaris, this optimization can causes trouble:
2310 // Setting this to the portion width ( = rMulti.Width() )
2311 // can make GetTextBreak inside SwTextGuess::Guess return too small
2312 // values. Therefore we add some extra twips.
2313 if( nActWidth
> nTmpX
+ rMulti
.Width() + 6 )
2314 nActWidth
= nTmpX
+ rMulti
.Width() + 6;
2315 nMaxWidth
= nActWidth
;
2316 nActWidth
= ( 3 * nMaxWidth
+ nMinWidth
+ 3 ) / 4;
2317 if( nActWidth
>= nMaxWidth
)
2319 // we do not allow break cuts during formatting
2323 pNextFirst
= nullptr;
2326 m_pMulti
= pOldMulti
;
2329 m_nStart
= nOldStart
;
2332 rMulti
.SetLen( rMulti
.GetRoot().GetLen() + ( rMulti
.GetRoot().GetNext() ?
2333 rMulti
.GetRoot().GetNext()->GetLen() : TextFrameIndex(0) ) );
2335 if( rMulti
.IsDouble() )
2337 static_cast<SwDoubleLinePortion
&>(rMulti
).CalcBlanks( rInf
);
2338 if( static_cast<SwDoubleLinePortion
&>(rMulti
).GetLineDiff() )
2340 SwLineLayout
* pLine
= &rMulti
.GetRoot();
2341 if( static_cast<SwDoubleLinePortion
&>(rMulti
).GetLineDiff() > 0 )
2343 rInf
.SetIdx( nStartIdx
+ pLine
->GetLen() );
2344 pLine
= pLine
->GetNext();
2348 GetInfo().SetMulti( true );
2350 // If the fourth element bSkipKashida of function CalcNewBlock is true, multiportion will be showed in justification.
2351 // Kashida (Persian) is a type of justification used in some cursive scripts, particularly Arabic.
2352 // In contrast to white-space justification, which increases the length of a line of text by expanding spaces between words or individual letters,
2353 // kashida justification is accomplished by elongating characters at certain chosen points.
2354 // Kashida justification can be combined with white-space justification to various extents.
2355 // The default value of bSkipKashida (the 4th parameter passed to 'CalcNewBlock') is false.
2356 // Only when Adjust is SvxAdjust::Block ( alignment is justify ), multiportion will be showed in justification in new code.
2357 CalcNewBlock( pLine
, nullptr, rMulti
.Width(), GetAdjust() != SvxAdjust::Block
);
2359 GetInfo().SetMulti( false );
2361 rInf
.SetIdx( nStartIdx
);
2363 if( static_cast<SwDoubleLinePortion
&>(rMulti
).GetBrackets() )
2365 rMulti
.Width( rMulti
.Width() +
2366 static_cast<SwDoubleLinePortion
&>(rMulti
).BracketWidth() );
2367 GetInfo().X( nOldX
);
2372 rMulti
.ActualizeTabulator();
2373 if( rMulti
.IsRuby() )
2375 static_cast<SwRubyPortion
&>(rMulti
).Adjust( rInf
);
2376 static_cast<SwRubyPortion
&>(rMulti
).CalcRubyOffset();
2379 if( rMulti
.HasRotation() )
2381 SwTwips nH
= rMulti
.Width();
2382 SwTwips nAsc
= rMulti
.GetAscent() + ( nH
- rMulti
.Height() )/2;
2387 rMulti
.Width( rMulti
.Height() );
2388 rMulti
.Height( sal_uInt16(nH
) );
2389 rMulti
.SetAscent( sal_uInt16(nAsc
) );
2390 bRet
= ( rInf
.GetPos().X() + rMulti
.Width() > rInf
.Width() ) &&
2391 nStartIdx
!= rInf
.GetLineStart();
2393 else if ( rMulti
.IsBidi() )
2395 bRet
= rMulti
.GetLen() < nMultiLen
|| pNextFirst
;
2398 // line break has to be performed!
2401 OSL_ENSURE( !pNextFirst
|| pNextFirst
->InFieldGrp(),
2402 "BuildMultiPortion: Surprising restportion, field expected" );
2403 SwMultiPortion
*pTmp
;
2404 if( rMulti
.IsDouble() )
2405 pTmp
= new SwDoubleLinePortion( static_cast<SwDoubleLinePortion
&>(rMulti
),
2406 nMultiLen
+ rInf
.GetIdx() );
2407 else if( rMulti
.IsRuby() )
2409 OSL_ENSURE( !pNextSecond
|| pNextSecond
->InFieldGrp(),
2410 "BuildMultiPortion: Surprising restportion, field expected" );
2412 if ( rInf
.GetIdx() == rInf
.GetLineStart() )
2414 // the ruby portion has to be split in two portions
2415 pTmp
= new SwRubyPortion( static_cast<SwRubyPortion
&>(rMulti
),
2416 nMultiLen
+ rInf
.GetIdx() );
2420 pTmp
->GetRoot().SetNext( new SwLineLayout() );
2421 pTmp
->GetRoot().GetNext()->SetNextPortion( pNextSecond
);
2423 pTmp
->SetFollowField();
2427 // we try to keep our ruby portion together
2428 lcl_TruncateMultiPortion(rMulti
, rInf
, nStartIdx
);
2430 // A follow field portion may still be waiting. If nobody wants
2431 // it, we delete it.
2435 else if( rMulti
.HasRotation() )
2437 // we try to keep our rotated portion together
2438 lcl_TruncateMultiPortion(rMulti
, rInf
, nStartIdx
);
2439 pTmp
= new SwRotatedPortion( nMultiLen
+ rInf
.GetIdx(),
2440 rMulti
.GetDirection() );
2442 // during a recursion of BuildMultiPortions we may not build
2443 // a new SwBidiPortion, this would cause a memory leak
2444 else if( rMulti
.IsBidi() && ! m_pMulti
)
2446 auto nTruncType
= lcl_BidiPortionNeedsTruncation(rMulti
, rInf
, aInf
, nStartIdx
);
2447 if (nTruncType
!= BidiTruncationType::None
)
2449 lcl_TruncateMultiPortion(rMulti
, rInf
, nStartIdx
, nTruncType
);
2452 // If there is a HolePortion at the end of the bidi portion,
2453 // it has to be moved behind the bidi portion. Otherwise
2454 // the visual cursor travelling gets into trouble.
2455 SwLineLayout
& aRoot
= rMulti
.GetRoot();
2456 SwLinePortion
* pPor
= aRoot
.GetFirstPortion();
2459 if ( pPor
->GetNextPortion() && pPor
->GetNextPortion()->IsHolePortion() )
2461 SwLinePortion
* pHolePor
= pPor
->GetNextPortion();
2462 pPor
->SetNextPortion( nullptr );
2463 aRoot
.SetLen( aRoot
.GetLen() - pHolePor
->GetLen() );
2464 rMulti
.SetLen( rMulti
.GetLen() - pHolePor
->GetLen() );
2465 rMulti
.SetNextPortion( pHolePor
);
2468 pPor
= pPor
->GetNextPortion();
2471 pTmp
= new SwBidiPortion( nMultiLen
+ rInf
.GetIdx(),
2472 static_cast<SwBidiPortion
&>(rMulti
).GetLevel() );
2477 if ( ! rMulti
.GetLen() && rInf
.GetLast() )
2479 SeekAndChgBefore( rInf
);
2480 rInf
.GetLast()->FormatEOL( rInf
);
2483 if( pNextFirst
&& pTmp
)
2485 pTmp
->SetFollowField();
2486 pTmp
->GetRoot().SetNextPortion( pNextFirst
);
2489 // A follow field portion is still waiting. If nobody wants it,
2493 rInf
.SetRest( pTmp
);
2496 rInf
.SetCachedVclData(pOldCachedVclData
);
2497 rInf
.SetText( *pOldText
);
2498 rInf
.SetPaintOfst( nOldPaintOfst
);
2499 rInf
.SetStop( aInf
.IsStop() );
2500 rInf
.SetNumDone( true );
2501 rInf
.SetFootnoteDone( true );
2509 static bool IsIncompleteRuby(const SwMultiPortion
& rHelpMulti
)
2511 return rHelpMulti
.IsRuby() && static_cast<const SwRubyPortion
&>(rHelpMulti
).GetRubyOffset() < TextFrameIndex(COMPLETE_STRING
);
2514 // When a fieldportion at the end of line breaks and needs a following
2515 // fieldportion in the next line, then the "restportion" of the formatinfo
2516 // has to be set. Normally this happens during the formatting of the first
2517 // part of the fieldportion.
2518 // But sometimes the formatting starts at the line with the following part,
2519 // especially when the following part is on the next page.
2520 // In this case the MakeRestPortion-function has to create the following part.
2521 // The first parameter is the line that contains possibly a first part
2522 // of a field. When the function finds such field part, it creates the right
2523 // restportion. This may be a multiportion, e.g. if the field is surrounded by
2524 // a doubleline- or ruby-portion.
2525 // The second parameter is the start index of the line.
2526 SwLinePortion
* SwTextFormatter::MakeRestPortion( const SwLineLayout
* pLine
,
2527 TextFrameIndex nPosition
)
2531 TextFrameIndex nMultiPos
= nPosition
- pLine
->GetLen();
2532 const SwMultiPortion
*pTmpMulti
= nullptr;
2533 const SwMultiPortion
*pHelpMulti
= nullptr;
2534 const SwLinePortion
* pPor
= pLine
->GetFirstPortion();
2535 SwFieldPortion
*pField
= nullptr;
2538 if( pPor
->GetLen() && !pHelpMulti
)
2540 nMultiPos
= nMultiPos
+ pPor
->GetLen();
2541 pTmpMulti
= nullptr;
2543 if( pPor
->InFieldGrp() )
2546 pTmpMulti
= nullptr;
2547 pField
= const_cast<SwFieldPortion
*>(static_cast<const SwFieldPortion
*>(pPor
));
2549 else if( pPor
->IsMultiPortion() )
2551 OSL_ENSURE( !pHelpMulti
|| pHelpMulti
->IsBidi(),
2552 "Nested multiportions are forbidden." );
2555 pTmpMulti
= static_cast<const SwMultiPortion
*>(pPor
);
2557 pPor
= pPor
->GetNextPortion();
2558 // If the last portion is a multi-portion, we enter it
2559 // and look for a field portion inside.
2560 // If we are already in a multiportion, we could change to the
2562 if( !pPor
&& pTmpMulti
)
2565 { // We're already inside the multiportion, let's take the second
2566 // line, if we are in a double line portion
2567 if( !pHelpMulti
->IsRuby() )
2568 pPor
= pHelpMulti
->GetRoot().GetNext();
2569 pTmpMulti
= nullptr;
2572 { // Now we enter a multiportion, in a ruby portion we take the
2573 // main line, not the phonetic line, in a doublelineportion we
2574 // starts with the first line.
2575 pHelpMulti
= pTmpMulti
;
2576 nMultiPos
= nMultiPos
- pHelpMulti
->GetLen();
2577 if( pHelpMulti
->IsRuby() && pHelpMulti
->OnTop() )
2578 pPor
= pHelpMulti
->GetRoot().GetNext();
2580 pPor
= pHelpMulti
->GetRoot().GetFirstPortion();
2584 if( pField
&& !pField
->HasFollow() )
2587 SwLinePortion
*pRest
= nullptr;
2590 const SwTextAttr
*pHint
= GetAttr(nPosition
- TextFrameIndex(1));
2592 && ( pHint
->Which() == RES_TXTATR_FIELD
2593 || pHint
->Which() == RES_TXTATR_ANNOTATION
) )
2595 pRest
= NewFieldPortion( GetInfo(), pHint
);
2596 if( pRest
->InFieldGrp() )
2597 static_cast<SwFieldPortion
*>(pRest
)->TakeNextOffset( pField
);
2608 nPosition
= nMultiPos
+ pHelpMulti
->GetLen();
2609 std::optional
<SwMultiCreator
> pCreate
= GetInfo().GetMultiCreator( nMultiPos
, nullptr );
2613 OSL_ENSURE( !pHelpMulti
->GetLen(), "Multiportion without attribute?" );
2616 pCreate
= GetInfo().GetMultiCreator( --nMultiPos
, nullptr );
2622 if( pRest
|| nMultiPos
> nPosition
|| IsIncompleteRuby(*pHelpMulti
))
2624 SwMultiPortion
* pTmp
;
2625 if( pHelpMulti
->IsDouble() )
2626 pTmp
= new SwDoubleLinePortion( *pCreate
, nMultiPos
);
2627 else if( pHelpMulti
->IsBidi() )
2628 pTmp
= new SwBidiPortion( nMultiPos
, pCreate
->nLevel
);
2629 else if (IsIncompleteRuby(*pHelpMulti
) && pCreate
->pAttr
)
2631 TextFrameIndex nRubyOffset
= static_cast<const SwRubyPortion
*>(pHelpMulti
)->GetRubyOffset();
2632 pTmp
= new SwRubyPortion( *pCreate
, *GetInfo().GetFont(),
2633 m_pFrame
->GetDoc().getIDocumentSettingAccess(),
2634 nMultiPos
, nRubyOffset
,
2637 else if( pHelpMulti
->HasRotation() )
2638 pTmp
= new SwRotatedPortion( nMultiPos
, pHelpMulti
->GetDirection() );
2644 pTmp
->SetFollowField();
2647 SwLineLayout
*pLay
= &pTmp
->GetRoot();
2648 if( pTmp
->IsRuby() && pTmp
->OnTop() )
2650 pLay
->SetNext( new SwLineLayout() );
2651 pLay
= pLay
->GetNext();
2653 pLay
->SetNextPortion( pRest
);
2660 // SwTextCursorSave notes the start and current line of a SwTextCursor,
2661 // sets them to the values for GetModelPositionForViewPoint inside a multiportion
2662 // and restores them in the destructor.
2663 SwTextCursorSave::SwTextCursorSave( SwTextCursor
* pCursor
,
2664 SwMultiPortion
* pMulti
,
2667 TextFrameIndex
const nCurrStart
,
2668 tools::Long nSpaceAdd
)
2669 : pTextCursor(pCursor
),
2670 pCurr(pCursor
->m_pCurr
),
2671 nStart(pCursor
->m_nStart
)
2673 pCursor
->m_nStart
= nCurrStart
;
2674 pCursor
->m_pCurr
= &pMulti
->GetRoot();
2675 while( pCursor
->Y() + pCursor
->GetLineHeight() < nY
&&
2678 nWidth
= pCursor
->m_pCurr
->Width();
2679 nOldProp
= pCursor
->GetPropFont();
2681 if ( pMulti
->IsDouble() || pMulti
->IsBidi() )
2683 bSpaceChg
= pMulti
->ChgSpaceAdd( pCursor
->m_pCurr
, nSpaceAdd
);
2685 TextFrameIndex nSpaceCnt
;
2686 if ( pMulti
->IsDouble() )
2688 pCursor
->SetPropFont( 50 );
2689 nSpaceCnt
= static_cast<SwDoubleLinePortion
*>(pMulti
)->GetSpaceCnt();
2693 TextFrameIndex
const nOldIdx
= pCursor
->GetInfo().GetIdx();
2694 pCursor
->GetInfo().SetIdx ( nCurrStart
);
2695 nSpaceCnt
= static_cast<SwBidiPortion
*>(pMulti
)->GetSpaceCnt(pCursor
->GetInfo());
2696 pCursor
->GetInfo().SetIdx ( nOldIdx
);
2699 if( nSpaceAdd
> 0 && !pMulti
->HasTabulator() )
2700 pCursor
->m_pCurr
->Width( o3tl::narrowing
<sal_uInt16
>(nWidth
+ nSpaceAdd
* sal_Int32(nSpaceCnt
) / SPACING_PRECISION_FACTOR
) );
2702 // For a BidiPortion we have to calculate the offset from the
2703 // end of the portion
2704 if ( nX
&& pMulti
->IsBidi() )
2705 nX
= pCursor
->m_pCurr
->Width() - nX
;
2711 SwTextCursorSave::~SwTextCursorSave()
2714 SwDoubleLinePortion::ResetSpaceAdd( pTextCursor
->m_pCurr
);
2715 pTextCursor
->m_pCurr
->Width( nWidth
);
2716 pTextCursor
->m_pCurr
= pCurr
;
2717 pTextCursor
->m_nStart
= nStart
;
2718 pTextCursor
->SetPropFont( nOldProp
);
2721 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */