1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20 #include <deque>
21 #include <memory>
22
23 #include <hintids.hxx>
24
25 #include <editeng/twolinesitem.hxx>
26 #include <editeng/charrotateitem.hxx>
27 #include <vcl/outdev.hxx>
28 #include <fmtfld.hxx>
29 #include <fldbas.hxx>
30 #include <txatbase.hxx>
31 #include <fmtruby.hxx>
32 #include <txtatr.hxx>
33 #include <charfmt.hxx>
34 #include <txtinet.hxx>
35 #include <fchrfmt.hxx>
36 #include <layfrm.hxx>
37 #include <SwPortionHandler.hxx>
38 #include "pormulti.hxx"
39 #include "inftxt.hxx"
40 #include "itrpaint.hxx"
41 #include <viewopt.hxx>
42 #include "itrform2.hxx"
43 #include "porfld.hxx"
44 #include "porglue.hxx"
45 #include <breakit.hxx>
46 #include <pagefrm.hxx>
47 #include <rowfrm.hxx>
48 #include <pagedesc.hxx>
49 #include <tgrditem.hxx>
50 #include <swtable.hxx>
51 #include <fmtfsize.hxx>
52
53 using namespace ::com::sun::star;
54
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.
~SwMultiPortion()59 SwMultiPortion::~SwMultiPortion()
60 {
61 }
62
Paint(const SwTextPaintInfo &) const63 void SwMultiPortion::Paint( const SwTextPaintInfo & ) const
64 {
65 OSL_FAIL( "Don't try SwMultiPortion::Paint, try SwTextPainter::PaintMultiPortion" );
66 }
67
68 // Summarize the internal lines to calculate the (external) size.
69 // The internal line has to calculate first.
CalcSize(SwTextFormatter & rLine,SwTextFormatInfo & rInf)70 void SwMultiPortion::CalcSize( SwTextFormatter& rLine, SwTextFormatInfo &rInf )
71 {
72 Width( 0 );
73 Height( 0 );
74 SetAscent( 0 );
75 SetFlyInContent( false );
76 SwLineLayout *pLay = &GetRoot();
77 do
78 {
79 pLay->CalcLine( rLine, rInf );
80 if( rLine.IsFlyInCntBase() )
81 SetFlyInContent( true );
82 if( IsRuby() && ( OnTop() == ( pLay == &GetRoot() ) ) )
83 {
84 // An empty phonetic line don't need an ascent or a height.
85 if( !pLay->Width() )
86 {
87 pLay->SetAscent( 0 );
88 pLay->Height( 0 );
89 }
90 if( OnTop() )
91 SetAscent( GetAscent() + pLay->Height() );
92 }
93 else
94 SetAscent( GetAscent() + pLay->GetAscent() );
95
96 // Increase the line height, except for ruby text on the right.
97 if ( !IsRuby() || !OnRight() || pLay == &GetRoot() )
98 Height( Height() + pLay->Height() );
99 else
100 {
101 // We already added the width after building the portion,
102 // so no need to add it twice.
103 break;
104 }
105
106 if( Width() < pLay->Width() )
107 Width( pLay->Width() );
108 pLay = pLay->GetNext();
109 } while ( pLay );
110 if( HasBrackets() )
111 {
112 sal_uInt16 nTmp = static_cast<SwDoubleLinePortion*>(this)->GetBrackets()->nHeight;
113 if( nTmp > Height() )
114 {
115 const sal_uInt16 nAdd = ( nTmp - Height() ) / 2;
116 GetRoot().SetAscent( GetRoot().GetAscent() + nAdd );
117 GetRoot().Height( GetRoot().Height() + nAdd );
118 Height( nTmp );
119 }
120 nTmp = static_cast<SwDoubleLinePortion*>(this)->GetBrackets()->nAscent;
121 if( nTmp > GetAscent() )
122 SetAscent( nTmp );
123 }
124 }
125
CalcSpacing(long,const SwTextSizeInfo &) const126 long SwMultiPortion::CalcSpacing( long , const SwTextSizeInfo & ) const
127 {
128 return 0;
129 }
130
ChgSpaceAdd(SwLineLayout *,long) const131 bool SwMultiPortion::ChgSpaceAdd( SwLineLayout*, long ) const
132 {
133 return false;
134 }
135
HandlePortion(SwPortionHandler & rPH) const136 void SwMultiPortion::HandlePortion( SwPortionHandler& rPH ) const
137 {
138 rPH.Text( GetLen(), GetWhichPor() );
139 }
140
141 // sets the tabulator-flag, if there's any tabulator-portion inside.
ActualizeTabulator()142 void SwMultiPortion::ActualizeTabulator()
143 {
144 SwLinePortion* pPor = GetRoot().GetFirstPortion();
145 // First line
146 for( m_bTab1 = m_bTab2 = false; pPor; pPor = pPor->GetNextPortion() )
147 if( pPor->InTabGrp() )
148 SetTab1( true );
149 if( GetRoot().GetNext() )
150 {
151 // Second line
152 pPor = GetRoot().GetNext()->GetFirstPortion();
153 do
154 {
155 if( pPor->InTabGrp() )
156 SetTab2( true );
157 pPor = pPor->GetNextPortion();
158 } while ( pPor );
159 }
160 }
161
SwRotatedPortion(const SwMultiCreator & rCreate,TextFrameIndex const nEnd,bool bRTL)162 SwRotatedPortion::SwRotatedPortion( const SwMultiCreator& rCreate,
163 TextFrameIndex const nEnd, bool bRTL )
164 : SwMultiPortion( nEnd )
165 {
166 const SvxCharRotateItem* pRot = static_cast<const SvxCharRotateItem*>(rCreate.pItem);
167 if( !pRot )
168 {
169 const SwTextAttr& rAttr = *rCreate.pAttr;
170 const SfxPoolItem *const pItem =
171 CharFormat::GetItem(rAttr, RES_CHRATR_ROTATE);
172 if ( pItem )
173 {
174 pRot = static_cast<const SvxCharRotateItem*>(pItem);
175 }
176 }
177 if( pRot )
178 {
179 sal_uInt8 nDir;
180 if ( bRTL )
181 nDir = pRot->IsBottomToTop() ? 3 : 1;
182 else
183 nDir = pRot->IsBottomToTop() ? 1 : 3;
184
185 SetDirection( nDir );
186 }
187 }
188
SwBidiPortion(TextFrameIndex const nEnd,sal_uInt8 nLv)189 SwBidiPortion::SwBidiPortion(TextFrameIndex const nEnd, sal_uInt8 nLv)
190 : SwMultiPortion( nEnd ), nLevel( nLv )
191 {
192 SetBidi();
193
194 if ( nLevel % 2 )
195 SetDirection( DIR_RIGHT2LEFT );
196 else
197 SetDirection( DIR_LEFT2RIGHT );
198 }
199
CalcSpacing(long nSpaceAdd,const SwTextSizeInfo & rInf) const200 long SwBidiPortion::CalcSpacing( long nSpaceAdd, const SwTextSizeInfo& rInf ) const
201 {
202 return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt(rInf)) * nSpaceAdd / SPACING_PRECISION_FACTOR;
203 }
204
ChgSpaceAdd(SwLineLayout * pCurr,long nSpaceAdd) const205 bool SwBidiPortion::ChgSpaceAdd( SwLineLayout* pCurr, long nSpaceAdd ) const
206 {
207 if( !HasTabulator() && nSpaceAdd > 0 && !pCurr->IsSpaceAdd() )
208 {
209 pCurr->CreateSpaceAdd();
210 pCurr->SetLLSpaceAdd( nSpaceAdd, 0 );
211 return true;
212 }
213
214 return false;
215 }
216
GetSpaceCnt(const SwTextSizeInfo & rInf) const217 TextFrameIndex SwBidiPortion::GetSpaceCnt(const SwTextSizeInfo &rInf) const
218 {
219 // Calculate number of blanks for justified alignment
220 TextFrameIndex nTmpStart = rInf.GetIdx();
221 TextFrameIndex nNull(0);
222 TextFrameIndex nBlanks(0);
223
224 for (SwLinePortion* pPor = GetRoot().GetFirstPortion(); pPor; pPor = pPor->GetNextPortion())
225 {
226 if( pPor->InTextGrp() )
227 nBlanks = nBlanks + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
228 else if ( pPor->IsMultiPortion() &&
229 static_cast<SwMultiPortion*>(pPor)->IsBidi() )
230 nBlanks = nBlanks + static_cast<SwBidiPortion*>(pPor)->GetSpaceCnt( rInf );
231
232 const_cast<SwTextSizeInfo &>(rInf).SetIdx( rInf.GetIdx() + pPor->GetLen() );
233 }
234 const_cast<SwTextSizeInfo &>(rInf).SetIdx( nTmpStart );
235 return nBlanks;
236 }
237
238 // This constructor is for the continuation of a doubleline portion
239 // in the next line.
240 // It takes the same brackets and if the original has no content except
241 // brackets, these will be deleted.
SwDoubleLinePortion(SwDoubleLinePortion & rDouble,TextFrameIndex const nEnd)242 SwDoubleLinePortion::SwDoubleLinePortion(
243 SwDoubleLinePortion& rDouble, TextFrameIndex const nEnd)
244 : SwMultiPortion(nEnd)
245 , nLineDiff(0)
246 , nBlank1(0)
247 , nBlank2(0)
248 {
249 SetDirection( rDouble.GetDirection() );
250 SetDouble();
251 if( rDouble.GetBrackets() )
252 {
253 SetBrackets( rDouble );
254 // An empty multiportion needs no brackets.
255 // Notice: GetLen() might be zero, if the multiportion contains
256 // the second part of a field and the width might be zero, if
257 // it contains a note only. In this cases the brackets are okay.
258 // But if the length and the width are both zero, the portion
259 // is really empty.
260 if( rDouble.Width() == rDouble.BracketWidth() )
261 rDouble.ClearBrackets();
262 }
263 }
264
265 // This constructor uses the textattribute to get the right brackets.
266 // The textattribute could be a 2-line-attribute or a character- or
267 // internet style, which contains the 2-line-attribute.
SwDoubleLinePortion(const SwMultiCreator & rCreate,TextFrameIndex const nEnd)268 SwDoubleLinePortion::SwDoubleLinePortion(
269 const SwMultiCreator& rCreate, TextFrameIndex const nEnd)
270 : SwMultiPortion(nEnd)
271 , pBracket(new SwBracket)
272 , nLineDiff(0)
273 , nBlank1(0)
274 , nBlank2(0)
275 {
276 pBracket->nAscent = 0;
277 pBracket->nHeight = 0;
278 pBracket->nPreWidth = 0;
279 pBracket->nPostWidth = 0;
280
281 SetDouble();
282 const SvxTwoLinesItem* pTwo = static_cast<const SvxTwoLinesItem*>(rCreate.pItem);
283 if( pTwo )
284 pBracket->nStart = TextFrameIndex(0);
285 else
286 {
287 const SwTextAttr& rAttr = *rCreate.pAttr;
288 pBracket->nStart = rCreate.nStartOfAttr;
289
290 const SfxPoolItem * const pItem =
291 CharFormat::GetItem( rAttr, RES_CHRATR_TWO_LINES );
292 if ( pItem )
293 {
294 pTwo = static_cast<const SvxTwoLinesItem*>(pItem);
295 }
296 }
297 if( pTwo )
298 {
299 pBracket->cPre = pTwo->GetStartBracket();
300 pBracket->cPost = pTwo->GetEndBracket();
301 }
302 else
303 {
304 pBracket->cPre = 0;
305 pBracket->cPost = 0;
306 }
307 SwFontScript nTmp = SW_SCRIPTS;
308 if( pBracket->cPre > 255 )
309 {
310 OUString aText(pBracket->cPre);
311 nTmp = SwScriptInfo::WhichFont(0, aText);
312 }
313 pBracket->nPreScript = nTmp;
314 nTmp = SW_SCRIPTS;
315 if( pBracket->cPost > 255 )
316 {
317 OUString aText(pBracket->cPost);
318 nTmp = SwScriptInfo::WhichFont(0, aText);
319 }
320 pBracket->nPostScript = nTmp;
321
322 if( !pBracket->cPre && !pBracket->cPost )
323 {
324 pBracket.reset();
325 }
326
327 // double line portions have the same direction as the frame directions
328 if ( rCreate.nLevel % 2 )
329 SetDirection( DIR_RIGHT2LEFT );
330 else
331 SetDirection( DIR_LEFT2RIGHT );
332 }
333
334 // paints the wished bracket,
335 // if the multiportion has surrounding brackets.
336 // The X-position of the SwTextPaintInfo will be modified:
337 // the open bracket sets position behind itself,
338 // the close bracket in front of itself.
PaintBracket(SwTextPaintInfo & rInf,long nSpaceAdd,bool bOpen) const339 void SwDoubleLinePortion::PaintBracket( SwTextPaintInfo &rInf,
340 long nSpaceAdd,
341 bool bOpen ) const
342 {
343 sal_Unicode cCh = bOpen ? pBracket->cPre : pBracket->cPost;
344 if( !cCh )
345 return;
346 const sal_uInt16 nChWidth = bOpen ? PreWidth() : PostWidth();
347 if( !nChWidth )
348 return;
349 if( !bOpen )
350 rInf.X( rInf.X() + Width() - PostWidth() +
351 ( nSpaceAdd > 0 ? CalcSpacing( nSpaceAdd, rInf ) : 0 ) );
352
353 SwBlankPortion aBlank( cCh, true );
354 aBlank.SetAscent( pBracket->nAscent );
355 aBlank.Width( nChWidth );
356 aBlank.Height( pBracket->nHeight );
357 {
358 std::unique_ptr<SwFont> pTmpFnt( new SwFont( *rInf.GetFont() ) );
359 SwFontScript nAct = bOpen ? pBracket->nPreScript : pBracket->nPostScript;
360 if( SW_SCRIPTS > nAct )
361 pTmpFnt->SetActual( nAct );
362 pTmpFnt->SetProportion( 100 );
363 SwFontSave aSave( rInf, pTmpFnt.get() );
364 aBlank.Paint( rInf );
365 }
366 if( bOpen )
367 rInf.X( rInf.X() + PreWidth() );
368 }
369
370 // creates the bracket-structure
371 // and fills it, if not both characters are 0x00.
SetBrackets(const SwDoubleLinePortion & rDouble)372 void SwDoubleLinePortion::SetBrackets( const SwDoubleLinePortion& rDouble )
373 {
374 if( rDouble.pBracket )
375 {
376 pBracket.reset( new SwBracket );
377 pBracket->cPre = rDouble.pBracket->cPre;
378 pBracket->cPost = rDouble.pBracket->cPost;
379 pBracket->nPreScript = rDouble.pBracket->nPreScript;
380 pBracket->nPostScript = rDouble.pBracket->nPostScript;
381 pBracket->nStart = rDouble.pBracket->nStart;
382 }
383 }
384
385 // calculates the size of the brackets => pBracket,
386 // reduces the nMaxWidth-parameter ( minus bracket-width )
387 // and moves the rInf-x-position behind the opening bracket.
FormatBrackets(SwTextFormatInfo & rInf,SwTwips & nMaxWidth)388 void SwDoubleLinePortion::FormatBrackets( SwTextFormatInfo &rInf, SwTwips& nMaxWidth )
389 {
390 nMaxWidth -= rInf.X();
391 std::unique_ptr<SwFont> pTmpFnt( new SwFont( *rInf.GetFont() ) );
392 pTmpFnt->SetProportion( 100 );
393 pBracket->nAscent = 0;
394 pBracket->nHeight = 0;
395 if( pBracket->cPre )
396 {
397 OUString aStr( pBracket->cPre );
398 SwFontScript nActualScr = pTmpFnt->GetActual();
399 if( SW_SCRIPTS > pBracket->nPreScript )
400 pTmpFnt->SetActual( pBracket->nPreScript );
401 SwFontSave aSave( rInf, pTmpFnt.get() );
402 SwPosSize aSize = rInf.GetTextSize( aStr );
403 pBracket->nAscent = rInf.GetAscent();
404 pBracket->nHeight = aSize.Height();
405 pTmpFnt->SetActual( nActualScr );
406 if( nMaxWidth > aSize.Width() )
407 {
408 pBracket->nPreWidth = aSize.Width();
409 nMaxWidth -= aSize.Width();
410 rInf.X( rInf.X() + aSize.Width() );
411 }
412 else
413 {
414 pBracket->nPreWidth = 0;
415 nMaxWidth = 0;
416 }
417 }
418 else
419 pBracket->nPreWidth = 0;
420 if( pBracket->cPost )
421 {
422 OUString aStr( pBracket->cPost );
423 if( SW_SCRIPTS > pBracket->nPostScript )
424 pTmpFnt->SetActual( pBracket->nPostScript );
425 SwFontSave aSave( rInf, pTmpFnt.get() );
426 SwPosSize aSize = rInf.GetTextSize( aStr );
427 const sal_uInt16 nTmpAsc = rInf.GetAscent();
428 if( nTmpAsc > pBracket->nAscent )
429 {
430 pBracket->nHeight += nTmpAsc - pBracket->nAscent;
431 pBracket->nAscent = nTmpAsc;
432 }
433 if( aSize.Height() > pBracket->nHeight )
434 pBracket->nHeight = aSize.Height();
435 if( nMaxWidth > aSize.Width() )
436 {
437 pBracket->nPostWidth = aSize.Width();
438 nMaxWidth -= aSize.Width();
439 }
440 else
441 {
442 pBracket->nPostWidth = 0;
443 nMaxWidth = 0;
444 }
445 }
446 else
447 pBracket->nPostWidth = 0;
448 nMaxWidth += rInf.X();
449 }
450
451 // calculates the number of blanks in each line and
452 // the difference of the width of the two lines.
453 // These results are used from the text adjustment.
CalcBlanks(SwTextFormatInfo & rInf)454 void SwDoubleLinePortion::CalcBlanks( SwTextFormatInfo &rInf )
455 {
456 SwLinePortion* pPor = GetRoot().GetFirstPortion();
457 TextFrameIndex nNull(0);
458 TextFrameIndex nStart = rInf.GetIdx();
459 SetTab1( false );
460 SetTab2( false );
461 for (nBlank1 = TextFrameIndex(0); pPor; pPor = pPor->GetNextPortion())
462 {
463 if( pPor->InTextGrp() )
464 nBlank1 = nBlank1 + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
465 rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
466 if( pPor->InTabGrp() )
467 SetTab1( true );
468 }
469 nLineDiff = GetRoot().Width();
470 if( GetRoot().GetNext() )
471 {
472 pPor = GetRoot().GetNext()->GetFirstPortion();
473 nLineDiff -= GetRoot().GetNext()->Width();
474 }
475 for (nBlank2 = TextFrameIndex(0); pPor; pPor = pPor->GetNextPortion())
476 {
477 if( pPor->InTextGrp() )
478 nBlank2 = nBlank2 + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
479 rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
480 if( pPor->InTabGrp() )
481 SetTab2( true );
482 }
483 rInf.SetIdx( nStart );
484 }
485
CalcSpacing(long nSpaceAdd,const SwTextSizeInfo &) const486 long SwDoubleLinePortion::CalcSpacing( long nSpaceAdd, const SwTextSizeInfo & ) const
487 {
488 return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt()) * nSpaceAdd / SPACING_PRECISION_FACTOR;
489 }
490
491 // Merges the spaces for text adjustment from the inner and outer part.
492 // Inside the doubleline portion the wider line has no spaceadd-array, the
493 // smaller line has such an array to reach width of the wider line.
494 // If the surrounding line has text adjustment and the doubleline portion
495 // contains no tabulator, it is necessary to create/manipulate the inner
496 // space arrays.
ChgSpaceAdd(SwLineLayout * pCurr,long nSpaceAdd) const497 bool SwDoubleLinePortion::ChgSpaceAdd( SwLineLayout* pCurr,
498 long nSpaceAdd ) const
499 {
500 bool bRet = false;
501 if( !HasTabulator() && nSpaceAdd > 0 )
502 {
503 if( !pCurr->IsSpaceAdd() )
504 {
505 // The wider line gets the spaceadd from the surrounding line direct
506 pCurr->CreateSpaceAdd();
507 pCurr->SetLLSpaceAdd( nSpaceAdd, 0 );
508 bRet = true;
509 }
510 else
511 {
512 sal_Int32 const nMyBlank = sal_Int32(GetSmallerSpaceCnt());
513 sal_Int32 const nOther = sal_Int32(GetSpaceCnt());
514 SwTwips nMultiSpace = pCurr->GetLLSpaceAdd( 0 ) * nMyBlank + nOther * nSpaceAdd;
515
516 if( nMyBlank )
517 nMultiSpace /= sal_Int32(nMyBlank);
518
519 // pCurr->SetLLSpaceAdd( nMultiSpace, 0 );
520 // #i65711# SetLLSpaceAdd replaces the first value,
521 // instead we want to insert a new first value:
522 std::vector<long>* pVec = pCurr->GetpLLSpaceAdd();
523 pVec->insert( pVec->begin(), nMultiSpace );
524 bRet = true;
525 }
526 }
527 return bRet;
528 }
529 // cancels the manipulation from SwDoubleLinePortion::ChangeSpaceAdd(..)
ResetSpaceAdd(SwLineLayout * pCurr)530 void SwDoubleLinePortion::ResetSpaceAdd( SwLineLayout* pCurr )
531 {
532 pCurr->RemoveFirstLLSpaceAdd();
533 if( !pCurr->GetLLSpaceAddCount() )
534 pCurr->FinishSpaceAdd();
535 }
536
~SwDoubleLinePortion()537 SwDoubleLinePortion::~SwDoubleLinePortion()
538 {
539 }
540
541 // constructs a ruby portion, i.e. an additional text is displayed
542 // beside the main text, e.g. phonetic characters.
SwRubyPortion(const SwRubyPortion & rRuby,TextFrameIndex const nEnd)543 SwRubyPortion::SwRubyPortion(const SwRubyPortion& rRuby, TextFrameIndex const nEnd)
544 : SwMultiPortion( nEnd )
545 , nRubyOffset( rRuby.GetRubyOffset() )
546 , nAdjustment( rRuby.GetAdjustment() )
547 {
548 SetDirection( rRuby.GetDirection() );
549 SetRubyPosition( rRuby.GetRubyPosition() );
550 SetRuby();
551 }
552
553 // constructs a ruby portion, i.e. an additional text is displayed
554 // beside the main text, e.g. phonetic characters.
SwRubyPortion(const SwMultiCreator & rCreate,const SwFont & rFnt,const IDocumentSettingAccess & rIDocumentSettingAccess,TextFrameIndex const nEnd,TextFrameIndex const nOffs,const SwTextSizeInfo & rInf)555 SwRubyPortion::SwRubyPortion( const SwMultiCreator& rCreate, const SwFont& rFnt,
556 const IDocumentSettingAccess& rIDocumentSettingAccess,
557 TextFrameIndex const nEnd, TextFrameIndex const nOffs,
558 const SwTextSizeInfo &rInf )
559 : SwMultiPortion( nEnd )
560 {
561 SetRuby();
562 OSL_ENSURE( SwMultiCreatorId::Ruby == rCreate.nId, "Ruby expected" );
563 OSL_ENSURE( RES_TXTATR_CJK_RUBY == rCreate.pAttr->Which(), "Wrong attribute" );
564 const SwFormatRuby& rRuby = rCreate.pAttr->GetRuby();
565 nAdjustment = rRuby.GetAdjustment();
566 nRubyOffset = nOffs;
567
568 const SwTextFrame *pFrame = rInf.GetTextFrame();
569 RubyPosition ePos = static_cast<RubyPosition>( rRuby.GetPosition() );
570
571 // RIGHT is designed for horizontal writing mode only.
572 if ( ePos == RubyPosition::RIGHT && pFrame->IsVertical() )
573 ePos = RubyPosition::ABOVE;
574
575 // In grid mode we force the ruby text to the upper or lower line
576 if ( rInf.SnapToGrid() )
577 {
578 SwTextGridItem const*const pGrid( GetGridItem(pFrame->FindPageFrame()) );
579 if ( pGrid )
580 ePos = pGrid->GetRubyTextBelow() ? RubyPosition::BELOW : RubyPosition::ABOVE;
581 }
582
583 SetRubyPosition( ePos );
584
585 const SwCharFormat *const pFormat =
586 static_txtattr_cast<SwTextRuby const*>(rCreate.pAttr)->GetCharFormat();
587 std::unique_ptr<SwFont> pRubyFont;
588 if( pFormat )
589 {
590 const SwAttrSet& rSet = pFormat->GetAttrSet();
591 pRubyFont.reset(new SwFont( rFnt ));
592 pRubyFont->SetDiffFnt( &rSet, &rIDocumentSettingAccess );
593
594 // we do not allow a vertical font for the ruby text
595 pRubyFont->SetVertical( rFnt.GetOrientation() , OnRight() );
596 }
597
598 OUString aStr = rRuby.GetText().copy( sal_Int32(nOffs) );
599 SwFieldPortion *pField = new SwFieldPortion( aStr, std::move(pRubyFont) );
600 pField->SetNextOffset( nOffs );
601 pField->SetFollow( true );
602
603 if( OnTop() )
604 GetRoot().SetNextPortion( pField );
605 else
606 {
607 GetRoot().SetNext( new SwLineLayout() );
608 GetRoot().GetNext()->SetNextPortion( pField );
609 }
610
611 // ruby portions have the same direction as the frame directions
612 if ( rCreate.nLevel % 2 )
613 {
614 // switch right and left ruby adjustment in rtl environment
615 if ( css::text::RubyAdjust_LEFT == nAdjustment )
616 nAdjustment = css::text::RubyAdjust_RIGHT;
617 else if ( css::text::RubyAdjust_RIGHT == nAdjustment )
618 nAdjustment = css::text::RubyAdjust_LEFT;
619
620 SetDirection( DIR_RIGHT2LEFT );
621 }
622 else
623 SetDirection( DIR_LEFT2RIGHT );
624 }
625
626 // In ruby portion there are different alignments for
627 // the ruby text and the main text.
628 // Left, right, centered and two possibilities of block adjustment
629 // The block adjustment is realized by spacing between the characters,
630 // either with a half space or no space in front of the first letter and
631 // a half space at the end of the last letter.
632 // Notice: the smaller line will be manipulated, normally it's the ruby line,
633 // but it could be the main text, too.
634 // If there is a tabulator in smaller line, no adjustment is possible.
Adjust_(SwTextFormatInfo & rInf)635 void SwRubyPortion::Adjust_( SwTextFormatInfo &rInf )
636 {
637 SwTwips nLineDiff = GetRoot().Width() - GetRoot().GetNext()->Width();
638 TextFrameIndex const nOldIdx = rInf.GetIdx();
639 if( !nLineDiff )
640 return;
641 SwLineLayout *pCurr;
642 if( nLineDiff < 0 )
643 { // The first line has to be adjusted.
644 if( GetTab1() )
645 return;
646 pCurr = &GetRoot();
647 nLineDiff = -nLineDiff;
648 }
649 else
650 { // The second line has to be adjusted.
651 if( GetTab2() )
652 return;
653 pCurr = GetRoot().GetNext();
654 rInf.SetIdx( nOldIdx + GetRoot().GetLen() );
655 }
656 sal_uInt16 nLeft = 0; // the space in front of the first letter
657 sal_uInt16 nRight = 0; // the space at the end of the last letter
658 TextFrameIndex nSub(0);
659 switch ( nAdjustment )
660 {
661 case css::text::RubyAdjust_CENTER: nRight = static_cast<sal_uInt16>(nLineDiff / 2);
662 [[fallthrough]];
663 case css::text::RubyAdjust_RIGHT: nLeft = static_cast<sal_uInt16>(nLineDiff - nRight); break;
664 case css::text::RubyAdjust_BLOCK: nSub = TextFrameIndex(1);
665 [[fallthrough]];
666 case css::text::RubyAdjust_INDENT_BLOCK:
667 {
668 TextFrameIndex nCharCnt(0);
669 SwLinePortion *pPor;
670 for( pPor = pCurr->GetFirstPortion(); pPor; pPor = pPor->GetNextPortion() )
671 {
672 if( pPor->InTextGrp() )
673 static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nCharCnt );
674 rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
675 }
676 if( nCharCnt > nSub )
677 {
678 SwTwips nCalc = nLineDiff / sal_Int32(nCharCnt - nSub);
679 short nTmp;
680 if( nCalc < SHRT_MAX )
681 nTmp = -short(nCalc);
682 else
683 nTmp = SHRT_MIN;
684
685 pCurr->CreateSpaceAdd( SPACING_PRECISION_FACTOR * nTmp );
686 nLineDiff -= nCalc * (sal_Int32(nCharCnt) - 1);
687 }
688 if( nLineDiff > 1 )
689 {
690 nRight = static_cast<sal_uInt16>(nLineDiff / 2);
691 nLeft = static_cast<sal_uInt16>(nLineDiff - nRight);
692 }
693 break;
694 }
695 default: OSL_FAIL( "New ruby adjustment" );
696 }
697 if( nLeft || nRight )
698 {
699 if( !pCurr->GetNextPortion() )
700 pCurr->SetNextPortion(SwTextPortion::CopyLinePortion(*pCurr));
701 if( nLeft )
702 {
703 SwMarginPortion *pMarg = new SwMarginPortion;
704 pMarg->AddPrtWidth( nLeft );
705 pMarg->SetNextPortion( pCurr->GetNextPortion() );
706 pCurr->SetNextPortion( pMarg );
707 }
708 if( nRight )
709 {
710 SwMarginPortion *pMarg = new SwMarginPortion;
711 pMarg->AddPrtWidth( nRight );
712 pCurr->FindLastPortion()->Append( pMarg );
713 }
714 }
715
716 pCurr->Width( Width() );
717 rInf.SetIdx( nOldIdx );
718 }
719
720 // has to change the nRubyOffset, if there's a fieldportion
721 // in the phonetic line.
722 // The nRubyOffset is the position in the rubystring, where the
723 // next SwRubyPortion has start the displaying of the phonetics.
CalcRubyOffset()724 void SwRubyPortion::CalcRubyOffset()
725 {
726 const SwLineLayout *pCurr = &GetRoot();
727 if( !OnTop() )
728 {
729 pCurr = pCurr->GetNext();
730 if( !pCurr )
731 return;
732 }
733 const SwLinePortion *pPor = pCurr->GetFirstPortion();
734 const SwFieldPortion *pField = nullptr;
735 while( pPor )
736 {
737 if( pPor->InFieldGrp() )
738 pField = static_cast<const SwFieldPortion*>(pPor);
739 pPor = pPor->GetNextPortion();
740 }
741 if( pField )
742 {
743 if( pField->HasFollow() )
744 nRubyOffset = pField->GetNextOffset();
745 else
746 nRubyOffset = TextFrameIndex(COMPLETE_STRING);
747 }
748 }
749
750 // A little helper function for GetMultiCreator(..)
751 // It extracts the 2-line-format from a 2-line-attribute or a character style.
752 // The rValue is set to true, if the 2-line-attribute's value is set and
753 // no 2-line-format reference is passed. If there is a 2-line-format reference,
754 // then the rValue is set only, if the 2-line-attribute's value is set _and_
755 // the 2-line-formats has the same brackets.
lcl_Check2Lines(const SfxPoolItem * const pItem,const SvxTwoLinesItem * & rpRef,bool & rValue)756 static bool lcl_Check2Lines(const SfxPoolItem *const pItem,
757 const SvxTwoLinesItem* &rpRef, bool &rValue)
758 {
759 if( pItem )
760 {
761 rValue = static_cast<const SvxTwoLinesItem*>(pItem)->GetValue();
762 if( !rpRef )
763 rpRef = static_cast<const SvxTwoLinesItem*>(pItem);
764 else if( static_cast<const SvxTwoLinesItem*>(pItem)->GetEndBracket() !=
765 rpRef->GetEndBracket() ||
766 static_cast<const SvxTwoLinesItem*>(pItem)->GetStartBracket() !=
767 rpRef->GetStartBracket() )
768 rValue = false;
769 return true;
770 }
771 return false;
772 }
773
lcl_Has2Lines(const SwTextAttr & rAttr,const SvxTwoLinesItem * & rpRef,bool & rValue)774 static bool lcl_Has2Lines(const SwTextAttr& rAttr,
775 const SvxTwoLinesItem* &rpRef, bool &rValue)
776 {
777 const SfxPoolItem* pItem = CharFormat::GetItem(rAttr, RES_CHRATR_TWO_LINES);
778 return lcl_Check2Lines(pItem, rpRef, rValue);
779 }
780
781 // is a little help function for GetMultiCreator(..)
782 // It extracts the charrotation from a charrotate-attribute or a character style.
783 // The rValue is set to true, if the charrotate-attribute's value is set and
784 // no charrotate-format reference is passed.
785 // If there is a charrotate-format reference, then the rValue is set only,
786 // if the charrotate-attribute's value is set _and_ identical
787 // to the charrotate-format's value.
lcl_CheckRotation(const SfxPoolItem * const pItem,const SvxCharRotateItem * & rpRef,bool & rValue)788 static bool lcl_CheckRotation(const SfxPoolItem *const pItem,
789 const SvxCharRotateItem* &rpRef, bool &rValue)
790 {
791 if ( pItem )
792 {
793 rValue = static_cast<const SvxCharRotateItem*>(pItem)->GetValue();
794 if( !rpRef )
795 rpRef = static_cast<const SvxCharRotateItem*>(pItem);
796 else if( static_cast<const SvxCharRotateItem*>(pItem)->GetValue() !=
797 rpRef->GetValue() )
798 rValue = false;
799 return true;
800 }
801
802 return false;
803 }
804
lcl_HasRotation(const SwTextAttr & rAttr,const SvxCharRotateItem * & rpRef,bool & rValue)805 static bool lcl_HasRotation(const SwTextAttr& rAttr,
806 const SvxCharRotateItem* &rpRef, bool &rValue)
807 {
808 const SfxPoolItem* pItem = CharFormat::GetItem( rAttr, RES_CHRATR_ROTATE );
809 return lcl_CheckRotation(pItem, rpRef, rValue);
810 }
811
812 namespace sw {
813
814 // need to use a very special attribute iterator here that returns
815 // both the hints and the nodes, so that GetMultiCreator() can handle
816 // items in the nodes' set properly
817 class MergedAttrIterMulti
818 : public MergedAttrIterBase
819 {
820 private:
821 bool m_First = true;
822 public:
MergedAttrIterMulti(SwTextFrame const & rFrame)823 MergedAttrIterMulti(SwTextFrame const& rFrame) : MergedAttrIterBase(rFrame) {}
824 SwTextAttr const* NextAttr(SwTextNode const*& rpNode);
825 // can't have operator= because m_pMerged/m_pNode const
Assign(MergedAttrIterMulti const & rOther)826 void Assign(MergedAttrIterMulti const& rOther)
827 {
828 assert(m_pMerged == rOther.m_pMerged);
829 assert(m_pNode == rOther.m_pNode);
830 m_CurrentExtent = rOther.m_CurrentExtent;
831 m_CurrentHint = rOther.m_CurrentHint;
832 m_First = rOther.m_First;
833 }
834 };
835
NextAttr(SwTextNode const * & rpNode)836 SwTextAttr const* MergedAttrIterMulti::NextAttr(SwTextNode const*& rpNode)
837 {
838 if (m_First)
839 {
840 m_First = false;
841 rpNode = m_pMerged
842 ? !m_pMerged->extents.empty()
843 ? m_pMerged->extents[0].pNode
844 : m_pMerged->pFirstNode
845 : m_pNode;
846 return nullptr;
847 }
848 if (m_pMerged)
849 {
850 while (m_CurrentExtent < m_pMerged->extents.size())
851 {
852 sw::Extent const& rExtent(m_pMerged->extents[m_CurrentExtent]);
853 if (SwpHints const*const pHints = rExtent.pNode->GetpSwpHints())
854 {
855 while (m_CurrentHint < pHints->Count())
856 {
857 SwTextAttr const*const pHint(pHints->Get(m_CurrentHint));
858 if (rExtent.nEnd < pHint->GetStart())
859 {
860 break;
861 }
862 ++m_CurrentHint;
863 if (rExtent.nStart <= pHint->GetStart())
864 {
865 rpNode = rExtent.pNode;
866 return pHint;
867 }
868 }
869 }
870 ++m_CurrentExtent;
871 if (m_CurrentExtent < m_pMerged->extents.size() &&
872 rExtent.pNode != m_pMerged->extents[m_CurrentExtent].pNode)
873 {
874 m_CurrentHint = 0; // reset
875 rpNode = m_pMerged->extents[m_CurrentExtent].pNode;
876 return nullptr;
877 }
878 }
879 return nullptr;
880 }
881 else
882 {
883 SwpHints const*const pHints(m_pNode->GetpSwpHints());
884 if (pHints)
885 {
886 if (m_CurrentHint < pHints->Count())
887 {
888 SwTextAttr const*const pHint(pHints->Get(m_CurrentHint));
889 ++m_CurrentHint;
890 rpNode = m_pNode;
891 return pHint;
892 }
893 }
894 return nullptr;
895 }
896 }
897 }
898
899 // If we (e.g. the position rPos) are inside a two-line-attribute or
900 // a ruby-attribute, the attribute will be returned in a SwMultiCreator-struct,
901 // otherwise the function returns zero.
902 // The rPos parameter is set to the end of the multiportion,
903 // normally this is the end of the attribute,
904 // but sometimes it is the start of another attribute, which finished or
905 // interrupts the first attribute.
906 // E.g. a ruby portion interrupts a 2-line-attribute, a 2-line-attribute
907 // with different brackets interrupts another 2-line-attribute.
GetMultiCreator(TextFrameIndex & rPos,SwMultiPortion const * pMulti) const908 std::unique_ptr<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex &rPos,
909 SwMultiPortion const * pMulti ) const
910 {
911 SwScriptInfo& rSI = const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo();
912
913 // get the last embedding level
914 sal_uInt8 nCurrLevel;
915 if ( pMulti )
916 {
917 OSL_ENSURE( pMulti->IsBidi(), "Nested MultiPortion is not BidiPortion" );
918 // level associated with bidi-portion;
919 nCurrLevel = static_cast<SwBidiPortion const *>(pMulti)->GetLevel();
920 }
921 else
922 // no nested bidi portion required
923 nCurrLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
924
925 // check if there is a field at rPos:
926 sal_uInt8 nNextLevel = nCurrLevel;
927 bool bFieldBidi = false;
928
929 if (rPos < TextFrameIndex(GetText().getLength()) && CH_TXTATR_BREAKWORD == GetChar(rPos))
930 {
931 bFieldBidi = true;
932 }
933 else
934 nNextLevel = rSI.DirType( rPos );
935
936 if (TextFrameIndex(GetText().getLength()) != rPos && nNextLevel > nCurrLevel)
937 {
938 rPos = bFieldBidi ? rPos + TextFrameIndex(1) : rSI.NextDirChg(rPos, &nCurrLevel);
939 if (TextFrameIndex(COMPLETE_STRING) == rPos)
940 return nullptr;
941 std::unique_ptr<SwMultiCreator> pRet(new SwMultiCreator);
942 pRet->pItem = nullptr;
943 pRet->pAttr = nullptr;
944 pRet->nStartOfAttr = TextFrameIndex(-1);
945 pRet->nId = SwMultiCreatorId::Bidi;
946 pRet->nLevel = nCurrLevel + 1;
947 return pRet;
948 }
949
950 // a bidi portion can only contain other bidi portions
951 if ( pMulti )
952 return nullptr;
953
954 // need the node that contains input rPos
955 std::pair<SwTextNode const*, sal_Int32> startPos(m_pFrame->MapViewToModel(rPos));
956 const SvxCharRotateItem* pActiveRotateItem(nullptr);
957 const SfxPoolItem* pNodeRotateItem(nullptr);
958 const SvxTwoLinesItem* pActiveTwoLinesItem(nullptr);
959 const SfxPoolItem* pNodeTwoLinesItem(nullptr);
960 SwTextAttr const* pActiveTwoLinesHint(nullptr);
961 SwTextAttr const* pActiveRotateHint(nullptr);
962 const SwTextAttr *pRuby = nullptr;
963 sw::MergedAttrIterMulti iterAtStartOfNode(*m_pFrame);
964 bool bTwo = false;
965 bool bRot = false;
966
967 for (sw::MergedAttrIterMulti iter = *m_pFrame; ; )
968 {
969 SwTextNode const* pNode(nullptr);
970 SwTextAttr const*const pAttr = iter.NextAttr(pNode);
971 if (!pNode)
972 {
973 break;
974 }
975 if (pAttr)
976 {
977 assert(pNode->GetIndex() <= startPos.first->GetIndex()); // should break earlier
978 if (startPos.first->GetIndex() <= pNode->GetIndex())
979 {
980 if (startPos.first->GetIndex() != pNode->GetIndex()
981 || startPos.second < pAttr->GetStart())
982 {
983 break;
984 }
985 if (startPos.second < pAttr->GetAnyEnd())
986 {
987 // sw_redlinehide: ruby *always* splits
988 if (RES_TXTATR_CJK_RUBY == pAttr->Which())
989 pRuby = pAttr;
990 else
991 {
992 const SvxCharRotateItem* pRoTmp = nullptr;
993 if (lcl_HasRotation( *pAttr, pRoTmp, bRot ))
994 {
995 pActiveRotateHint = bRot ? pAttr : nullptr;
996 pActiveRotateItem = pRoTmp;
997 }
998 const SvxTwoLinesItem* p2Tmp = nullptr;
999 if (lcl_Has2Lines( *pAttr, p2Tmp, bTwo ))
1000 {
1001 pActiveTwoLinesHint = bTwo ? pAttr : nullptr;
1002 pActiveTwoLinesItem = p2Tmp;
1003 }
1004 }
1005 }
1006 }
1007 }
1008 else if (pNode) // !pAttr && pNode means the node changed
1009 {
1010 if (startPos.first->GetIndex() < pNode->GetIndex())
1011 {
1012 break; // only one node initially
1013 }
1014 if (startPos.first->GetIndex() == pNode->GetIndex())
1015 {
1016 iterAtStartOfNode.Assign(iter);
1017 if (SfxItemState::SET == pNode->GetSwAttrSet().GetItemState(
1018 RES_CHRATR_ROTATE, true, &pNodeRotateItem) &&
1019 static_cast<const SvxCharRotateItem*>(pNodeRotateItem)->GetValue())
1020 {
1021 pActiveRotateItem = static_cast<const SvxCharRotateItem*>(pNodeRotateItem);
1022 }
1023 else
1024 {
1025 pNodeRotateItem = nullptr;
1026 }
1027 if (SfxItemState::SET == startPos.first->GetSwAttrSet().GetItemState(
1028 RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem) &&
1029 static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem)->GetValue())
1030 {
1031 pActiveTwoLinesItem = static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem);
1032 }
1033 else
1034 {
1035 pNodeTwoLinesItem = nullptr;
1036 }
1037 }
1038 }
1039 }
1040 if (!pRuby && !pActiveTwoLinesItem && !pActiveRotateItem)
1041 return nullptr;
1042
1043 if( pRuby )
1044 { // The winner is ... a ruby attribute and so
1045 // the end of the multiportion is the end of the ruby attribute.
1046 rPos = m_pFrame->MapModelToView(startPos.first, *pRuby->End());
1047 std::unique_ptr<SwMultiCreator> pRet(new SwMultiCreator);
1048 pRet->pItem = nullptr;
1049 pRet->pAttr = pRuby;
1050 pRet->nStartOfAttr = m_pFrame->MapModelToView(startPos.first, pRet->pAttr->GetStart());
1051 pRet->nId = SwMultiCreatorId::Ruby;
1052 pRet->nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
1053 return pRet;
1054 }
1055 if (pActiveTwoLinesHint ||
1056 (pNodeTwoLinesItem && pNodeTwoLinesItem == pActiveTwoLinesItem &&
1057 rPos < TextFrameIndex(GetText().getLength())))
1058 { // The winner is a 2-line-attribute,
1059 // the end of the multiportion depends on the following attributes...
1060 std::unique_ptr<SwMultiCreator> pRet(new SwMultiCreator);
1061
1062 // We note the endpositions of the 2-line attributes in aEnd as stack
1063 std::deque<TextFrameIndex> aEnd;
1064
1065 // The bOn flag signs the state of the last 2-line attribute in the
1066 // aEnd-stack, it is compatible with the winner-attribute or
1067 // it interrupts the other attribute.
1068 bool bOn = true;
1069
1070 if (pActiveTwoLinesHint)
1071 {
1072 pRet->pItem = nullptr;
1073 pRet->pAttr = pActiveTwoLinesHint;
1074 pRet->nStartOfAttr = m_pFrame->MapModelToView(startPos.first, pRet->pAttr->GetStart());
1075 if (pNodeTwoLinesItem)
1076 {
1077 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
1078 bOn = static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem)->GetEndBracket() ==
1079 pActiveTwoLinesItem->GetEndBracket() &&
1080 static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem)->GetStartBracket() ==
1081 pActiveTwoLinesItem->GetStartBracket();
1082 }
1083 else
1084 {
1085 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *pRet->pAttr->End()));
1086 }
1087 }
1088 else
1089 {
1090 pRet->pItem = pNodeTwoLinesItem;
1091 pRet->pAttr = nullptr;
1092 pRet->nStartOfAttr = TextFrameIndex(-1);
1093 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
1094 }
1095 pRet->nId = SwMultiCreatorId::Double;
1096 pRet->nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
1097
1098 // pActiveTwoLinesHint is the last 2-line-attribute, which contains
1099 // the actual position.
1100
1101 // At this moment we know that at position rPos the "winner"-attribute
1102 // causes a 2-line-portion. The end of the attribute is the end of the
1103 // portion, if there's no interrupting attribute.
1104 // There are two kinds of interrupters:
1105 // - ruby attributes stops the 2-line-attribute, the end of the
1106 // multiline is the start of the ruby attribute
1107 // - 2-line-attributes with value "Off" or with different brackets,
1108 // these attributes may interrupt the winner, but they could be
1109 // neutralized by another 2-line-attribute starting at the same
1110 // position with the same brackets as the winner-attribute.
1111
1112 // In the following loop rPos is the critical position and it will be
1113 // evaluated, if at rPos starts an interrupting or a maintaining
1114 // continuity attribute.
1115
1116 // iterAtStartOfNode is positioned to the first hint of the node
1117 // (if any); the node item itself has already been handled above
1118 for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
1119 {
1120 SwTextNode const* pNode(nullptr);
1121 SwTextAttr const*const pTmp = iter.NextAttr(pNode);
1122 if (!pNode)
1123 {
1124 break;
1125 }
1126 assert(startPos.first->GetIndex() <= pNode->GetIndex());
1127 TextFrameIndex nTmpStart;
1128 TextFrameIndex nTmpEnd;
1129 if (pTmp)
1130 {
1131 nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
1132 if (nTmpEnd <= rPos)
1133 continue;
1134 nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
1135 }
1136 else
1137 {
1138 pNodeTwoLinesItem = nullptr;
1139 pNode->GetSwAttrSet().GetItemState(
1140 RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem);
1141 nTmpStart = m_pFrame->MapModelToView(pNode, 0);
1142 nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
1143 assert(rPos <= nTmpEnd); // next node must not have smaller index
1144 }
1145
1146 if (rPos < nTmpStart)
1147 {
1148 // If bOn is false and the next attribute starts later than rPos
1149 // the winner attribute is interrupted at rPos.
1150 // If the start of the next attribute is behind the end of
1151 // the last attribute on the aEnd-stack, this is the endposition
1152 // on the stack is the end of the 2-line portion.
1153 if (!bOn || aEnd.back() < nTmpStart)
1154 break;
1155 // At this moment, bOn is true and the next attribute starts
1156 // behind rPos, so we could move rPos to the next startpoint
1157 rPos = nTmpStart;
1158 // We clean up the aEnd-stack, endpositions equal to rPos are
1159 // superfluous.
1160 while( !aEnd.empty() && aEnd.back() <= rPos )
1161 {
1162 bOn = !bOn;
1163 aEnd.pop_back();
1164 }
1165 // If the endstack is empty, we simulate an attribute with
1166 // state true and endposition rPos
1167 if( aEnd.empty() )
1168 {
1169 aEnd.push_front( rPos );
1170 bOn = true;
1171 }
1172 }
1173 // A ruby attribute stops the 2-line immediately
1174 if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
1175 return pRet;
1176 if (pTmp ? lcl_Has2Lines(*pTmp, pActiveTwoLinesItem, bTwo)
1177 : lcl_Check2Lines(pNodeTwoLinesItem, pActiveTwoLinesItem, bTwo))
1178 { // We have an interesting attribute...
1179 if( bTwo == bOn )
1180 { // .. with the same state, so the last attribute could
1181 // be continued.
1182 if (aEnd.back() < nTmpEnd)
1183 aEnd.back() = nTmpEnd;
1184 }
1185 else
1186 { // .. with a different state.
1187 bOn = bTwo;
1188 // If this is smaller than the last on the stack, we put
1189 // it on the stack. If it has the same endposition, the last
1190 // could be removed.
1191 if (nTmpEnd < aEnd.back())
1192 aEnd.push_back( nTmpEnd );
1193 else if( aEnd.size() > 1 )
1194 aEnd.pop_back();
1195 else
1196 aEnd.back() = nTmpEnd;
1197 }
1198 }
1199 }
1200 if( bOn && !aEnd.empty() )
1201 rPos = aEnd.back();
1202 return pRet;
1203 }
1204 if (pActiveRotateHint ||
1205 (pNodeRotateItem && pNodeRotateItem == pActiveRotateItem &&
1206 rPos < TextFrameIndex(GetText().getLength())))
1207 { // The winner is a rotate-attribute,
1208 // the end of the multiportion depends on the following attributes...
1209 std::unique_ptr<SwMultiCreator> pRet(new SwMultiCreator);
1210 pRet->nId = SwMultiCreatorId::Rotate;
1211
1212 // We note the endpositions of the 2-line attributes in aEnd as stack
1213 std::deque<TextFrameIndex> aEnd;
1214
1215 // The bOn flag signs the state of the last 2-line attribute in the
1216 // aEnd-stack, which could interrupts the winning rotation attribute.
1217 bool bOn = pNodeTwoLinesItem != nullptr;
1218 aEnd.push_front(TextFrameIndex(GetText().getLength()));
1219
1220 // first, search for the start position of the next TWOLINE portion
1221 // because the ROTATE portion must end there at the latest
1222 TextFrameIndex n2Start = rPos;
1223 for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
1224 {
1225 SwTextNode const* pNode(nullptr);
1226 SwTextAttr const*const pTmp = iter.NextAttr(pNode);
1227 if (!pNode)
1228 {
1229 break;
1230 }
1231 assert(startPos.first->GetIndex() <= pNode->GetIndex());
1232 TextFrameIndex nTmpStart;
1233 TextFrameIndex nTmpEnd;
1234 if (pTmp)
1235 {
1236 nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
1237 if (nTmpEnd <= n2Start)
1238 continue;
1239 nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
1240 }
1241 else
1242 {
1243 pNodeTwoLinesItem = nullptr;
1244 pNode->GetSwAttrSet().GetItemState(
1245 RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem);
1246 nTmpStart = m_pFrame->MapModelToView(pNode, 0);
1247 nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
1248 assert(n2Start <= nTmpEnd); // next node must not have smaller index
1249 }
1250
1251 if (n2Start < nTmpStart)
1252 {
1253 if (bOn || aEnd.back() < nTmpStart)
1254 break;
1255 n2Start = nTmpStart;
1256 while( !aEnd.empty() && aEnd.back() <= n2Start )
1257 {
1258 bOn = !bOn;
1259 aEnd.pop_back();
1260 }
1261 if( aEnd.empty() )
1262 {
1263 aEnd.push_front( n2Start );
1264 bOn = false;
1265 }
1266 }
1267 // A ruby attribute stops immediately
1268 if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
1269 {
1270 bOn = true;
1271 break;
1272 }
1273 const SvxTwoLinesItem* p2Lines = nullptr;
1274 if (pTmp ? lcl_Has2Lines(*pTmp, p2Lines, bTwo)
1275 : lcl_Check2Lines(pNodeTwoLinesItem, p2Lines, bTwo))
1276 {
1277 if( bTwo == bOn )
1278 {
1279 if (aEnd.back() < nTmpEnd)
1280 aEnd.back() = nTmpEnd;
1281 }
1282 else
1283 {
1284 bOn = bTwo;
1285 if (nTmpEnd < aEnd.back())
1286 aEnd.push_back( nTmpEnd );
1287 else if( aEnd.size() > 1 )
1288 aEnd.pop_back();
1289 else
1290 aEnd.back() = nTmpEnd;
1291 }
1292 }
1293 }
1294 if( !bOn && !aEnd.empty() )
1295 n2Start = aEnd.back();
1296
1297 aEnd.clear();
1298
1299 // now, search for the end of the ROTATE portion, similar to above
1300 bOn = true;
1301 if (pActiveRotateHint)
1302 {
1303 pRet->pItem = nullptr;
1304 pRet->pAttr = pActiveRotateHint;
1305 pRet->nStartOfAttr = m_pFrame->MapModelToView(startPos.first, pRet->pAttr->GetStart());
1306 if (pNodeRotateItem)
1307 {
1308 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
1309 bOn = static_cast<const SvxCharRotateItem*>(pNodeRotateItem)->GetValue() ==
1310 pActiveRotateItem->GetValue();
1311 }
1312 else
1313 {
1314 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *pRet->pAttr->End()));
1315 }
1316 }
1317 else
1318 {
1319 pRet->pItem = pNodeRotateItem;
1320 pRet->pAttr = nullptr;
1321 pRet->nStartOfAttr = TextFrameIndex(-1);
1322 aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
1323 }
1324 for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
1325 {
1326 SwTextNode const* pNode(nullptr);
1327 SwTextAttr const*const pTmp = iter.NextAttr(pNode);
1328 if (!pNode)
1329 {
1330 break;
1331 }
1332 assert(startPos.first->GetIndex() <= pNode->GetIndex());
1333 TextFrameIndex nTmpStart;
1334 TextFrameIndex nTmpEnd;
1335 if (pTmp)
1336 {
1337 nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
1338 if (nTmpEnd <= rPos)
1339 continue;
1340 nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
1341 }
1342 else
1343 {
1344 pNodeRotateItem = nullptr;
1345 pNode->GetSwAttrSet().GetItemState(
1346 RES_CHRATR_ROTATE, true, &pNodeRotateItem);
1347 nTmpStart = m_pFrame->MapModelToView(pNode, 0);
1348 nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
1349 assert(rPos <= nTmpEnd); // next node must not have smaller index
1350 }
1351
1352 if (rPos < nTmpStart)
1353 {
1354 if (!bOn || aEnd.back() < nTmpStart)
1355 break;
1356 rPos = nTmpStart;
1357 while( !aEnd.empty() && aEnd.back() <= rPos )
1358 {
1359 bOn = !bOn;
1360 aEnd.pop_back();
1361 }
1362 if( aEnd.empty() )
1363 {
1364 aEnd.push_front( rPos );
1365 bOn = true;
1366 }
1367 }
1368 if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
1369 {
1370 bOn = false;
1371 break;
1372 }
1373 // TODO why does this use bTwo, not bRot ???
1374 if (pTmp ? lcl_HasRotation(*pTmp, pActiveRotateItem, bTwo)
1375 : lcl_CheckRotation(pNodeRotateItem, pActiveRotateItem, bTwo))
1376 {
1377 if( bTwo == bOn )
1378 {
1379 if (aEnd.back() < nTmpEnd)
1380 aEnd.back() = nTmpEnd;
1381 }
1382 else
1383 {
1384 bOn = bTwo;
1385 if (nTmpEnd < aEnd.back())
1386 aEnd.push_back( nTmpEnd );
1387 else if( aEnd.size() > 1 )
1388 aEnd.pop_back();
1389 else
1390 aEnd.back() = nTmpEnd;
1391 }
1392 }
1393 }
1394 if( bOn && !aEnd.empty() )
1395 rPos = aEnd.back();
1396 if( rPos > n2Start )
1397 rPos = n2Start;
1398 return pRet;
1399 }
1400 return nullptr;
1401 }
1402
1403 // A little helper class to manage the spaceadd-arrays of the text adjustment
1404 // during a PaintMultiPortion.
1405 // The constructor prepares the array for the first line of multiportion,
1406 // the SecondLine-function restores the values for the first line and prepares
1407 // the second line.
1408 // The destructor restores the values of the last manipulation.
1409 class SwSpaceManipulator
1410 {
1411 SwTextPaintInfo& rInfo;
1412 SwMultiPortion& rMulti;
1413 std::vector<long>* pOldSpaceAdd;
1414 sal_uInt16 nOldSpIdx;
1415 long nSpaceAdd;
1416 bool bSpaceChg;
1417 sal_uInt8 nOldDir;
1418 public:
1419 SwSpaceManipulator( SwTextPaintInfo& rInf, SwMultiPortion& rMult );
1420 ~SwSpaceManipulator();
1421 void SecondLine();
GetSpaceAdd() const1422 long GetSpaceAdd() const { return nSpaceAdd; }
1423 };
1424
SwSpaceManipulator(SwTextPaintInfo & rInf,SwMultiPortion & rMult)1425 SwSpaceManipulator::SwSpaceManipulator( SwTextPaintInfo& rInf,
1426 SwMultiPortion& rMult )
1427 : rInfo(rInf)
1428 , rMulti(rMult)
1429 , nSpaceAdd(0)
1430 {
1431 pOldSpaceAdd = rInfo.GetpSpaceAdd();
1432 nOldSpIdx = rInfo.GetSpaceIdx();
1433 nOldDir = rInfo.GetDirection();
1434 rInfo.SetDirection( rMulti.GetDirection() );
1435 bSpaceChg = false;
1436
1437 if( rMulti.IsDouble() )
1438 {
1439 nSpaceAdd = ( pOldSpaceAdd && !rMulti.HasTabulator() ) ?
1440 rInfo.GetSpaceAdd() : 0;
1441 if( rMulti.GetRoot().IsSpaceAdd() )
1442 {
1443 rInfo.SetpSpaceAdd( rMulti.GetRoot().GetpLLSpaceAdd() );
1444 rInfo.ResetSpaceIdx();
1445 bSpaceChg = rMulti.ChgSpaceAdd( &rMulti.GetRoot(), nSpaceAdd );
1446 }
1447 else if( rMulti.HasTabulator() )
1448 rInfo.SetpSpaceAdd( nullptr );
1449 }
1450 else if ( ! rMulti.IsBidi() )
1451 {
1452 rInfo.SetpSpaceAdd( rMulti.GetRoot().GetpLLSpaceAdd() );
1453 rInfo.ResetSpaceIdx();
1454 }
1455 }
1456
SecondLine()1457 void SwSpaceManipulator::SecondLine()
1458 {
1459 if( bSpaceChg )
1460 {
1461 rInfo.RemoveFirstSpaceAdd();
1462 bSpaceChg = false;
1463 }
1464 SwLineLayout *pLay = rMulti.GetRoot().GetNext();
1465 if( pLay->IsSpaceAdd() )
1466 {
1467 rInfo.SetpSpaceAdd( pLay->GetpLLSpaceAdd() );
1468 rInfo.ResetSpaceIdx();
1469 bSpaceChg = rMulti.ChgSpaceAdd( pLay, nSpaceAdd );
1470 }
1471 else
1472 {
1473 rInfo.SetpSpaceAdd( (!rMulti.IsDouble() || rMulti.HasTabulator() ) ?
1474 nullptr : pOldSpaceAdd );
1475 rInfo.SetSpaceIdx( nOldSpIdx);
1476 }
1477 }
1478
~SwSpaceManipulator()1479 SwSpaceManipulator::~SwSpaceManipulator()
1480 {
1481 if( bSpaceChg )
1482 {
1483 rInfo.RemoveFirstSpaceAdd();
1484 bSpaceChg = false;
1485 }
1486 rInfo.SetpSpaceAdd( pOldSpaceAdd );
1487 rInfo.SetSpaceIdx( nOldSpIdx);
1488 rInfo.SetDirection( nOldDir );
1489 }
1490
1491 // Manages the paint for a SwMultiPortion.
1492 // External, for the calling function, it seems to be a normal Paint-function,
1493 // internal it is like a SwTextFrame::PaintSwFrame with multiple DrawTextLines
PaintMultiPortion(const SwRect & rPaint,SwMultiPortion & rMulti,const SwMultiPortion * pEnvPor)1494 void SwTextPainter::PaintMultiPortion( const SwRect &rPaint,
1495 SwMultiPortion& rMulti, const SwMultiPortion* pEnvPor )
1496 {
1497 SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
1498 const bool bHasGrid = pGrid && GetInfo().SnapToGrid();
1499 sal_uInt16 nRubyHeight = 0;
1500 bool bRubyTop = true;
1501
1502 if ( bHasGrid && pGrid->IsSquaredMode() )
1503 {
1504 nRubyHeight = pGrid->GetRubyHeight();
1505 bRubyTop = ! pGrid->GetRubyTextBelow();
1506 }
1507
1508 // do not allow grid mode for first line in ruby portion
1509 const bool bRubyInGrid = bHasGrid && rMulti.IsRuby();
1510
1511 const sal_uInt16 nOldHeight = rMulti.Height();
1512 const bool bOldGridModeAllowed = GetInfo().SnapToGrid();
1513
1514 if ( bRubyInGrid )
1515 {
1516 GetInfo().SetSnapToGrid( ! bRubyTop );
1517 if (pGrid->IsSquaredMode())
1518 rMulti.Height( m_pCurr->Height() );
1519 }
1520
1521 SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() );
1522 bool bEnvDir = false;
1523 bool bThisDir = false;
1524 bool bFrameDir = false;
1525 if ( rMulti.IsBidi() )
1526 {
1527 // these values are needed for the calculation of the x coordinate
1528 // and the layout mode
1529 OSL_ENSURE( ! pEnvPor || pEnvPor->IsBidi(),
1530 "Oh no, I expected a BidiPortion" );
1531 bFrameDir = GetInfo().GetTextFrame()->IsRightToLeft();
1532 bEnvDir = pEnvPor ? ((static_cast<const SwBidiPortion*>(pEnvPor)->GetLevel() % 2) != 0) : bFrameDir;
1533 bThisDir = (static_cast<SwBidiPortion&>(rMulti).GetLevel() % 2) != 0;
1534 }
1535
1536 #if OSL_DEBUG_LEVEL > 1
1537 // only paint first level bidi portions
1538 if( rMulti.Width() > 1 && ! pEnvPor )
1539 GetInfo().DrawViewOpt( rMulti, PortionType::Field );
1540 #endif
1541
1542 if ( bRubyInGrid && pGrid->IsSquaredMode() )
1543 rMulti.Height( nOldHeight );
1544
1545 // do we have to repaint a post it portion?
1546 if( GetInfo().OnWin() && rMulti.GetNextPortion() &&
1547 ! rMulti.GetNextPortion()->Width() )
1548 rMulti.GetNextPortion()->PrePaint( GetInfo(), &rMulti );
1549
1550 // old values must be saved and restored at the end
1551 TextFrameIndex const nOldLen = GetInfo().GetLen();
1552 const SwTwips nOldX = GetInfo().X();
1553 const SwTwips nOldY = GetInfo().Y();
1554 TextFrameIndex const nOldIdx = GetInfo().GetIdx();
1555
1556 SwSpaceManipulator aManip( GetInfo(), rMulti );
1557
1558 std::unique_ptr<SwFontSave> pFontSave;
1559 std::unique_ptr<SwFont> pTmpFnt;
1560
1561 if( rMulti.IsDouble() )
1562 {
1563 pTmpFnt.reset(new SwFont( *GetInfo().GetFont() ));
1564 if( rMulti.IsDouble() )
1565 {
1566 SetPropFont( 50 );
1567 pTmpFnt->SetProportion( GetPropFont() );
1568 }
1569 pFontSave.reset(new SwFontSave( GetInfo(), pTmpFnt.get(), this ));
1570 }
1571 else
1572 {
1573 pFontSave = nullptr;
1574 pTmpFnt = nullptr;
1575 }
1576
1577 if( rMulti.HasBrackets() )
1578 {
1579 TextFrameIndex const nTmpOldIdx = GetInfo().GetIdx();
1580 GetInfo().SetIdx(static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart);
1581 SeekAndChg( GetInfo() );
1582 static_cast<SwDoubleLinePortion&>(rMulti).PaintBracket( GetInfo(), 0, true );
1583 GetInfo().SetIdx( nTmpOldIdx );
1584 }
1585
1586 const SwTwips nTmpX = GetInfo().X();
1587
1588 SwLineLayout* pLay = &rMulti.GetRoot();// the first line of the multiportion
1589 SwLinePortion* pPor = pLay->GetFirstPortion();//first portion of these line
1590 SwTwips nOfst = 0;
1591
1592 // GetInfo().Y() is the baseline from the surrounding line. We must switch
1593 // this temporary to the baseline of the inner lines of the multiportion.
1594 if( rMulti.HasRotation() )
1595 {
1596 if( rMulti.IsRevers() )
1597 {
1598 GetInfo().Y( nOldY - rMulti.GetAscent() );
1599 nOfst = nTmpX + rMulti.Width();
1600 }
1601 else
1602 {
1603 GetInfo().Y( nOldY - rMulti.GetAscent() + rMulti.Height() );
1604 nOfst = nTmpX;
1605 }
1606 }
1607 else if ( rMulti.IsBidi() )
1608 {
1609 // does the current bidi portion has the same direction
1610 // as its environment?
1611 if ( bEnvDir != bThisDir )
1612 {
1613 // different directions, we have to adjust the x coordinate
1614 SwTwips nMultiWidth = rMulti.Width() +
1615 rMulti.CalcSpacing( GetInfo().GetSpaceAdd(), GetInfo() );
1616
1617 if ( bFrameDir == bThisDir )
1618 GetInfo().X( GetInfo().X() - nMultiWidth );
1619 else
1620 GetInfo().X( GetInfo().X() + nMultiWidth );
1621 }
1622
1623 nOfst = nOldY - rMulti.GetAscent();
1624
1625 // set layout mode
1626 aLayoutModeModifier.Modify( bThisDir );
1627 }
1628 else
1629 nOfst = nOldY - rMulti.GetAscent();
1630
1631 bool bRest = pLay->IsRest();
1632 bool bFirst = true;
1633
1634 OSL_ENSURE( nullptr == GetInfo().GetUnderFnt() || rMulti.IsBidi(),
1635 " Only BiDi portions are allowed to use the common underlining font" );
1636
1637 if ( rMulti.IsRuby() )
1638 GetInfo().SetRuby( rMulti.OnTop() );
1639
1640 do
1641 {
1642 if ( bHasGrid && pGrid->IsSquaredMode() )
1643 {
1644 if( rMulti.HasRotation() )
1645 {
1646 const sal_uInt16 nAdjustment = ( pLay->Height() - pPor->Height() ) / 2 +
1647 pPor->GetAscent();
1648 if( rMulti.IsRevers() )
1649 GetInfo().X( nOfst - nAdjustment );
1650 else
1651 GetInfo().X( nOfst + nAdjustment );
1652 }
1653 else
1654 {
1655 // special treatment for ruby portions in grid mode
1656 SwTwips nAdjustment = 0;
1657 if ( rMulti.IsRuby() )
1658 {
1659 if ( bRubyTop != ( pLay == &rMulti.GetRoot() ) )
1660 // adjust base text
1661 nAdjustment = ( m_pCurr->Height() - nRubyHeight - pPor->Height() ) / 2;
1662 else if ( bRubyTop )
1663 // adjust upper ruby text
1664 nAdjustment = nRubyHeight - pPor->Height();
1665 // else adjust lower ruby text
1666 }
1667
1668 GetInfo().Y( nOfst + nAdjustment + pPor->GetAscent() );
1669 }
1670 }
1671 else if( rMulti.HasRotation() )
1672 {
1673 if( rMulti.IsRevers() )
1674 GetInfo().X( nOfst - AdjustBaseLine( *pLay, pPor, 0, 0, true ) );
1675 else
1676 GetInfo().X( nOfst + AdjustBaseLine( *pLay, pPor ) );
1677 }
1678 else if ( rMulti.IsRuby() && rMulti.OnRight() && GetInfo().IsRuby() )
1679 {
1680 SwTwips nLineDiff = std::max(( rMulti.GetRoot().Height() - pPor->Width() ) / 2, 0 );
1681 GetInfo().Y( nOfst + nLineDiff );
1682 // Draw the ruby text on top of the preserved space.
1683 GetInfo().X( GetInfo().X() - pPor->Height() );
1684 }
1685 else
1686 GetInfo().Y( nOfst + AdjustBaseLine( *pLay, pPor ) );
1687
1688 bool bSeeked = true;
1689 GetInfo().SetLen( pPor->GetLen() );
1690
1691 if( bRest && pPor->InFieldGrp() && !pPor->GetLen() )
1692 {
1693 if( static_cast<SwFieldPortion*>(pPor)->HasFont() )
1694 bSeeked = false;
1695 else
1696 SeekAndChgBefore( GetInfo() );
1697 }
1698 else if( pPor->InTextGrp() || pPor->InFieldGrp() || pPor->InTabGrp() )
1699 SeekAndChg( GetInfo() );
1700 else if ( !bFirst && pPor->IsBreakPortion() && GetInfo().GetOpt().IsParagraph() )
1701 {
1702 if( GetRedln() )
1703 SeekAndChg( GetInfo() );
1704 else
1705 SeekAndChgBefore( GetInfo() );
1706 }
1707 else
1708 bSeeked = false;
1709
1710 SwLinePortion *pNext = pPor->GetNextPortion();
1711 if(GetInfo().OnWin() && pNext && !pNext->Width() )
1712 {
1713 if ( !bSeeked )
1714 SeekAndChg( GetInfo() );
1715 pNext->PrePaint( GetInfo(), pPor );
1716 }
1717
1718 CheckSpecialUnderline( pPor );
1719 SwUnderlineFont* pUnderLineFnt = GetInfo().GetUnderFnt();
1720 if ( pUnderLineFnt )
1721 {
1722 if ( rMulti.IsDouble() )
1723 pUnderLineFnt->GetFont().SetProportion( 50 );
1724 pUnderLineFnt->SetPos( GetInfo().GetPos() );
1725 }
1726
1727 if ( rMulti.IsBidi() )
1728 {
1729 // we do not allow any rotation inside a bidi portion
1730 SwFont* pTmpFont = GetInfo().GetFont();
1731 pTmpFont->SetVertical( 0, GetInfo().GetTextFrame()->IsVertical() );
1732 }
1733
1734 if( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() )
1735 {
1736 // but we do allow nested bidi portions
1737 OSL_ENSURE( rMulti.IsBidi(), "Only nesting of bidi portions is allowed" );
1738 PaintMultiPortion( rPaint, static_cast<SwMultiPortion&>(*pPor), &rMulti );
1739 }
1740 else
1741 pPor->Paint( GetInfo() );
1742
1743 bFirst &= !pPor->GetLen();
1744 if( pNext || !pPor->IsMarginPortion() )
1745 pPor->Move( GetInfo() );
1746
1747 pPor = pNext;
1748
1749 // If there's no portion left, we go to the next line
1750 if( !pPor && pLay->GetNext() )
1751 {
1752 pLay = pLay->GetNext();
1753 pPor = pLay->GetFirstPortion();
1754 bRest = pLay->IsRest();
1755 aManip.SecondLine();
1756
1757 // delete underline font
1758 delete GetInfo().GetUnderFnt();
1759 GetInfo().SetUnderFnt( nullptr );
1760
1761 if( rMulti.HasRotation() )
1762 {
1763 if( rMulti.IsRevers() )
1764 {
1765 nOfst += pLay->Height();
1766 GetInfo().Y( nOldY - rMulti.GetAscent() );
1767 }
1768 else
1769 {
1770 nOfst -= pLay->Height();
1771 GetInfo().Y( nOldY - rMulti.GetAscent() + rMulti.Height() );
1772 }
1773 }
1774 else if ( bHasGrid && rMulti.IsRuby() )
1775 {
1776 GetInfo().SetSnapToGrid( bRubyTop );
1777 GetInfo().X( nTmpX );
1778 if (pGrid->IsSquaredMode() )
1779 {
1780 if ( bRubyTop )
1781 nOfst += nRubyHeight;
1782 else
1783 nOfst += m_pCurr->Height() - nRubyHeight;
1784 }
1785 else
1786 {
1787 nOfst += rMulti.GetRoot().Height();
1788 }
1789 }
1790 else if ( rMulti.IsRuby() && rMulti.OnRight() )
1791 {
1792 GetInfo().SetDirection( DIR_TOP2BOTTOM );
1793 GetInfo().SetRuby( true );
1794 } else
1795 {
1796 GetInfo().X( nTmpX );
1797 // We switch to the baseline of the next inner line
1798 nOfst += rMulti.GetRoot().Height();
1799 }
1800 }
1801 } while( pPor );
1802
1803 if ( bRubyInGrid )
1804 GetInfo().SetSnapToGrid( bOldGridModeAllowed );
1805
1806 // delete underline font
1807 if ( ! rMulti.IsBidi() )
1808 {
1809 delete GetInfo().GetUnderFnt();
1810 GetInfo().SetUnderFnt( nullptr );
1811 }
1812
1813 GetInfo().SetIdx( nOldIdx );
1814 GetInfo().Y( nOldY );
1815
1816 if( rMulti.HasBrackets() )
1817 {
1818 TextFrameIndex const nTmpOldIdx = GetInfo().GetIdx();
1819 GetInfo().SetIdx(static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart);
1820 SeekAndChg( GetInfo() );
1821 GetInfo().X( nOldX );
1822 static_cast<SwDoubleLinePortion&>(rMulti).PaintBracket( GetInfo(),
1823 aManip.GetSpaceAdd(), false );
1824 GetInfo().SetIdx( nTmpOldIdx );
1825 }
1826 // Restore the saved values
1827 GetInfo().X( nOldX );
1828 GetInfo().SetLen( nOldLen );
1829 pFontSave.reset();
1830 pTmpFnt.reset();
1831 SetPropFont( 0 );
1832 }
1833
lcl_ExtractFieldFollow(SwLineLayout * pLine,SwLinePortion * & rpField)1834 static bool lcl_ExtractFieldFollow( SwLineLayout* pLine, SwLinePortion* &rpField )
1835 {
1836 SwLinePortion* pLast = pLine;
1837 rpField = pLine->GetNextPortion();
1838 while( rpField && !rpField->InFieldGrp() )
1839 {
1840 pLast = rpField;
1841 rpField = rpField->GetNextPortion();
1842 }
1843 bool bRet = rpField != nullptr;
1844 if( bRet )
1845 {
1846 if( static_cast<SwFieldPortion*>(rpField)->IsFollow() )
1847 {
1848 rpField->Truncate();
1849 pLast->SetNextPortion( nullptr );
1850 }
1851 else
1852 rpField = nullptr;
1853 }
1854 pLine->Truncate();
1855 return bRet;
1856 }
1857
1858 // If a multi portion completely has to go to the
1859 // next line, this function is called to truncate
1860 // the rest of the remaining multi portion
lcl_TruncateMultiPortion(SwMultiPortion & rMulti,SwTextFormatInfo & rInf,TextFrameIndex const nStartIdx)1861 static void lcl_TruncateMultiPortion( SwMultiPortion& rMulti, SwTextFormatInfo& rInf,
1862 TextFrameIndex const nStartIdx)
1863 {
1864 rMulti.GetRoot().Truncate();
1865 rMulti.GetRoot().SetLen(TextFrameIndex(0));
1866 rMulti.GetRoot().Width(0);
1867 // rMulti.CalcSize( *this, aInf );
1868 if ( rMulti.GetRoot().GetNext() )
1869 {
1870 rMulti.GetRoot().GetNext()->Truncate();
1871 rMulti.GetRoot().GetNext()->SetLen(TextFrameIndex(0));
1872 rMulti.GetRoot().GetNext()->Width( 0 );
1873 }
1874 rMulti.Width( 0 );
1875 rMulti.SetLen(TextFrameIndex(0));
1876 rInf.SetIdx( nStartIdx );
1877 }
1878
1879 // Manages the formatting of a SwMultiPortion. External, for the calling
1880 // function, it seems to be a normal Format-function, internal it is like a
1881 // SwTextFrame::Format_ with multiple BuildPortions
BuildMultiPortion(SwTextFormatInfo & rInf,SwMultiPortion & rMulti)1882 bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf,
1883 SwMultiPortion& rMulti )
1884 {
1885 SwTwips nMaxWidth = rInf.Width();
1886 SwTwips nOldX = 0;
1887
1888 if( rMulti.HasBrackets() )
1889 {
1890 TextFrameIndex const nOldIdx = rInf.GetIdx();
1891 rInf.SetIdx( static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart );
1892 SeekAndChg( rInf );
1893 nOldX = GetInfo().X();
1894 static_cast<SwDoubleLinePortion&>(rMulti).FormatBrackets( rInf, nMaxWidth );
1895 rInf.SetIdx( nOldIdx );
1896 }
1897
1898 SeekAndChg( rInf );
1899 std::unique_ptr<SwFontSave> xFontSave;
1900 if( rMulti.IsDouble() )
1901 {
1902 SwFont* pTmpFnt = new SwFont( *rInf.GetFont() );
1903 if( rMulti.IsDouble() )
1904 {
1905 SetPropFont( 50 );
1906 pTmpFnt->SetProportion( GetPropFont() );
1907 }
1908 xFontSave.reset(new SwFontSave(rInf, pTmpFnt, this));
1909 }
1910
1911 SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() );
1912 if ( rMulti.IsBidi() )
1913 {
1914 // set layout mode
1915 aLayoutModeModifier.Modify( ! rInf.GetTextFrame()->IsRightToLeft() );
1916 }
1917
1918 SwTwips nTmpX = 0;
1919
1920 if( rMulti.HasRotation() )
1921 {
1922 // For nMaxWidth we take the height of the body frame.
1923 // #i25067#: If the current frame is inside a table, we restrict
1924 // nMaxWidth to the current frame height, unless the frame size
1925 // attribute is set to variable size:
1926
1927 // We set nTmpX (which is used for portion calculating) to the
1928 // current Y value
1929 const SwPageFrame* pPage = m_pFrame->FindPageFrame();
1930 OSL_ENSURE( pPage, "No page in frame!");
1931 const SwLayoutFrame* pUpperFrame = pPage;
1932
1933 if ( m_pFrame->IsInTab() )
1934 {
1935 pUpperFrame = m_pFrame->GetUpper();
1936 while ( pUpperFrame && !pUpperFrame->IsCellFrame() )
1937 pUpperFrame = pUpperFrame->GetUpper();
1938 assert(pUpperFrame); //pFrame is in table but does not have an upper cell frame
1939 if (!pUpperFrame)
1940 return false;
1941 const SwTableLine* pLine = static_cast<const SwRowFrame*>(pUpperFrame->GetUpper())->GetTabLine();
1942 const SwFormatFrameSize& rFrameFormatSize = pLine->GetFrameFormat()->GetFrameSize();
1943 if ( ATT_VAR_SIZE == rFrameFormatSize.GetHeightSizeType() )
1944 pUpperFrame = pPage;
1945 }
1946 if ( pUpperFrame == pPage && !m_pFrame->IsInFootnote() )
1947 pUpperFrame = pPage->FindBodyCont();
1948
1949 nMaxWidth = pUpperFrame ?
1950 ( rInf.GetTextFrame()->IsVertical() ?
1951 pUpperFrame->getFramePrintArea().Width() :
1952 pUpperFrame->getFramePrintArea().Height() ) :
1953 USHRT_MAX;
1954 }
1955 else
1956 nTmpX = rInf.X();
1957
1958 SwMultiPortion* pOldMulti = m_pMulti;
1959
1960 m_pMulti = &rMulti;
1961 SwLineLayout *pOldCurr = m_pCurr;
1962 TextFrameIndex const nOldStart = GetStart();
1963 SwTwips nMinWidth = nTmpX + 1;
1964 SwTwips nActWidth = nMaxWidth;
1965 const TextFrameIndex nStartIdx = rInf.GetIdx();
1966 TextFrameIndex nMultiLen = rMulti.GetLen();
1967
1968 SwLinePortion *pFirstRest;
1969 SwLinePortion *pSecondRest;
1970 if( rMulti.IsFormatted() )
1971 {
1972 if( !lcl_ExtractFieldFollow( &rMulti.GetRoot(), pFirstRest )
1973 && rMulti.IsDouble() && rMulti.GetRoot().GetNext() )
1974 lcl_ExtractFieldFollow( rMulti.GetRoot().GetNext(), pFirstRest );
1975 if( !rMulti.IsDouble() && rMulti.GetRoot().GetNext() )
1976 lcl_ExtractFieldFollow( rMulti.GetRoot().GetNext(), pSecondRest );
1977 else
1978 pSecondRest = nullptr;
1979 }
1980 else
1981 {
1982 pFirstRest = rMulti.GetRoot().GetNextPortion();
1983 pSecondRest = rMulti.GetRoot().GetNext() ?
1984 rMulti.GetRoot().GetNext()->GetNextPortion() : nullptr;
1985 if( pFirstRest )
1986 rMulti.GetRoot().SetNextPortion( nullptr );
1987 if( pSecondRest )
1988 rMulti.GetRoot().GetNext()->SetNextPortion( nullptr );
1989 rMulti.SetFormatted();
1990 nMultiLen = nMultiLen - rInf.GetIdx();
1991 }
1992
1993 // save some values
1994 const OUString* pOldText = &(rInf.GetText());
1995 const SwTwips nOldPaintOfst = rInf.GetPaintOfst();
1996 std::shared_ptr<vcl::TextLayoutCache> const pOldCachedVclData(rInf.GetCachedVclData());
1997 rInf.SetCachedVclData(nullptr);
1998
1999 OUString const aMultiStr( rInf.GetText().copy(0, sal_Int32(nMultiLen + rInf.GetIdx())) );
2000 rInf.SetText( aMultiStr );
2001 SwTextFormatInfo aInf( rInf, rMulti.GetRoot(), nActWidth );
2002 // Do we allow break cuts? The FirstMulti-Flag is evaluated during
2003 // line break determination.
2004 bool bFirstMulti = rInf.GetIdx() != rInf.GetLineStart();
2005
2006 SwLinePortion *pNextFirst = nullptr;
2007 SwLinePortion *pNextSecond = nullptr;
2008 bool bRet = false;
2009
2010 SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
2011 const bool bHasGrid = pGrid && GRID_LINES_CHARS == pGrid->GetGridType();
2012
2013 bool bRubyTop = false;
2014
2015 if ( bHasGrid )
2016 bRubyTop = ! pGrid->GetRubyTextBelow();
2017
2018 do
2019 {
2020 m_pCurr = &rMulti.GetRoot();
2021 m_nStart = nStartIdx;
2022 bRet = false;
2023 FormatReset( aInf );
2024 aInf.X( nTmpX );
2025 aInf.Width( sal_uInt16(nActWidth) );
2026 aInf.RealWidth( sal_uInt16(nActWidth) );
2027 aInf.SetFirstMulti( bFirstMulti );
2028 aInf.SetNumDone( rInf.IsNumDone() );
2029 aInf.SetFootnoteDone( rInf.IsFootnoteDone() );
2030
2031 if( pFirstRest )
2032 {
2033 OSL_ENSURE( pFirstRest->InFieldGrp(), "BuildMulti: Fieldrest expected");
2034 SwFieldPortion *pField =
2035 static_cast<SwFieldPortion*>(pFirstRest)->Clone(
2036 static_cast<SwFieldPortion*>(pFirstRest)->GetExp() );
2037 pField->SetFollow( true );
2038 aInf.SetRest( pField );
2039 }
2040 aInf.SetRuby( rMulti.IsRuby() && rMulti.OnTop() );
2041
2042 // in grid mode we temporarily have to disable the grid for the ruby line
2043 const bool bOldGridModeAllowed = GetInfo().SnapToGrid();
2044 if ( bHasGrid && aInf.IsRuby() && bRubyTop )
2045 aInf.SetSnapToGrid( false );
2046
2047 // If there's no more rubytext, then buildportion is forbidden
2048 if( pFirstRest || !aInf.IsRuby() )
2049 BuildPortions( aInf );
2050
2051 aInf.SetSnapToGrid( bOldGridModeAllowed );
2052
2053 rMulti.CalcSize( *this, aInf );
2054 m_pCurr->SetRealHeight( m_pCurr->Height() );
2055
2056 if( rMulti.IsBidi() )
2057 {
2058 pNextFirst = aInf.GetRest();
2059 break;
2060 }
2061
2062 if( rMulti.HasRotation() && !rMulti.IsDouble() )
2063 break;
2064 // second line has to be formatted
2065 else if( m_pCurr->GetLen()<nMultiLen || rMulti.IsRuby() || aInf.GetRest())
2066 {
2067 TextFrameIndex const nFirstLen = m_pCurr->GetLen();
2068 delete m_pCurr->GetNext();
2069 m_pCurr->SetNext( new SwLineLayout() );
2070 m_pCurr = m_pCurr->GetNext();
2071 m_nStart = aInf.GetIdx();
2072 aInf.X( nTmpX );
2073 SwTextFormatInfo aTmp( aInf, *m_pCurr, nActWidth );
2074 if( rMulti.IsRuby() )
2075 {
2076 aTmp.SetRuby( !rMulti.OnTop() );
2077 pNextFirst = aInf.GetRest();
2078 if( pSecondRest )
2079 {
2080 OSL_ENSURE( pSecondRest->InFieldGrp(), "Fieldrest expected");
2081 SwFieldPortion *pField = static_cast<SwFieldPortion*>(pSecondRest)->Clone(
2082 static_cast<SwFieldPortion*>(pSecondRest)->GetExp() );
2083 pField->SetFollow( true );
2084 aTmp.SetRest( pField );
2085 }
2086 if( !rMulti.OnTop() && nFirstLen < nMultiLen )
2087 bRet = true;
2088 }
2089 else
2090 aTmp.SetRest( aInf.GetRest() );
2091 aInf.SetRest( nullptr );
2092
2093 // in grid mode we temporarily have to disable the grid for the ruby line
2094 if ( bHasGrid && aTmp.IsRuby() && ! bRubyTop )
2095 aTmp.SetSnapToGrid( false );
2096
2097 BuildPortions( aTmp );
2098
2099 const SwLinePortion *pRightPortion = rMulti.OnRight() ?
2100 rMulti.GetRoot().GetNext()->GetNextPortion() : nullptr;
2101 if (pRightPortion)
2102 {
2103 // The ruby text on the right is vertical.
2104 // The width and the height are swapped.
2105 SwTwips nHeight = pRightPortion->Height();
2106 // Keep room for the ruby text.
2107 rMulti.GetRoot().FindLastPortion()->AddPrtWidth( nHeight );
2108 }
2109
2110 aTmp.SetSnapToGrid( bOldGridModeAllowed );
2111
2112 rMulti.CalcSize( *this, aInf );
2113 rMulti.GetRoot().SetRealHeight( rMulti.GetRoot().Height() );
2114 m_pCurr->SetRealHeight( m_pCurr->Height() );
2115 if( rMulti.IsRuby() )
2116 {
2117 pNextSecond = aTmp.GetRest();
2118 if( pNextFirst )
2119 bRet = true;
2120 }
2121 else
2122 pNextFirst = aTmp.GetRest();
2123 if( ( !aTmp.IsRuby() && nFirstLen + m_pCurr->GetLen() < nMultiLen )
2124 || aTmp.GetRest() )
2125 // our guess for width of multiportion was too small,
2126 // text did not fit into multiportion
2127 bRet = true;
2128 }
2129 if( rMulti.IsRuby() )
2130 break;
2131 if( bRet )
2132 {
2133 // our guess for multiportion width was too small,
2134 // we set min to act
2135 nMinWidth = nActWidth;
2136 nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4;
2137 if ( nActWidth == nMaxWidth && rInf.GetLineStart() == rInf.GetIdx() )
2138 // we have too less space, we must allow break cuts
2139 // ( the first multi flag is considered during TextPortion::Format_() )
2140 bFirstMulti = false;
2141 if( nActWidth <= nMinWidth )
2142 break;
2143 }
2144 else
2145 {
2146 // For Solaris, this optimization can causes trouble:
2147 // Setting this to the portion width ( = rMulti.Width() )
2148 // can make GetTextBreak inside SwTextGuess::Guess return too small
2149 // values. Therefore we add some extra twips.
2150 if( nActWidth > nTmpX + rMulti.Width() + 6 )
2151 nActWidth = nTmpX + rMulti.Width() + 6;
2152 nMaxWidth = nActWidth;
2153 nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4;
2154 if( nActWidth >= nMaxWidth )
2155 break;
2156 // we do not allow break cuts during formatting
2157 bFirstMulti = true;
2158 }
2159 delete pNextFirst;
2160 pNextFirst = nullptr;
2161 } while ( true );
2162
2163 m_pMulti = pOldMulti;
2164
2165 m_pCurr = pOldCurr;
2166 m_nStart = nOldStart;
2167 SetPropFont( 0 );
2168
2169 rMulti.SetLen( rMulti.GetRoot().GetLen() + ( rMulti.GetRoot().GetNext() ?
2170 rMulti.GetRoot().GetNext()->GetLen() : TextFrameIndex(0) ) );
2171
2172 if( rMulti.IsDouble() )
2173 {
2174 static_cast<SwDoubleLinePortion&>(rMulti).CalcBlanks( rInf );
2175 if( static_cast<SwDoubleLinePortion&>(rMulti).GetLineDiff() )
2176 {
2177 SwLineLayout* pLine = &rMulti.GetRoot();
2178 if( static_cast<SwDoubleLinePortion&>(rMulti).GetLineDiff() > 0 )
2179 {
2180 rInf.SetIdx( nStartIdx + pLine->GetLen() );
2181 pLine = pLine->GetNext();
2182 }
2183 if( pLine )
2184 {
2185 GetInfo().SetMulti( true );
2186
2187 // If the fourth element bSkipKashida of function CalcNewBlock is true, multiportion will be showed in justification.
2188 // Kashida (Persian) is a type of justification used in some cursive scripts, particularly Arabic.
2189 // In contrast to white-space justification, which increases the length of a line of text by expanding spaces between words or individual letters,
2190 // kashida justification is accomplished by elongating characters at certain chosen points.
2191 // Kashida justification can be combined with white-space justification to various extents.
2192 // The default value of bSkipKashida (the 4th parameter passed to 'CalcNewBlock') is false.
2193 // Only when Adjust is SvxAdjust::Block ( alignment is justify ), multiportion will be showed in justification in new code.
2194 CalcNewBlock( pLine, nullptr, rMulti.Width(), GetAdjust() != SvxAdjust::Block );
2195
2196 GetInfo().SetMulti( false );
2197 }
2198 rInf.SetIdx( nStartIdx );
2199 }
2200 if( static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets() )
2201 {
2202 rMulti.Width( rMulti.Width() +
2203 static_cast<SwDoubleLinePortion&>(rMulti).BracketWidth() );
2204 GetInfo().X( nOldX );
2205 }
2206 }
2207 else
2208 {
2209 rMulti.ActualizeTabulator();
2210 if( rMulti.IsRuby() )
2211 {
2212 static_cast<SwRubyPortion&>(rMulti).Adjust( rInf );
2213 static_cast<SwRubyPortion&>(rMulti).CalcRubyOffset();
2214 }
2215 }
2216 if( rMulti.HasRotation() )
2217 {
2218 SwTwips nH = rMulti.Width();
2219 SwTwips nAsc = rMulti.GetAscent() + ( nH - rMulti.Height() )/2;
2220 if( nAsc > nH )
2221 nAsc = nH;
2222 else if( nAsc < 0 )
2223 nAsc = 0;
2224 rMulti.Width( rMulti.Height() );
2225 rMulti.Height( sal_uInt16(nH) );
2226 rMulti.SetAscent( sal_uInt16(nAsc) );
2227 bRet = ( rInf.GetPos().X() + rMulti.Width() > rInf.Width() ) &&
2228 nStartIdx != rInf.GetLineStart();
2229 }
2230 else if ( rMulti.IsBidi() )
2231 {
2232 bRet = rMulti.GetLen() < nMultiLen || pNextFirst;
2233 }
2234
2235 // line break has to be performed!
2236 if( bRet )
2237 {
2238 OSL_ENSURE( !pNextFirst || pNextFirst->InFieldGrp(),
2239 "BuildMultiPortion: Surprising restportion, field expected" );
2240 SwMultiPortion *pTmp;
2241 if( rMulti.IsDouble() )
2242 pTmp = new SwDoubleLinePortion( static_cast<SwDoubleLinePortion&>(rMulti),
2243 nMultiLen + rInf.GetIdx() );
2244 else if( rMulti.IsRuby() )
2245 {
2246 OSL_ENSURE( !pNextSecond || pNextSecond->InFieldGrp(),
2247 "BuildMultiPortion: Surprising restportion, field expected" );
2248
2249 if ( rInf.GetIdx() == rInf.GetLineStart() )
2250 {
2251 // the ruby portion has to be split in two portions
2252 pTmp = new SwRubyPortion( static_cast<SwRubyPortion&>(rMulti),
2253 nMultiLen + rInf.GetIdx() );
2254
2255 if( pNextSecond )
2256 {
2257 pTmp->GetRoot().SetNext( new SwLineLayout() );
2258 pTmp->GetRoot().GetNext()->SetNextPortion( pNextSecond );
2259 }
2260 pTmp->SetFollowField();
2261 }
2262 else
2263 {
2264 // we try to keep our ruby portion together
2265 lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
2266 pTmp = nullptr;
2267 }
2268 }
2269 else if( rMulti.HasRotation() )
2270 {
2271 // we try to keep our rotated portion together
2272 lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
2273 pTmp = new SwRotatedPortion( nMultiLen + rInf.GetIdx(),
2274 rMulti.GetDirection() );
2275 }
2276 // during a recursion of BuildMultiPortions we may not build
2277 // a new SwBidiPortion, this would cause a memory leak
2278 else if( rMulti.IsBidi() && ! m_pMulti )
2279 {
2280 if ( ! rMulti.GetLen() )
2281 lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
2282
2283 // If there is a HolePortion at the end of the bidi portion,
2284 // it has to be moved behind the bidi portion. Otherwise
2285 // the visual cursor travelling gets into trouble.
2286 SwLineLayout& aRoot = rMulti.GetRoot();
2287 SwLinePortion* pPor = aRoot.GetFirstPortion();
2288 while ( pPor )
2289 {
2290 if ( pPor->GetNextPortion() && pPor->GetNextPortion()->IsHolePortion() )
2291 {
2292 SwLinePortion* pHolePor = pPor->GetNextPortion();
2293 pPor->SetNextPortion( nullptr );
2294 aRoot.SetLen( aRoot.GetLen() - pHolePor->GetLen() );
2295 rMulti.SetLen( rMulti.GetLen() - pHolePor->GetLen() );
2296 rMulti.SetNextPortion( pHolePor );
2297 break;
2298 }
2299 pPor = pPor->GetNextPortion();
2300 }
2301
2302 pTmp = new SwBidiPortion( nMultiLen + rInf.GetIdx(),
2303 static_cast<SwBidiPortion&>(rMulti).GetLevel() );
2304 }
2305 else
2306 pTmp = nullptr;
2307
2308 if ( ! rMulti.GetLen() && rInf.GetLast() )
2309 {
2310 SeekAndChgBefore( rInf );
2311 rInf.GetLast()->FormatEOL( rInf );
2312 }
2313
2314 if( pNextFirst && pTmp )
2315 {
2316 pTmp->SetFollowField();
2317 pTmp->GetRoot().SetNextPortion( pNextFirst );
2318 }
2319 else
2320 // A follow field portion is still waiting. If nobody wants it,
2321 // we delete it.
2322 delete pNextFirst;
2323
2324 rInf.SetRest( pTmp );
2325 }
2326
2327 rInf.SetCachedVclData(pOldCachedVclData);
2328 rInf.SetText( *pOldText );
2329 rInf.SetPaintOfst( nOldPaintOfst );
2330 rInf.SetStop( aInf.IsStop() );
2331 rInf.SetNumDone( true );
2332 rInf.SetFootnoteDone( true );
2333 SeekAndChg( rInf );
2334 delete pFirstRest;
2335 delete pSecondRest;
2336 xFontSave.reset();
2337 return bRet;
2338 }
2339
2340 // When a fieldportion at the end of line breaks and needs a following
2341 // fieldportion in the next line, then the "restportion" of the formatinfo
2342 // has to be set. Normally this happens during the formatting of the first
2343 // part of the fieldportion.
2344 // But sometimes the formatting starts at the line with the following part,
2345 // especially when the following part is on the next page.
2346 // In this case the MakeRestPortion-function has to create the following part.
2347 // The first parameter is the line that contains possibly a first part
2348 // of a field. When the function finds such field part, it creates the right
2349 // restportion. This may be a multiportion, e.g. if the field is surrounded by
2350 // a doubleline- or ruby-portion.
2351 // The second parameter is the start index of the line.
MakeRestPortion(const SwLineLayout * pLine,TextFrameIndex nPosition)2352 SwLinePortion* SwTextFormatter::MakeRestPortion( const SwLineLayout* pLine,
2353 TextFrameIndex nPosition)
2354 {
2355 if( !nPosition )
2356 return nullptr;
2357 TextFrameIndex nMultiPos = nPosition - pLine->GetLen();
2358 const SwMultiPortion *pTmpMulti = nullptr;
2359 const SwMultiPortion *pHelpMulti = nullptr;
2360 const SwLinePortion* pPor = pLine->GetFirstPortion();
2361 SwFieldPortion *pField = nullptr;
2362 while( pPor )
2363 {
2364 if( pPor->GetLen() && !pHelpMulti )
2365 {
2366 nMultiPos = nMultiPos + pPor->GetLen();
2367 pTmpMulti = nullptr;
2368 }
2369 if( pPor->InFieldGrp() )
2370 {
2371 if( !pHelpMulti )
2372 pTmpMulti = nullptr;
2373 pField = const_cast<SwFieldPortion*>(static_cast<const SwFieldPortion*>(pPor));
2374 }
2375 else if( pPor->IsMultiPortion() )
2376 {
2377 OSL_ENSURE( !pHelpMulti || pHelpMulti->IsBidi(),
2378 "Nested multiportions are forbidden." );
2379
2380 pField = nullptr;
2381 pTmpMulti = static_cast<const SwMultiPortion*>(pPor);
2382 }
2383 pPor = pPor->GetNextPortion();
2384 // If the last portion is a multi-portion, we enter it
2385 // and look for a field portion inside.
2386 // If we are already in a multiportion, we could change to the
2387 // next line
2388 if( !pPor && pTmpMulti )
2389 {
2390 if( pHelpMulti )
2391 { // We're already inside the multiportion, let's take the second
2392 // line, if we are in a double line portion
2393 if( !pHelpMulti->IsRuby() )
2394 pPor = pHelpMulti->GetRoot().GetNext();
2395 pTmpMulti = nullptr;
2396 }
2397 else
2398 { // Now we enter a multiportion, in a ruby portion we take the
2399 // main line, not the phonetic line, in a doublelineportion we
2400 // starts with the first line.
2401 pHelpMulti = pTmpMulti;
2402 nMultiPos = nMultiPos - pHelpMulti->GetLen();
2403 if( pHelpMulti->IsRuby() && pHelpMulti->OnTop() )
2404 pPor = pHelpMulti->GetRoot().GetNext();
2405 else
2406 pPor = pHelpMulti->GetRoot().GetFirstPortion();
2407 }
2408 }
2409 }
2410 if( pField && !pField->HasFollow() )
2411 pField = nullptr;
2412
2413 SwLinePortion *pRest = nullptr;
2414 if( pField )
2415 {
2416 const SwTextAttr *pHint = GetAttr(nPosition - TextFrameIndex(1));
2417 if ( pHint
2418 && ( pHint->Which() == RES_TXTATR_FIELD
2419 || pHint->Which() == RES_TXTATR_ANNOTATION ) )
2420 {
2421 pRest = NewFieldPortion( GetInfo(), pHint );
2422 if( pRest->InFieldGrp() )
2423 static_cast<SwFieldPortion*>(pRest)->TakeNextOffset( pField );
2424 else
2425 {
2426 delete pRest;
2427 pRest = nullptr;
2428 }
2429 }
2430 }
2431 if( !pHelpMulti )
2432 return pRest;
2433
2434 nPosition = nMultiPos + pHelpMulti->GetLen();
2435 std::unique_ptr<SwMultiCreator> pCreate = GetInfo().GetMultiCreator( nMultiPos, nullptr );
2436
2437 if ( !pCreate )
2438 {
2439 OSL_ENSURE( !pHelpMulti->GetLen(), "Multiportion without attribute?" );
2440 if ( nMultiPos )
2441 --nMultiPos;
2442 pCreate = GetInfo().GetMultiCreator( --nMultiPos, nullptr );
2443 }
2444
2445 if (!pCreate)
2446 return pRest;
2447
2448 if( pRest || nMultiPos > nPosition || ( pHelpMulti->IsRuby() &&
2449 static_cast<const SwRubyPortion*>(pHelpMulti)->GetRubyOffset() < TextFrameIndex(COMPLETE_STRING)))
2450 {
2451 SwMultiPortion* pTmp;
2452 if( pHelpMulti->IsDouble() )
2453 pTmp = new SwDoubleLinePortion( *pCreate, nMultiPos );
2454 else if( pHelpMulti->IsBidi() )
2455 pTmp = new SwBidiPortion( nMultiPos, pCreate->nLevel );
2456 else if( pHelpMulti->IsRuby() )
2457 {
2458 pTmp = new SwRubyPortion( *pCreate, *GetInfo().GetFont(),
2459 m_pFrame->GetDoc().getIDocumentSettingAccess(),
2460 nMultiPos, static_cast<const SwRubyPortion*>(pHelpMulti)->GetRubyOffset(),
2461 GetInfo() );
2462 }
2463 else if( pHelpMulti->HasRotation() )
2464 pTmp = new SwRotatedPortion( nMultiPos, pHelpMulti->GetDirection() );
2465 else
2466 {
2467 return pRest;
2468 }
2469 pCreate.reset();
2470 pTmp->SetFollowField();
2471 if( pRest )
2472 {
2473 SwLineLayout *pLay = &pTmp->GetRoot();
2474 if( pTmp->IsRuby() && pTmp->OnTop() )
2475 {
2476 pLay->SetNext( new SwLineLayout() );
2477 pLay = pLay->GetNext();
2478 }
2479 pLay->SetNextPortion( pRest );
2480 }
2481 return pTmp;
2482 }
2483 return pRest;
2484 }
2485
2486 // SwTextCursorSave notes the start and current line of a SwTextCursor,
2487 // sets them to the values for GetCursorOfst inside a multiportion
2488 // and restores them in the destructor.
SwTextCursorSave(SwTextCursor * pCursor,SwMultiPortion * pMulti,SwTwips nY,sal_uInt16 & nX,TextFrameIndex const nCurrStart,long nSpaceAdd)2489 SwTextCursorSave::SwTextCursorSave( SwTextCursor* pCursor,
2490 SwMultiPortion* pMulti,
2491 SwTwips nY,
2492 sal_uInt16& nX,
2493 TextFrameIndex const nCurrStart,
2494 long nSpaceAdd )
2495 {
2496 pTextCursor = pCursor;
2497 nStart = pCursor->m_nStart;
2498 pCursor->m_nStart = nCurrStart;
2499 pCurr = pCursor->m_pCurr;
2500 pCursor->m_pCurr = &pMulti->GetRoot();
2501 while( pCursor->Y() + pCursor->GetLineHeight() < nY &&
2502 pCursor->Next() )
2503 ; // nothing
2504 nWidth = pCursor->m_pCurr->Width();
2505 nOldProp = pCursor->GetPropFont();
2506
2507 if ( pMulti->IsDouble() || pMulti->IsBidi() )
2508 {
2509 bSpaceChg = pMulti->ChgSpaceAdd( pCursor->m_pCurr, nSpaceAdd );
2510
2511 TextFrameIndex nSpaceCnt;
2512 if ( pMulti->IsDouble() )
2513 {
2514 pCursor->SetPropFont( 50 );
2515 nSpaceCnt = static_cast<SwDoubleLinePortion*>(pMulti)->GetSpaceCnt();
2516 }
2517 else
2518 {
2519 TextFrameIndex const nOldIdx = pCursor->GetInfo().GetIdx();
2520 pCursor->GetInfo().SetIdx ( nCurrStart );
2521 nSpaceCnt = static_cast<SwBidiPortion*>(pMulti)->GetSpaceCnt(pCursor->GetInfo());
2522 pCursor->GetInfo().SetIdx ( nOldIdx );
2523 }
2524
2525 if( nSpaceAdd > 0 && !pMulti->HasTabulator() )
2526 pCursor->m_pCurr->Width( static_cast<sal_uInt16>(nWidth + nSpaceAdd * sal_Int32(nSpaceCnt) / SPACING_PRECISION_FACTOR) );
2527
2528 // For a BidiPortion we have to calculate the offset from the
2529 // end of the portion
2530 if ( nX && pMulti->IsBidi() )
2531 nX = pCursor->m_pCurr->Width() - nX;
2532 }
2533 else
2534 bSpaceChg = false;
2535 }
2536
~SwTextCursorSave()2537 SwTextCursorSave::~SwTextCursorSave()
2538 {
2539 if( bSpaceChg )
2540 SwDoubleLinePortion::ResetSpaceAdd( pTextCursor->m_pCurr );
2541 pTextCursor->m_pCurr->Width( nWidth );
2542 pTextCursor->m_pCurr = pCurr;
2543 pTextCursor->m_nStart = nStart;
2544 pTextCursor->SetPropFont( nOldProp );
2545 }
2546
2547 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2548