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