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 <hintids.hxx>
21 
22 #include <com/sun/star/i18n/ScriptType.hpp>
23 #include <com/sun/star/i18n/XBreakIterator.hpp>
24 #include <vcl/graph.hxx>
25 #include <editeng/brushitem.hxx>
26 #include <vcl/metric.hxx>
27 #include <vcl/outdev.hxx>
28 #include <viewopt.hxx>
29 #include <SwPortionHandler.hxx>
30 #include "porlay.hxx"
31 #include "porfld.hxx"
32 #include "inftxt.hxx"
33 #include <fmtornt.hxx>
34 #include <frmatr.hxx>
35 #include <frmtool.hxx>
36 #include <viewsh.hxx>
37 #include <doc.hxx>
38 #include <IDocumentSettingAccess.hxx>
39 #include <rootfrm.hxx>
40 #include <breakit.hxx>
41 #include "porftn.hxx"
42 #include <accessibilityoptions.hxx>
43 #include <editeng/lrspitem.hxx>
44 #include <unicode/ubidi.h>
45 #include <bookmrk.hxx>
46 
47 using namespace ::com::sun::star;
48 
Compress()49 SwLinePortion *SwFieldPortion::Compress()
50 { return (GetLen() || !m_aExpand.isEmpty() || SwLinePortion::Compress()) ? this : nullptr; }
51 
Clone(const OUString & rExpand) const52 SwFieldPortion *SwFieldPortion::Clone( const OUString &rExpand ) const
53 {
54     std::unique_ptr<SwFont> pNewFnt;
55     if( m_pFont )
56     {
57         pNewFnt.reset(new SwFont( *m_pFont ));
58     }
59     // #i107143#
60     // pass placeholder property to created <SwFieldPortion> instance.
61     SwFieldPortion* pClone = new SwFieldPortion( rExpand, std::move(pNewFnt), m_bPlaceHolder );
62     pClone->SetNextOffset( m_nNextOffset );
63     pClone->m_bNoLength = m_bNoLength;
64     return pClone;
65 }
66 
TakeNextOffset(const SwFieldPortion * pField)67 void SwFieldPortion::TakeNextOffset( const SwFieldPortion* pField )
68 {
69     OSL_ENSURE( pField, "TakeNextOffset: Missing Source" );
70     m_nNextOffset = pField->GetNextOffset();
71     m_aExpand = m_aExpand.replaceAt(0, sal_Int32(m_nNextOffset), "");
72     m_bFollow = true;
73 }
74 
SwFieldPortion(const OUString & rExpand,std::unique_ptr<SwFont> pFont,bool bPlaceHold,TextFrameIndex const nFieldLen)75 SwFieldPortion::SwFieldPortion(const OUString &rExpand, std::unique_ptr<SwFont> pFont, bool bPlaceHold, TextFrameIndex const nFieldLen)
76     : m_aExpand(rExpand), m_pFont(std::move(pFont)), m_nNextOffset(0)
77     , m_nNextScriptChg(COMPLETE_STRING), m_nFieldLen(nFieldLen), m_nViewWidth(0)
78     , m_bFollow( false ), m_bLeft( false), m_bHide( false)
79     , m_bCenter (false), m_bHasFollow( false )
80     , m_bAnimated( false), m_bNoPaint( false)
81     , m_bReplace( false), m_bPlaceHolder( bPlaceHold )
82     , m_bNoLength( false )
83     , m_nAttrFieldType(0)
84 {
85     SetWhichPor( PortionType::Field );
86 }
87 
SwFieldPortion(const SwFieldPortion & rField)88 SwFieldPortion::SwFieldPortion( const SwFieldPortion& rField )
89     : SwExpandPortion( rField )
90     , m_aExpand( rField.GetExp() )
91     , m_nNextOffset( rField.GetNextOffset() )
92     , m_nNextScriptChg( rField.m_nNextScriptChg )
93     , m_nFieldLen(rField.m_nFieldLen)
94     , m_nViewWidth( rField.m_nViewWidth )
95     , m_bFollow( rField.IsFollow() )
96     , m_bLeft( rField.IsLeft() )
97     , m_bHide( rField.IsHide() )
98     , m_bCenter( rField.IsCenter() )
99     , m_bHasFollow( rField.HasFollow() )
100     , m_bAnimated ( rField.m_bAnimated )
101     , m_bNoPaint( rField.m_bNoPaint)
102     , m_bReplace( rField.m_bReplace )
103     , m_bPlaceHolder( rField.m_bPlaceHolder )
104     , m_bNoLength( rField.m_bNoLength )
105     , m_nAttrFieldType( rField.m_nAttrFieldType)
106 {
107     if ( rField.HasFont() )
108         m_pFont.reset( new SwFont( *rField.GetFont() ) );
109 
110     SetWhichPor( PortionType::Field );
111 }
112 
~SwFieldPortion()113 SwFieldPortion::~SwFieldPortion()
114 {
115     m_pFont.reset();
116 }
117 
GetViewWidth(const SwTextSizeInfo & rInf) const118 sal_uInt16 SwFieldPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const
119 {
120     // even though this is const, nViewWidth should be computed at the very end:
121     SwFieldPortion* pThis = const_cast<SwFieldPortion*>(this);
122     if( !Width() && rInf.OnWin() && !rInf.GetOpt().IsPagePreview() &&
123             !rInf.GetOpt().IsReadonly() && SwViewOption::IsFieldShadings() )
124     {
125         if( !m_nViewWidth )
126             pThis->m_nViewWidth = rInf.GetTextSize(OUString(' ')).Width();
127     }
128     else
129         pThis->m_nViewWidth = 0;
130     return m_nViewWidth;
131 }
132 
133 namespace {
134 
135 /**
136  * Never just use SetLen(0)
137  */
138 class SwFieldSlot
139 {
140     std::shared_ptr<vcl::TextLayoutCache> m_pOldCachedVclData;
141     const OUString *pOldText;
142     OUString aText;
143     TextFrameIndex nIdx;
144     TextFrameIndex nLen;
145     SwTextFormatInfo *pInf;
146     bool bOn;
147 public:
148     SwFieldSlot( const SwTextFormatInfo* pNew, const SwFieldPortion *pPor );
149     ~SwFieldSlot();
150 };
151 
152 }
153 
SwFieldSlot(const SwTextFormatInfo * pNew,const SwFieldPortion * pPor)154 SwFieldSlot::SwFieldSlot( const SwTextFormatInfo* pNew, const SwFieldPortion *pPor )
155     : pOldText(nullptr)
156     , nIdx(0)
157     , nLen(0)
158     , pInf(nullptr)
159 {
160     bOn = pPor->GetExpText( *pNew, aText );
161 
162     // The text will be replaced ...
163     if( !bOn )
164         return;
165 
166     pInf = const_cast<SwTextFormatInfo*>(pNew);
167     nIdx = pInf->GetIdx();
168     nLen = pInf->GetLen();
169     pOldText = &(pInf->GetText());
170     m_pOldCachedVclData = pInf->GetCachedVclData();
171     pInf->SetLen(TextFrameIndex(aText.getLength()));
172     pInf->SetCachedVclData(nullptr);
173     if( pPor->IsFollow() )
174     {
175         pInf->SetFakeLineStart( nIdx > pInf->GetLineStart() );
176         pInf->SetIdx(TextFrameIndex(0));
177     }
178     else if (nIdx < TextFrameIndex(pOldText->getLength()))
179     {
180         sal_Int32 const nFieldLen(pPor->GetFieldLen());
181         aText = (*pOldText).replaceAt(sal_Int32(nIdx), nFieldLen, aText);
182     }
183     pInf->SetText( aText );
184 }
185 
~SwFieldSlot()186 SwFieldSlot::~SwFieldSlot()
187 {
188     if( bOn )
189     {
190         pInf->SetCachedVclData(m_pOldCachedVclData);
191         pInf->SetText( *pOldText );
192         pInf->SetIdx( nIdx );
193         pInf->SetLen( nLen );
194         pInf->SetFakeLineStart( false );
195     }
196 }
197 
CheckScript(const SwTextSizeInfo & rInf)198 void SwFieldPortion::CheckScript( const SwTextSizeInfo &rInf )
199 {
200     OUString aText;
201     if (!GetExpText(rInf, aText) || aText.isEmpty())
202         return;
203 
204     SwFontScript nActual = m_pFont ? m_pFont->GetActual() : rInf.GetFont()->GetActual();
205     sal_uInt16 nScript = g_pBreakIt->GetBreakIter()->getScriptType( aText, 0 );
206     sal_Int32 nChg = 0;
207     if( i18n::ScriptType::WEAK == nScript )
208     {
209         nChg = g_pBreakIt->GetBreakIter()->endOfScript(aText,0,nScript);
210         if (nChg < aText.getLength() && nChg >= 0)
211             nScript = g_pBreakIt->GetBreakIter()->getScriptType( aText, nChg );
212     }
213 
214     // nNextScriptChg will be evaluated during SwFieldPortion::Format()
215 
216     if (nChg < aText.getLength() && nChg >= 0)
217         m_nNextScriptChg = TextFrameIndex(
218                 g_pBreakIt->GetBreakIter()->endOfScript(aText, nChg, nScript));
219     else
220         m_nNextScriptChg = TextFrameIndex(aText.getLength());
221 
222     SwFontScript nTmp;
223     switch ( nScript ) {
224         case i18n::ScriptType::LATIN : nTmp = SwFontScript::Latin; break;
225         case i18n::ScriptType::ASIAN : nTmp = SwFontScript::CJK; break;
226         case i18n::ScriptType::COMPLEX : nTmp = SwFontScript::CTL; break;
227         default: nTmp = nActual;
228     }
229 
230     // #i16354# Change script type for RTL text to CTL.
231     const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo();
232     // #i98418#
233     const sal_uInt8 nFieldDir = (IsNumberPortion() || IsFootnoteNumPortion())
234         ? rSI.GetDefaultDir()
235         : rSI.DirType(IsFollow() ? rInf.GetIdx() - m_nFieldLen : rInf.GetIdx());
236 
237     {
238         UErrorCode nError = U_ZERO_ERROR;
239         UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError );
240         ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nFieldDir, nullptr, &nError );
241         int32_t nEnd;
242         UBiDiLevel nCurrDir;
243         ubidi_getLogicalRun( pBidi, 0, &nEnd, &nCurrDir );
244         ubidi_close( pBidi );
245         const TextFrameIndex nNextDirChg(nEnd);
246         m_nNextScriptChg = std::min( m_nNextScriptChg, nNextDirChg );
247 
248         // #i89825# change the script type also to CTL
249         // if there is no strong LTR char in the LTR run (numbers)
250         if (nCurrDir != UBIDI_RTL &&
251             (UBIDI_LTR != nFieldDir || i18n::ScriptType::COMPLEX == nScript))
252         {
253             nCurrDir = UBIDI_RTL;
254             for( sal_Int32 nCharIdx = 0; nCharIdx < nEnd; ++nCharIdx )
255             {
256                 UCharDirection nCharDir = u_charDirection ( aText[ nCharIdx ]);
257                 if ( nCharDir == U_LEFT_TO_RIGHT ||
258                      nCharDir == U_LEFT_TO_RIGHT_EMBEDDING ||
259                      nCharDir == U_LEFT_TO_RIGHT_OVERRIDE )
260                 {
261                     nCurrDir = UBIDI_LTR;
262                     break;
263                 }
264             }
265         }
266 
267         if (nCurrDir == UBIDI_RTL)
268         {
269             nTmp = SwFontScript::CTL;
270             // If we decided that this range was RTL after all and the
271             // previous range was complex but clipped to the start of this
272             // range, then extend it to be complex over the additional RTL range
273             if (nScript == i18n::ScriptType::COMPLEX)
274                 m_nNextScriptChg = nNextDirChg;
275         }
276     }
277 
278     // #i98418#
279     // keep determined script type for footnote portions as preferred script type.
280     // For footnote portions a font can not be created directly - see footnote
281     // portion format method.
282     if ( IsFootnotePortion() )
283     {
284         static_cast<SwFootnotePortion*>(this)->SetPreferredScriptType( nTmp );
285     }
286     else if ( nTmp != nActual )
287     {
288         if( !m_pFont )
289             m_pFont.reset( new SwFont( *rInf.GetFont() ) );
290         m_pFont->SetActual( nTmp );
291     }
292 
293 }
294 
Format(SwTextFormatInfo & rInf)295 bool SwFieldPortion::Format( SwTextFormatInfo &rInf )
296 {
297     // Scope wegen aDiffText::DTOR!
298     bool bFull = false;
299     bool bEOL = false;
300     TextFrameIndex const nTextRest = TextFrameIndex(rInf.GetText().getLength()) - rInf.GetIdx();
301     {
302         TextFrameIndex nRest;
303         SwFieldSlot aDiffText( &rInf, this );
304         SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() );
305         aLayoutModeModifier.SetAuto();
306 
307         // Field portion has to be split in several parts if
308         // 1. There are script/direction changes inside the field
309         // 2. There are portion breaks (tab, break) inside the field:
310         const TextFrameIndex nOldFullLen = rInf.GetLen();
311         TextFrameIndex nFullLen = rInf.ScanPortionEnd(rInf.GetIdx(), rInf.GetIdx() + nOldFullLen) - rInf.GetIdx();
312         if ( m_nNextScriptChg < nFullLen )
313         {
314             nFullLen = m_nNextScriptChg;
315             rInf.SetHookChar( 0 );
316         }
317         rInf.SetLen( nFullLen );
318 
319         if (TextFrameIndex(COMPLETE_STRING) != rInf.GetUnderScorePos() &&
320              rInf.GetUnderScorePos() > rInf.GetIdx() )
321              rInf.SetUnderScorePos( rInf.GetIdx() );
322 
323         if( m_pFont )
324             m_pFont->AllocFontCacheId( rInf.GetVsh(), m_pFont->GetActual() );
325 
326         SwFontSave aSave( rInf, m_pFont.get() );
327 
328         // Length must be 0: the length is set for bFull after format
329         // and passed along in nRest. Or else the old length would be
330         // retained and be used for nRest!
331         SetLen(TextFrameIndex(0));
332         TextFrameIndex const nFollow(IsFollow() ? TextFrameIndex(0) : m_nFieldLen);
333 
334         // As odd is may seem: the query for GetLen() must return false due
335         // to the ExpandPortions _after_ aDiffText (see SoftHyphs), caused
336         // by SetFull.
337         if( !nFullLen )
338         {
339             // Don't Init(), as we need height and ascent
340             Width(0);
341             bFull = rInf.Width() <= rInf.GetPos().X();
342         }
343         else
344         {
345             TextFrameIndex const nOldLineStart = rInf.GetLineStart();
346             if( IsFollow() )
347                 rInf.SetLineStart(TextFrameIndex(0));
348             rInf.SetNotEOL( nFullLen == nOldFullLen && nTextRest > nFollow );
349 
350             // the height depending on the fields font is set,
351             // this is required for SwTextGuess::Guess
352             Height( rInf.GetTextHeight() + rInf.GetFont()->GetTopBorderSpace() +
353                     rInf.GetFont()->GetBottomBorderSpace() );
354             // If a kerning portion is inserted after our field portion,
355             // the ascent and height must be known
356             SetAscent( rInf.GetAscent() + rInf.GetFont()->GetTopBorderSpace() );
357             bFull = SwTextPortion::Format( rInf );
358             rInf.SetNotEOL( false );
359             rInf.SetLineStart( nOldLineStart );
360         }
361         TextFrameIndex const nTmpLen = GetLen();
362         bEOL = !nTmpLen && nFollow && bFull;
363         nRest = nOldFullLen - nTmpLen;
364 
365         // The char is held in the first position
366         // Unconditionally after format!
367         SetLen( m_bNoLength ? TextFrameIndex(0) : nFollow );
368 
369         if( nRest )
370         {
371             // aExpand has not yet been shortened; the new Ofst is a
372             // result of nRest
373             TextFrameIndex nNextOfst = TextFrameIndex(m_aExpand.getLength()) - nRest;
374 
375             if ( IsQuoVadisPortion() )
376                 nNextOfst = nNextOfst + TextFrameIndex(static_cast<SwQuoVadisPortion*>(this)->GetContText().getLength());
377 
378             OUString aNew( m_aExpand.copy(sal_Int32(nNextOfst)) );
379             m_aExpand = m_aExpand.copy(0, sal_Int32(nNextOfst));
380 
381             // These characters should not be contained in the follow
382             // field portion. They are handled via the HookChar mechanism.
383             const sal_Unicode nNew = !aNew.isEmpty() ? aNew[0] : 0;
384             switch (nNew)
385             {
386                 case CH_BREAK  : bFull = true;
387                     [[fallthrough]];
388                 case ' ' :
389                 case CH_TAB    :
390                 case CHAR_HARDHYPHEN:               // non-breaking hyphen
391                 case CHAR_SOFTHYPHEN:
392                 case CHAR_HARDBLANK:
393                 case CHAR_ZWSP :
394                 case CHAR_WJ :
395                 case CH_TXTATR_BREAKWORD:
396                 case CH_TXTATR_INWORD:
397                 {
398                     aNew = aNew.copy( 1 );
399                     ++nNextOfst;
400                     break;
401                 }
402                 default: ;
403             }
404 
405             // Even if there is no more text left for a follow field,
406             // we have to build a follow field portion (without font),
407             // otherwise the HookChar mechanism would not work.
408             SwFieldPortion *pField = Clone( aNew );
409             if( !aNew.isEmpty() && !pField->GetFont() )
410             {
411                 pField->SetFont( std::make_unique<SwFont>( *rInf.GetFont() ) );
412             }
413             pField->SetFollow( true );
414             SetHasFollow( true );
415 
416             // For a newly created field, nNextOffset contains the Offset
417             // of its start of the original string
418             // If a FollowField is created when formatting, this FollowField's
419             // Offset is being held in nNextOffset
420             m_nNextOffset = m_nNextOffset + nNextOfst;
421             pField->SetNextOffset( m_nNextOffset );
422             rInf.SetRest( pField );
423         }
424     }
425 
426     if( bEOL && rInf.GetLast() && !rInf.GetUnderflow() )
427         rInf.GetLast()->FormatEOL( rInf );
428     return bFull;
429 }
430 
Paint(const SwTextPaintInfo & rInf) const431 void SwFieldPortion::Paint( const SwTextPaintInfo &rInf ) const
432 {
433     SwFontSave aSave( rInf, m_pFont.get() );
434 
435 //    OSL_ENSURE(GetLen() <= TextFrameIndex(1), "SwFieldPortion::Paint: rest-portion pollution?");
436     if( Width() && ( !m_bPlaceHolder || rInf.GetOpt().IsShowPlaceHolderFields() ) )
437     {
438         // A very liberal use of the background
439         rInf.DrawViewOpt( *this, PortionType::Field );
440         SwExpandPortion::Paint( rInf );
441     }
442 }
443 
GetExpText(const SwTextSizeInfo & rInf,OUString & rText) const444 bool SwFieldPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const
445 {
446     rText = m_aExpand;
447     if( rText.isEmpty() && rInf.OnWin() &&
448         !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() &&
449             SwViewOption::IsFieldShadings() &&
450             !HasFollow() )
451         rText = " ";
452     return true;
453 }
454 
HandlePortion(SwPortionHandler & rPH) const455 void SwFieldPortion::HandlePortion( SwPortionHandler& rPH ) const
456 {
457     sal_Int32 nH = 0;
458     sal_Int32 nW = 0;
459     if (m_pFont)
460     {
461         nH = m_pFont->GetSize(m_pFont->GetActual()).Height();
462         nW = m_pFont->GetSize(m_pFont->GetActual()).Width();
463     }
464     rPH.Special( GetLen(), m_aExpand, GetWhichPor(), nH, nW, m_pFont.get() );
465 }
466 
GetTextSize(const SwTextSizeInfo & rInf) const467 SwPosSize SwFieldPortion::GetTextSize( const SwTextSizeInfo &rInf ) const
468 {
469     SwFontSave aSave( rInf, m_pFont.get() );
470     SwPosSize aSize( SwExpandPortion::GetTextSize( rInf ) );
471     return aSize;
472 }
473 
Clone(const OUString & rExpand) const474 SwFieldPortion *SwHiddenPortion::Clone(const OUString &rExpand ) const
475 {
476     std::unique_ptr<SwFont> pNewFnt;
477     if( m_pFont )
478         pNewFnt.reset(new SwFont( *m_pFont ));
479     return new SwHiddenPortion( rExpand, std::move(pNewFnt) );
480 }
481 
Paint(const SwTextPaintInfo & rInf) const482 void SwHiddenPortion::Paint( const SwTextPaintInfo &rInf ) const
483 {
484     if( Width() )
485     {
486         SwFontSave aSave( rInf, m_pFont.get() );
487         rInf.DrawViewOpt( *this, PortionType::Hidden );
488         SwExpandPortion::Paint( rInf );
489     }
490 }
491 
GetExpText(const SwTextSizeInfo & rInf,OUString & rText) const492 bool SwHiddenPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const
493 {
494     // Do not query for IsHidden()!
495     return SwFieldPortion::GetExpText( rInf, rText );
496 }
497 
SwNumberPortion(const OUString & rExpand,std::unique_ptr<SwFont> pFont,const bool bLft,const bool bCntr,const sal_uInt16 nMinDst,const bool bLabelAlignmentPosAndSpaceModeActive)498 SwNumberPortion::SwNumberPortion( const OUString &rExpand,
499                                   std::unique_ptr<SwFont> pFont,
500                                   const bool bLft,
501                                   const bool bCntr,
502                                   const sal_uInt16 nMinDst,
503                                   const bool bLabelAlignmentPosAndSpaceModeActive )
504         : SwFieldPortion( rExpand, std::move(pFont) ),
505           m_nFixWidth(0),
506           m_nMinDist( nMinDst ),
507           mbLabelAlignmentPosAndSpaceModeActive( bLabelAlignmentPosAndSpaceModeActive )
508 {
509     SetWhichPor( PortionType::Number );
510     SetLeft( bLft );
511     SetHide( false );
512     SetCenter( bCntr );
513 }
514 
GetModelPositionForViewPoint(const sal_uInt16) const515 TextFrameIndex SwNumberPortion::GetModelPositionForViewPoint(const sal_uInt16) const
516 {
517     return TextFrameIndex(0);
518 }
519 
Clone(const OUString & rExpand) const520 SwFieldPortion *SwNumberPortion::Clone( const OUString &rExpand ) const
521 {
522     std::unique_ptr<SwFont> pNewFnt;
523     if( m_pFont )
524         pNewFnt.reset(new SwFont( *m_pFont ));
525 
526     return new SwNumberPortion( rExpand, std::move(pNewFnt), IsLeft(), IsCenter(),
527                                 m_nMinDist, mbLabelAlignmentPosAndSpaceModeActive );
528 }
529 
530 /**
531  * We can create multiple NumFields
532  * Tricky, if one enters enough previous-text in the dialog box
533  * to cause the line to overflow
534  * We need to keep the Fly's evasion tactics in mind
535  */
Format(SwTextFormatInfo & rInf)536 bool SwNumberPortion::Format( SwTextFormatInfo &rInf )
537 {
538     SetHide( false );
539     const bool bFull = SwFieldPortion::Format( rInf );
540     SetLen(TextFrameIndex(0));
541     // a numbering portion can be contained in a rotated portion!!!
542     m_nFixWidth = rInf.IsMulti() ? Height() : Width();
543     rInf.SetNumDone( !rInf.GetRest() );
544     if( rInf.IsNumDone() )
545     {
546 //        SetAscent( rInf.GetAscent() );
547         OSL_ENSURE( Height() && mnAscent, "NumberPortions without Height | Ascent" );
548 
549         tools::Long nDiff( 0 );
550 
551         if ( !mbLabelAlignmentPosAndSpaceModeActive )
552         {
553             if (!rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) &&
554                  // #i32902#
555                  !IsFootnoteNumPortion() )
556             {
557                 nDiff = rInf.Left()
558                     + rInf.GetTextFrame()->GetTextNodeForParaProps()->
559                     GetSwAttrSet().GetLRSpace().GetTextFirstLineOffset()
560                     - rInf.First()
561                     + rInf.ForcedLeftMargin();
562             }
563             else
564             {
565                 nDiff = rInf.Left() - rInf.First() + rInf.ForcedLeftMargin();
566             }
567         }
568         // The text part of the numbering should always at least
569         // start at the left margin
570         if( nDiff < 0 )
571             nDiff = 0;
572         else if ( nDiff > rInf.X() )
573             nDiff -= rInf.X();
574         else
575             nDiff = 0;
576 
577         if( nDiff < m_nFixWidth + m_nMinDist )
578             nDiff = m_nFixWidth + m_nMinDist;
579 
580         // Numbering evades the Fly, no nDiff in the second round
581         // Tricky special case: FlyFrame is in an Area we're just about to
582         // acquire
583         // The NumberPortion is marked as hidden
584         const bool bFly = rInf.GetFly() ||
585             ( rInf.GetLast() && rInf.GetLast()->IsFlyPortion() );
586         if( nDiff > rInf.Width() )
587         {
588             nDiff = rInf.Width();
589             if ( bFly )
590                 SetHide( true );
591         }
592 
593         // A numbering portion can be inside a SwRotatedPortion. Then the
594         // Height has to be changed
595         if ( rInf.IsMulti() )
596         {
597             if ( Height() < nDiff )
598                 Height( sal_uInt16( nDiff ) );
599         }
600         else if( Width() < nDiff )
601             Width( sal_uInt16(nDiff) );
602     }
603     return bFull;
604 }
605 
606 
607 /**
608  * A FormatEOL indicates that the subsequent text did not fit onto
609  * the line anymore. In order for the Numbering to follow through,
610  * we hide this NumberPortion
611  */
FormatEOL(SwTextFormatInfo &)612 void SwNumberPortion::FormatEOL( SwTextFormatInfo& )
613 {
614 
615     // This caused trouble with flys anchored as characters.
616     // If one of these is numbered but does not fit to the line,
617     // it calls this function, causing a loop because both the number
618     // portion and the fly portion go to the next line
619 //    SetHide( true );
620 }
621 
622 
623 /**
624  * A hidden NumberPortion is not displayed, unless there are TextPortions in
625  * this line or there's just one line at all
626  */
Paint(const SwTextPaintInfo & rInf) const627 void SwNumberPortion::Paint( const SwTextPaintInfo &rInf ) const
628 {
629     if ( IsHide() && rInf.GetParaPortion() && rInf.GetParaPortion()->GetNext() )
630     {
631         SwLinePortion *pTmp = GetNextPortion();
632         while ( pTmp && !pTmp->InTextGrp() )
633             pTmp = pTmp->GetNextPortion();
634         if ( !pTmp )
635             return;
636     }
637 
638     // calculate the width of the number portion, including follows
639     const sal_uInt16 nOldWidth = Width();
640     sal_uInt16 nSumWidth = 0;
641     sal_uInt16 nOffset = 0;
642 
643     const SwLinePortion* pTmp = this;
644     while ( pTmp && pTmp->InNumberGrp() )
645     {
646         nSumWidth = nSumWidth + pTmp->Width();
647         if ( static_cast<const SwNumberPortion*>(pTmp)->HasFollow() )
648             pTmp = pTmp->GetNextPortion();
649         else
650         {
651             nOffset = pTmp->Width() - static_cast<const SwNumberPortion*>(pTmp)->m_nFixWidth;
652             break;
653         }
654     }
655 
656     // The master portion takes care for painting the background of the
657     // follow field portions
658     if ( ! IsFollow() )
659     {
660         SwNumberPortion *pThis = const_cast<SwNumberPortion*>(this);
661         pThis->Width( nSumWidth );
662         rInf.DrawViewOpt( *this, PortionType::Number );
663         pThis->Width( nOldWidth );
664     }
665 
666     if( m_aExpand.isEmpty() )
667         return;
668 
669     const SwFont *pTmpFnt = rInf.GetFont();
670     bool bPaintSpace = ( LINESTYLE_NONE != pTmpFnt->GetUnderline() ||
671                              LINESTYLE_NONE != pTmpFnt->GetOverline()  ||
672                              STRIKEOUT_NONE != pTmpFnt->GetStrikeout() ) &&
673                              !pTmpFnt->IsWordLineMode();
674     if( bPaintSpace && m_pFont )
675         bPaintSpace = ( LINESTYLE_NONE != m_pFont->GetUnderline() ||
676                         LINESTYLE_NONE != m_pFont->GetOverline()  ||
677                         STRIKEOUT_NONE != m_pFont->GetStrikeout() ) &&
678                         !m_pFont->IsWordLineMode();
679 
680     SwFontSave aSave( rInf, m_pFont.get() );
681 
682     if( m_nFixWidth == Width() && ! HasFollow() )
683         SwExpandPortion::Paint( rInf );
684     else
685     {
686         // logical const: reset width
687         SwNumberPortion *pThis = const_cast<SwNumberPortion*>(this);
688         bPaintSpace = bPaintSpace && m_nFixWidth < nOldWidth;
689         sal_uInt16 nSpaceOffs = m_nFixWidth;
690         pThis->Width( m_nFixWidth );
691 
692         if( ( IsLeft() && ! rInf.GetTextFrame()->IsRightToLeft() ) ||
693             ( ! IsLeft() && ! IsCenter() && rInf.GetTextFrame()->IsRightToLeft() ) )
694             SwExpandPortion::Paint( rInf );
695         else
696         {
697             SwTextPaintInfo aInf( rInf );
698             if( nOffset < m_nMinDist )
699                 nOffset = 0;
700             else
701             {
702                 if( IsCenter() )
703                 {
704                     /* #110778# a / 2 * 2 == a is not a tautology */
705                     sal_uInt16 nTmpOffset = nOffset;
706                     nOffset /= 2;
707                     if( nOffset < m_nMinDist )
708                         nOffset = nTmpOffset - m_nMinDist;
709                 }
710                 else
711                     nOffset = nOffset - m_nMinDist;
712             }
713             aInf.X( aInf.X() + nOffset );
714             SwExpandPortion::Paint( aInf );
715             if( bPaintSpace )
716                 nSpaceOffs = nSpaceOffs + nOffset;
717         }
718         if( bPaintSpace && nOldWidth > nSpaceOffs )
719         {
720             SwTextPaintInfo aInf( rInf );
721             aInf.X( aInf.X() + nSpaceOffs );
722 
723             // #i53199# Adjust position of underline:
724             if ( rInf.GetUnderFnt() )
725             {
726                 const Point aNewPos( aInf.GetPos().X(), rInf.GetUnderFnt()->GetPos().Y() );
727                 rInf.GetUnderFnt()->SetPos( aNewPos );
728             }
729 
730             pThis->Width( nOldWidth - nSpaceOffs + 12 );
731             {
732                 SwTextSlot aDiffText( &aInf, this, true, false, "  " );
733                 aInf.DrawText( *this, aInf.GetLen(), true );
734             }
735         }
736         pThis->Width( nOldWidth );
737     }
738 }
739 
SwBulletPortion(const sal_UCS4 cBullet,std::u16string_view rBulletFollowedBy,std::unique_ptr<SwFont> pFont,const bool bLft,const bool bCntr,const sal_uInt16 nMinDst,const bool bLabelAlignmentPosAndSpaceModeActive)740 SwBulletPortion::SwBulletPortion( const sal_UCS4 cBullet,
741                                   std::u16string_view rBulletFollowedBy,
742                                   std::unique_ptr<SwFont> pFont,
743                                   const bool bLft,
744                                   const bool bCntr,
745                                   const sal_uInt16 nMinDst,
746                                   const bool bLabelAlignmentPosAndSpaceModeActive )
747     : SwNumberPortion( OUString(&cBullet, 1) + rBulletFollowedBy,
748                        std::move(pFont), bLft, bCntr, nMinDst,
749                        bLabelAlignmentPosAndSpaceModeActive )
750 {
751     SetWhichPor( PortionType::Bullet );
752 }
753 
754 #define GRFNUM_SECURE 10
755 
SwGrfNumPortion(const OUString & rGraphicFollowedBy,const SvxBrushItem * pGrfBrush,OUString const & referer,const SwFormatVertOrient * pGrfOrient,const Size & rGrfSize,const bool bLft,const bool bCntr,const sal_uInt16 nMinDst,const bool bLabelAlignmentPosAndSpaceModeActive)756 SwGrfNumPortion::SwGrfNumPortion(
757         const OUString& rGraphicFollowedBy,
758         const SvxBrushItem* pGrfBrush, OUString const & referer,
759         const SwFormatVertOrient* pGrfOrient, const Size& rGrfSize,
760         const bool bLft, const bool bCntr, const sal_uInt16 nMinDst,
761         const bool bLabelAlignmentPosAndSpaceModeActive ) :
762     SwNumberPortion( rGraphicFollowedBy, nullptr, bLft, bCntr, nMinDst,
763                      bLabelAlignmentPosAndSpaceModeActive ),
764     m_pBrush( new SvxBrushItem(RES_BACKGROUND) ), m_nId( 0 )
765 {
766     SetWhichPor( PortionType::GrfNum );
767     SetAnimated( false );
768     m_bReplace = false;
769     if( pGrfBrush )
770     {
771         m_pBrush.reset(pGrfBrush->Clone());
772         const Graphic* pGraph = pGrfBrush->GetGraphic(referer);
773         if( pGraph )
774             SetAnimated( pGraph->IsAnimated() );
775         else
776             m_bReplace = true;
777     }
778     if( pGrfOrient )
779     {
780         m_nYPos = pGrfOrient->GetPos();
781         m_eOrient = pGrfOrient->GetVertOrient();
782     }
783     else
784     {
785         m_nYPos = 0;
786         m_eOrient = text::VertOrientation::TOP;
787     }
788     Width( static_cast<sal_uInt16>(rGrfSize.Width() + 2 * GRFNUM_SECURE) );
789     m_nFixWidth = Width();
790     m_nGrfHeight = rGrfSize.Height() + 2 * GRFNUM_SECURE;
791     Height( sal_uInt16(m_nGrfHeight) );
792     m_bNoPaint = false;
793 }
794 
~SwGrfNumPortion()795 SwGrfNumPortion::~SwGrfNumPortion()
796 {
797     if ( IsAnimated() )
798     {
799         Graphic* pGraph = const_cast<Graphic*>(m_pBrush->GetGraphic());
800         if (pGraph)
801             pGraph->StopAnimation( nullptr, m_nId );
802     }
803     m_pBrush.reset();
804 }
805 
StopAnimation(const OutputDevice * pOut)806 void SwGrfNumPortion::StopAnimation( const OutputDevice* pOut )
807 {
808     if ( IsAnimated() )
809     {
810         Graphic* pGraph = const_cast<Graphic*>(m_pBrush->GetGraphic());
811         if (pGraph)
812             pGraph->StopAnimation( pOut, m_nId );
813     }
814 }
815 
Format(SwTextFormatInfo & rInf)816 bool SwGrfNumPortion::Format( SwTextFormatInfo &rInf )
817 {
818     SetHide( false );
819 //    Width( nFixWidth );
820     sal_uInt16 nFollowedByWidth( 0 );
821     if ( mbLabelAlignmentPosAndSpaceModeActive )
822     {
823         SwFieldPortion::Format( rInf );
824         nFollowedByWidth = Width();
825         SetLen(TextFrameIndex(0));
826     }
827     Width( m_nFixWidth + nFollowedByWidth );
828     const bool bFull = rInf.Width() < rInf.X() + Width();
829     const bool bFly = rInf.GetFly() ||
830         ( rInf.GetLast() && rInf.GetLast()->IsFlyPortion() );
831     SetAscent( GetRelPos() > 0 ? GetRelPos() : 0 );
832     if( GetAscent() > Height() )
833         Height( GetAscent() );
834 
835     if( bFull )
836     {
837         Width( rInf.Width() - static_cast<sal_uInt16>(rInf.X()) );
838         if( bFly )
839         {
840             SetLen(TextFrameIndex(0));
841             m_bNoPaint = true;
842             rInf.SetNumDone( false );
843             return true;
844         }
845     }
846     rInf.SetNumDone( true );
847 //    long nDiff = rInf.Left() - rInf.First() + rInf.ForcedLeftMargin();
848     tools::Long nDiff = mbLabelAlignmentPosAndSpaceModeActive
849                  ? 0
850                  : rInf.Left() - rInf.First() + rInf.ForcedLeftMargin();
851     // The TextPortion should at least always start on the
852     // left margin
853     if( nDiff < 0 )
854         nDiff = 0;
855     else if ( nDiff > rInf.X() )
856         nDiff -= rInf.X();
857     if( nDiff < m_nFixWidth + m_nMinDist )
858         nDiff = m_nFixWidth + m_nMinDist;
859 
860     // Numbering evades Fly, no nDiff in the second round
861     // Tricky special case: FlyFrame is in the Area we were just
862     // about to get a hold of.
863     // The NumberPortion is marked as hidden
864     if( nDiff > rInf.Width() )
865     {
866         nDiff = rInf.Width();
867         if( bFly )
868             SetHide( true );
869     }
870 
871     if( Width() < nDiff )
872         Width( sal_uInt16(nDiff) );
873     return bFull;
874 }
875 
876 
877 /**
878  * A hidden NumberPortion is not displayed, unless there are TextPortions in
879  * this line or there's only one line at all
880  */
Paint(const SwTextPaintInfo & rInf) const881 void SwGrfNumPortion::Paint( const SwTextPaintInfo &rInf ) const
882 {
883     if( m_bNoPaint )
884         return;
885     if ( IsHide() && rInf.GetParaPortion() && rInf.GetParaPortion()->GetNext() )
886     {
887         SwLinePortion *pTmp = GetNextPortion();
888         while ( pTmp && !pTmp->InTextGrp() )
889             pTmp = pTmp->GetNextPortion();
890         if ( !pTmp )
891             return;
892     }
893     Point aPos( rInf.X() + GRFNUM_SECURE, rInf.Y() - GetRelPos() + GRFNUM_SECURE );
894     tools::Long nTmpWidth = std::max( tools::Long(0), static_cast<tools::Long>(m_nFixWidth - 2 * GRFNUM_SECURE) );
895     Size aSize( nTmpWidth, GetGrfHeight() - 2 * GRFNUM_SECURE );
896 
897     const bool bTmpLeft = mbLabelAlignmentPosAndSpaceModeActive ||
898                               ( IsLeft() && ! rInf.GetTextFrame()->IsRightToLeft() ) ||
899                               ( ! IsLeft() && ! IsCenter() && rInf.GetTextFrame()->IsRightToLeft() );
900 
901     if( m_nFixWidth < Width() && !bTmpLeft )
902     {
903         sal_uInt16 nOffset = Width() - m_nFixWidth;
904         if( nOffset < m_nMinDist )
905             nOffset = 0;
906         else
907         {
908             if( IsCenter() )
909             {
910                 nOffset /= 2;
911                 if( nOffset < m_nMinDist )
912                     nOffset = Width() - m_nFixWidth - m_nMinDist;
913             }
914             else
915                 nOffset = nOffset - m_nMinDist;
916         }
917         aPos.AdjustX(nOffset );
918     }
919 
920     if( m_bReplace )
921     {
922         const tools::Long nTmpH = GetNextPortion() ? GetNextPortion()->GetAscent() : 120;
923         aSize = Size( nTmpH, nTmpH );
924         aPos.setY( rInf.Y() - nTmpH );
925     }
926     SwRect aTmp( aPos, aSize );
927 
928     bool bDraw = true;
929 
930     if ( IsAnimated() )
931     {
932         bDraw = !rInf.GetOpt().IsGraphic();
933         if( !m_nId )
934         {
935             SetId( reinterpret_cast<sal_IntPtr>( rInf.GetTextFrame() ) );
936             rInf.GetTextFrame()->SetAnimation();
937         }
938         if( aTmp.IsOver( rInf.GetPaintRect() ) && !bDraw )
939         {
940             rInf.NoteAnimation();
941             const SwViewShell* pViewShell = rInf.GetVsh();
942 
943             // virtual device, not pdf export
944             if( OUTDEV_VIRDEV == rInf.GetOut()->GetOutDevType() &&
945                 pViewShell && pViewShell->GetWin()  )
946             {
947                 Graphic* pGraph = const_cast<Graphic*>(m_pBrush->GetGraphic());
948                 if (pGraph)
949                     pGraph->StopAnimation(nullptr,m_nId);
950                 rInf.GetTextFrame()->getRootFrame()->GetCurrShell()->InvalidateWindows( aTmp );
951             }
952 
953             else if ( pViewShell &&
954                      !pViewShell->GetAccessibilityOptions()->IsStopAnimatedGraphics() &&
955                      !pViewShell->IsPreview() &&
956                       // #i9684# Stop animation during printing/pdf export.
957                       pViewShell->GetWin() )
958             {
959                 Graphic* pGraph = const_cast<Graphic*>(m_pBrush->GetGraphic());
960                 if (pGraph)
961                 {
962                     const OutputDevice* pOut = rInf.GetOut();
963                     assert(pOut);
964                     pGraph->StartAnimation(
965                         *const_cast<OutputDevice*>(pOut), aPos, aSize, m_nId);
966                 }
967             }
968 
969             // pdf export, printing, preview, stop animations...
970             else
971                 bDraw = true;
972         }
973         if( bDraw )
974         {
975 
976             Graphic* pGraph = const_cast<Graphic*>(m_pBrush->GetGraphic());
977             if (pGraph)
978                 pGraph->StopAnimation( nullptr, m_nId );
979         }
980     }
981 
982     SwRect aRepaint( rInf.GetPaintRect() );
983     const SwTextFrame& rFrame = *rInf.GetTextFrame();
984     if( rFrame.IsVertical() )
985     {
986         rFrame.SwitchHorizontalToVertical( aTmp );
987         rFrame.SwitchHorizontalToVertical( aRepaint );
988     }
989 
990     if( rFrame.IsRightToLeft() )
991     {
992         rFrame.SwitchLTRtoRTL( aTmp );
993         rFrame.SwitchLTRtoRTL( aRepaint );
994     }
995 
996     if( bDraw && aTmp.HasArea() )
997     {
998         const OutputDevice* pOut = rInf.GetOut();
999         assert(pOut);
1000         DrawGraphic( m_pBrush.get(), *const_cast<OutputDevice*>(pOut),
1001             aTmp, aRepaint, m_bReplace ? GRFNUM_REPLACE : GRFNUM_YES );
1002     }
1003 }
1004 
SetBase(tools::Long nLnAscent,tools::Long nLnDescent,tools::Long nFlyAsc,tools::Long nFlyDesc)1005 void SwGrfNumPortion::SetBase( tools::Long nLnAscent, tools::Long nLnDescent,
1006                                tools::Long nFlyAsc, tools::Long nFlyDesc )
1007 {
1008     if ( GetOrient() == text::VertOrientation::NONE )
1009         return;
1010 
1011     SetRelPos( 0 );
1012     if ( GetOrient() == text::VertOrientation::CENTER )
1013         SetRelPos( GetGrfHeight() / 2 );
1014     else if ( GetOrient() == text::VertOrientation::TOP )
1015         SetRelPos( GetGrfHeight() - GRFNUM_SECURE );
1016     else if ( GetOrient() == text::VertOrientation::BOTTOM )
1017         ;
1018     else if ( GetOrient() == text::VertOrientation::CHAR_CENTER )
1019         SetRelPos( ( GetGrfHeight() + nLnAscent - nLnDescent ) / 2 );
1020     else if ( GetOrient() == text::VertOrientation::CHAR_TOP )
1021         SetRelPos( nLnAscent );
1022     else if ( GetOrient() == text::VertOrientation::CHAR_BOTTOM )
1023         SetRelPos( GetGrfHeight() - nLnDescent );
1024     else
1025     {
1026         if( GetGrfHeight() >= nFlyAsc + nFlyDesc )
1027         {
1028             // If I'm as large as the line, I do not need to adjust
1029             // at the line; I'll leave the max. ascent unchanged
1030             SetRelPos( nFlyAsc );
1031         }
1032         else if ( GetOrient() == text::VertOrientation::LINE_CENTER )
1033             SetRelPos( ( GetGrfHeight() + nFlyAsc - nFlyDesc ) / 2 );
1034         else if ( GetOrient() == text::VertOrientation::LINE_TOP )
1035             SetRelPos( nFlyAsc );
1036         else if ( GetOrient() == text::VertOrientation::LINE_BOTTOM )
1037             SetRelPos( GetGrfHeight() - nFlyDesc );
1038     }
1039 }
1040 
StopAnimation(const OutputDevice * pOut)1041 void SwTextFrame::StopAnimation( const OutputDevice* pOut )
1042 {
1043     OSL_ENSURE( HasAnimation(), "SwTextFrame::StopAnimation: Which Animation?" );
1044     if( !HasPara() )
1045         return;
1046 
1047     SwLineLayout *pLine = GetPara();
1048     while( pLine )
1049     {
1050         SwLinePortion *pPor = pLine->GetNextPortion();
1051         while( pPor )
1052         {
1053             if( pPor->IsGrfNumPortion() )
1054                 static_cast<SwGrfNumPortion*>(pPor)->StopAnimation( pOut );
1055             // The NumberPortion is always at the first char,
1056             // which means we can cancel as soon as we've reached a portion
1057             // with a length > 0
1058             pPor = pPor->GetLen() ? nullptr : pPor->GetNextPortion();
1059         }
1060         pLine = pLine->GetLen() ? nullptr : pLine->GetNext();
1061     }
1062 }
1063 
1064 /**
1065  * Initializes the script array and clears the width array
1066  */
SwCombinedPortion(const OUString & rText)1067 SwCombinedPortion::SwCombinedPortion( const OUString &rText )
1068     : SwFieldPortion( rText )
1069     , m_nUpPos(0)
1070     , m_nLowPos(0)
1071     , m_nProportion(55)
1072 {
1073     SetLen(TextFrameIndex(1));
1074     SetWhichPor( PortionType::Combined );
1075     if( m_aExpand.getLength() > 6 )
1076         m_aExpand = m_aExpand.copy( 0, 6 );
1077 
1078     // Initialization of the scripttype array,
1079     // the arrays of width and position are filled by the format function
1080     assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
1081 
1082     SwFontScript nScr = SW_SCRIPTS;
1083     for( sal_Int32 i = 0; i < rText.getLength(); ++i )
1084     {
1085         switch ( g_pBreakIt->GetBreakIter()->getScriptType( rText, i ) ) {
1086             case i18n::ScriptType::LATIN : nScr = SwFontScript::Latin; break;
1087             case i18n::ScriptType::ASIAN : nScr = SwFontScript::CJK; break;
1088             case i18n::ScriptType::COMPLEX : nScr = SwFontScript::CTL; break;
1089         }
1090         m_aScrType[i] = nScr;
1091     }
1092 }
1093 
Paint(const SwTextPaintInfo & rInf) const1094 void SwCombinedPortion::Paint( const SwTextPaintInfo &rInf ) const
1095 {
1096     OSL_ENSURE(GetLen() <= TextFrameIndex(1), "SwFieldPortion::Paint: rest-portion pollution?");
1097     if( !Width() )
1098         return;
1099 
1100     rInf.DrawBackBrush( *this );
1101     rInf.DrawViewOpt( *this, PortionType::Field );
1102 
1103     // do we have to repaint a post it portion?
1104     if( rInf.OnWin() && mpNextPortion && !mpNextPortion->Width() )
1105         mpNextPortion->PrePaint( rInf, this );
1106 
1107     const sal_Int32 nCount = m_aExpand.getLength();
1108     if( !nCount )
1109         return;
1110     OSL_ENSURE( nCount < 7, "Too much combined characters" );
1111 
1112     // the first character of the second row
1113     const sal_Int32 nTop = ( nCount + 1 ) / 2;
1114 
1115     SwFont aTmpFont( *rInf.GetFont() );
1116     aTmpFont.SetProportion( m_nProportion );  // a smaller font
1117     SwFontSave aFontSave( rInf, &aTmpFont );
1118 
1119     Point aOldPos = rInf.GetPos();
1120     Point aOutPos( aOldPos.X(), aOldPos.Y() - m_nUpPos );// Y of the first row
1121     for( sal_Int32 i = 0 ; i < nCount; ++i )
1122     {
1123         if( i == nTop ) // change the row
1124             aOutPos.setY( aOldPos.Y() + m_nLowPos );    // Y of the second row
1125         aOutPos.setX( aOldPos.X() + m_aPos[i] );        // X position
1126         const SwFontScript nAct = m_aScrType[i];        // script type
1127         aTmpFont.SetActual( nAct );
1128 
1129         // if there're more than 4 characters to display, we choose fonts
1130         // with 2/3 of the original font width.
1131         if( m_aWidth[ nAct ] )
1132         {
1133             Size aTmpSz = aTmpFont.GetSize( nAct );
1134             if( aTmpSz.Width() != m_aWidth[ nAct ] )
1135             {
1136                 aTmpSz.setWidth( m_aWidth[ nAct ] );
1137                 aTmpFont.SetSize( aTmpSz, nAct );
1138             }
1139         }
1140         const_cast<SwTextPaintInfo&>(rInf).SetPos( aOutPos );
1141         rInf.DrawText(m_aExpand, *this, TextFrameIndex(i), TextFrameIndex(1));
1142     }
1143     // rInf is const, so we have to take back our manipulations
1144     const_cast<SwTextPaintInfo&>(rInf).SetPos( aOldPos );
1145 
1146 }
1147 
Format(SwTextFormatInfo & rInf)1148 bool SwCombinedPortion::Format( SwTextFormatInfo &rInf )
1149 {
1150     const sal_Int32 nCount = m_aExpand.getLength();
1151     if( !nCount )
1152     {
1153         Width( 0 );
1154         return false;
1155     }
1156 
1157     OSL_ENSURE( nCount < 7, "Too much combined characters" );
1158 
1159     // If there are leading "weak"-scripttyped characters in this portion,
1160     // they get the actual scripttype.
1161     for( sal_Int32 i = 0; i < nCount && SW_SCRIPTS == m_aScrType[i]; ++i )
1162         m_aScrType[i] = rInf.GetFont()->GetActual();
1163     if( nCount > 4 )
1164     {
1165         // more than four? Ok, then we need the 2/3 font width
1166         for( sal_Int32 i = 0; i < m_aExpand.getLength(); ++i )
1167         {
1168             OSL_ENSURE( m_aScrType[i] < SW_SCRIPTS, "Combined: Script fault" );
1169             if( !m_aWidth[ m_aScrType[i] ] )
1170             {
1171                 rInf.GetOut()->SetFont( rInf.GetFont()->GetFnt( m_aScrType[i] ) );
1172                 m_aWidth[ m_aScrType[i] ] =
1173                         static_cast<sal_uInt16>(2 * rInf.GetOut()->GetFontMetric().GetFontSize().Width() / 3);
1174             }
1175         }
1176     }
1177 
1178     const sal_Int32 nTop = ( nCount + 1 ) / 2; // the first character of the second line
1179     SwViewShell *pSh = rInf.GetTextFrame()->getRootFrame()->GetCurrShell();
1180     SwFont aTmpFont( *rInf.GetFont() );
1181     SwFontSave aFontSave( rInf, &aTmpFont );
1182     m_nProportion = 55;
1183     // In nMainAscent/Descent we store the ascent and descent
1184     // of the original surrounding font
1185     sal_uInt16 nMaxDescent, nMaxAscent, nMaxWidth;
1186     sal_uInt16 nMainDescent = rInf.GetFont()->GetHeight( pSh, *rInf.GetOut() );
1187     const sal_uInt16 nMainAscent = rInf.GetFont()->GetAscent( pSh, *rInf.GetOut() );
1188     nMainDescent = nMainDescent - nMainAscent;
1189     // we start with a 50% font, but if we notice that the combined portion
1190     // becomes bigger than the surrounding font, we check 45% and maybe 40%.
1191     do
1192     {
1193         m_nProportion -= 5;
1194         aTmpFont.SetProportion( m_nProportion );
1195         memset( &m_aPos, 0, sizeof(m_aPos) );
1196         nMaxDescent = 0;
1197         nMaxAscent = 0;
1198         nMaxWidth = 0;
1199         m_nUpPos = m_nLowPos = 0;
1200 
1201         // Now we get the width of all characters.
1202         // The ascent and the width of the first line are stored in the
1203         // ascent member of the portion, the descent in nLowPos.
1204         // The ascent, descent and width of the second line are stored in the
1205         // local nMaxAscent, nMaxDescent and nMaxWidth variables.
1206         for( sal_Int32 i = 0; i < nCount; ++i )
1207         {
1208             SwFontScript nScrp = m_aScrType[i];
1209             aTmpFont.SetActual( nScrp );
1210             if( m_aWidth[ nScrp ] )
1211             {
1212                 Size aFontSize( aTmpFont.GetSize( nScrp ) );
1213                 aFontSize.setWidth( m_aWidth[ nScrp ] );
1214                 aTmpFont.SetSize( aFontSize, nScrp );
1215             }
1216 
1217             SwDrawTextInfo aDrawInf(pSh, *rInf.GetOut(), m_aExpand, i, 1);
1218             Size aSize = aTmpFont.GetTextSize_( aDrawInf );
1219             const sal_uInt16 nAsc = aTmpFont.GetAscent( pSh, *rInf.GetOut() );
1220             m_aPos[ i ] = static_cast<sal_uInt16>(aSize.Width());
1221             if( i == nTop ) // enter the second line
1222             {
1223                 m_nLowPos = nMaxDescent;
1224                 Height( nMaxDescent + nMaxAscent );
1225                 Width( nMaxWidth );
1226                 SetAscent( nMaxAscent );
1227                 nMaxAscent = 0;
1228                 nMaxDescent = 0;
1229                 nMaxWidth = 0;
1230             }
1231             nMaxWidth = nMaxWidth + m_aPos[ i ];
1232             if( nAsc > nMaxAscent )
1233                 nMaxAscent = nAsc;
1234             if( aSize.Height() - nAsc > nMaxDescent )
1235                 nMaxDescent = static_cast<sal_uInt16>(aSize.Height() - nAsc);
1236         }
1237         // for one or two characters we double the width of the portion
1238         if( nCount < 3 )
1239         {
1240             nMaxWidth *= 2;
1241             Width( 2*Width() );
1242             if( nCount < 2 )
1243             {
1244                 Height( nMaxAscent + nMaxDescent );
1245                 m_nLowPos = nMaxDescent;
1246             }
1247         }
1248         Height( Height() + nMaxDescent + nMaxAscent );
1249         m_nUpPos = nMaxAscent;
1250         SetAscent( Height() - nMaxDescent - m_nLowPos );
1251     } while( m_nProportion > 40 && ( GetAscent() > nMainAscent ||
1252                                     Height() - GetAscent() > nMainDescent ) );
1253     // if the combined portion is smaller than the surrounding text,
1254     // the portion grows. This looks better, if there's a character background.
1255     if( GetAscent() < nMainAscent )
1256     {
1257         Height( Height() + nMainAscent - GetAscent() );
1258         SetAscent( nMainAscent );
1259     }
1260     if( Height() < nMainAscent + nMainDescent )
1261         Height( nMainAscent + nMainDescent );
1262 
1263     // We calculate the x positions of the characters in both lines...
1264     sal_uInt16 nTopDiff = 0;
1265     sal_uInt16 nBotDiff = 0;
1266     if( nMaxWidth > Width() )
1267     {
1268         nTopDiff = ( nMaxWidth - Width() ) / 2;
1269         Width( nMaxWidth );
1270     }
1271     else
1272         nBotDiff = ( Width() - nMaxWidth ) / 2;
1273     switch( nTop)
1274     {
1275         case 3: m_aPos[1] = m_aPos[0] + nTopDiff;
1276             [[fallthrough]];
1277         case 2: m_aPos[nTop-1] = Width() - m_aPos[nTop-1];
1278     }
1279     m_aPos[0] = 0;
1280     switch( nCount )
1281     {
1282         case 5: m_aPos[4] = m_aPos[3] + nBotDiff;
1283             [[fallthrough]];
1284         case 3: m_aPos[nTop] = nBotDiff;          break;
1285         case 6: m_aPos[4] = m_aPos[3] + nBotDiff;
1286             [[fallthrough]];
1287         case 4: m_aPos[nTop] = 0;
1288             [[fallthrough]];
1289         case 2: m_aPos[nCount-1] = Width() - m_aPos[nCount-1];
1290     }
1291 
1292     // Does the combined portion fit the line?
1293     const bool bFull = rInf.Width() < rInf.X() + Width();
1294     if( bFull )
1295     {
1296         if( rInf.GetLineStart() == rInf.GetIdx() && (!rInf.GetLast()->InFieldGrp()
1297             || !static_cast<SwFieldPortion*>(rInf.GetLast())->IsFollow() ) )
1298             Width( static_cast<sal_uInt16>( rInf.Width() - rInf.X() ) );
1299         else
1300         {
1301             Truncate();
1302             Width( 0 );
1303             SetLen(TextFrameIndex(0));
1304             if( rInf.GetLast() )
1305                 rInf.GetLast()->FormatEOL( rInf );
1306         }
1307     }
1308     return bFull;
1309 }
1310 
GetViewWidth(const SwTextSizeInfo & rInf) const1311 sal_uInt16 SwCombinedPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const
1312 {
1313     if( !GetLen() ) // for the dummy part at the end of the line, where
1314         return 0;   // the combined portion doesn't fit.
1315     return SwFieldPortion::GetViewWidth( rInf );
1316 }
1317 
Clone(const OUString & rExpand) const1318 SwFieldPortion *SwFieldFormDropDownPortion::Clone(const OUString &rExpand) const
1319 {
1320     return new SwFieldFormDropDownPortion(m_pFieldMark, rExpand);
1321 }
1322 
Paint(const SwTextPaintInfo & rInf) const1323 void SwFieldFormDropDownPortion::Paint( const SwTextPaintInfo &rInf ) const
1324 {
1325     SwFieldPortion::Paint( rInf );
1326 
1327     ::sw::mark::DropDownFieldmark* pDropDownField = dynamic_cast< ::sw::mark::DropDownFieldmark* >(m_pFieldMark);
1328     if(pDropDownField)
1329     {
1330         SwRect aPaintArea;
1331         rInf.CalcRect( *this, &aPaintArea );
1332         pDropDownField->SetPortionPaintArea(aPaintArea);
1333     }
1334 }
1335 
Clone(const OUString &) const1336 SwFieldPortion *SwFieldFormDatePortion::Clone(const OUString &/*rExpand*/) const
1337 {
1338     return new SwFieldFormDatePortion(m_pFieldMark, m_bStart);
1339 }
1340 
Paint(const SwTextPaintInfo & rInf) const1341 void SwFieldFormDatePortion::Paint( const SwTextPaintInfo &rInf ) const
1342 {
1343     SwFieldPortion::Paint( rInf );
1344 
1345     ::sw::mark::DateFieldmark* pDateField = dynamic_cast< ::sw::mark::DateFieldmark* >(m_pFieldMark);
1346     if(pDateField)
1347     {
1348         SwRect aPaintArea;
1349         rInf.CalcRect( *this, &aPaintArea );
1350         if(m_bStart)
1351             pDateField->SetPortionPaintAreaStart(aPaintArea);
1352         else
1353             pDateField->SetPortionPaintAreaEnd(aPaintArea);
1354     }
1355 }
1356 
1357 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1358