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 #include <vcl/metric.hxx>
22 #include <vcl/svapp.hxx>
23 #include <paratr.hxx>
24 #include <txtfrm.hxx>
25 #include <charfmt.hxx>
26 #include <viewopt.hxx>
27 #include <viewsh.hxx>
28 #include "pordrop.hxx"
29 #include "itrform2.hxx"
30 #include "txtpaint.hxx"
31 #include <blink.hxx>
32 #include <breakit.hxx>
33 #include <com/sun/star/i18n/ScriptType.hpp>
34 #include <com/sun/star/i18n/WordType.hpp>
35 #include <com/sun/star/i18n/XBreakIterator.hpp>
36 #include <editeng/langitem.hxx>
37 #include <charatr.hxx>
38 #include <editeng/fhgtitem.hxx>
39 #include <calbck.hxx>
40 #include <doc.hxx>
41 
42 using namespace ::com::sun::star::i18n;
43 using namespace ::com::sun::star;
44 
45 /**
46  * Calculates if a drop caps portion intersects with a fly
47  * The width and height of the drop caps portion are passed as arguments,
48  * the position is calculated from the values in rInf
49  */
lcl_IsDropFlyInter(const SwTextFormatInfo & rInf,sal_uInt16 nWidth,sal_uInt16 nHeight)50 static bool lcl_IsDropFlyInter( const SwTextFormatInfo &rInf,
51                              sal_uInt16 nWidth, sal_uInt16 nHeight )
52 {
53     const SwTextFly& rTextFly = rInf.GetTextFly();
54     if( rTextFly.IsOn() )
55     {
56         SwRect aRect( rInf.GetTextFrame()->getFrameArea().Pos(), Size( nWidth, nHeight) );
57         aRect.Pos() += rInf.GetTextFrame()->getFramePrintArea().Pos();
58         aRect.Pos().AdjustX(rInf.X() );
59         aRect.Pos().setY( rInf.Y() );
60         aRect = rTextFly.GetFrame( aRect );
61         return aRect.HasArea();
62     }
63 
64     return false;
65 }
66 
67 class SwDropSave
68 {
69     SwTextPaintInfo* pInf;
70     sal_Int32 const nIdx;
71     sal_Int32 const nLen;
72     long const nX;
73     long const nY;
74 
75 public:
76     explicit SwDropSave( const SwTextPaintInfo &rInf );
77     ~SwDropSave();
78 };
79 
SwDropSave(const SwTextPaintInfo & rInf)80 SwDropSave::SwDropSave( const SwTextPaintInfo &rInf ) :
81         pInf( const_cast<SwTextPaintInfo*>(&rInf) ), nIdx( rInf.GetIdx() ),
82         nLen( rInf.GetLen() ), nX( rInf.X() ), nY( rInf.Y() )
83 {
84 }
85 
~SwDropSave()86 SwDropSave::~SwDropSave()
87 {
88     pInf->SetIdx(TextFrameIndex(nIdx));
89     pInf->SetLen(TextFrameIndex(nLen));
90     pInf->X( nX );
91     pInf->Y( nY );
92 }
93 
94 /// SwDropPortionPart DTor
~SwDropPortionPart()95 SwDropPortionPart::~SwDropPortionPart()
96 {
97     pFollow.reset();
98     pFnt.reset();
99 }
100 
101 /// SwDropPortion CTor, DTor
SwDropPortion(const sal_uInt16 nLineCnt,const sal_uInt16 nDrpHeight,const sal_uInt16 nDrpDescent,const sal_uInt16 nDist)102 SwDropPortion::SwDropPortion( const sal_uInt16 nLineCnt,
103                               const sal_uInt16 nDrpHeight,
104                               const sal_uInt16 nDrpDescent,
105                               const sal_uInt16 nDist )
106   : nLines( nLineCnt ),
107     nDropHeight(nDrpHeight),
108     nDropDescent(nDrpDescent),
109     nDistance(nDist),
110     nFix(0),
111     nY(0)
112 {
113     SetWhichPor( PortionType::Drop );
114 }
115 
~SwDropPortion()116 SwDropPortion::~SwDropPortion()
117 {
118     pPart.reset();
119     if( pBlink )
120         pBlink->Delete( this );
121 }
122 
123 /// nWishLen = 0 indicates that we want a whole word
GetDropLen(sal_Int32 nWishLen) const124 sal_Int32 SwTextNode::GetDropLen( sal_Int32 nWishLen ) const
125 {
126     sal_Int32 nEnd = GetText().getLength();
127     if( nWishLen && nWishLen < nEnd )
128         nEnd = nWishLen;
129 
130     if (! nWishLen)
131     {
132         // find first word
133         const SwAttrSet& rAttrSet = GetSwAttrSet();
134         const sal_uInt16 nTextScript = g_pBreakIt->GetRealScriptOfText( GetText(), 0 );
135 
136         LanguageType eLanguage;
137 
138         switch ( nTextScript )
139         {
140         case i18n::ScriptType::ASIAN :
141             eLanguage = rAttrSet.GetCJKLanguage().GetLanguage();
142             break;
143         case i18n::ScriptType::COMPLEX :
144             eLanguage = rAttrSet.GetCTLLanguage().GetLanguage();
145             break;
146         default :
147             eLanguage = rAttrSet.GetLanguage().GetLanguage();
148             break;
149         }
150 
151         Boundary aBound =
152             g_pBreakIt->GetBreakIter()->getWordBoundary( GetText(), 0,
153             g_pBreakIt->GetLocale( eLanguage ), WordType::DICTIONARY_WORD, true );
154 
155         nEnd = aBound.endPos;
156     }
157 
158     sal_Int32 i = 0;
159     for( ; i < nEnd; ++i )
160     {
161         sal_Unicode const cChar = GetText()[i];
162         if( CH_TAB == cChar || CH_BREAK == cChar ||
163             (( CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar )
164                 && GetTextAttrForCharAt(i)) )
165             break;
166     }
167     return i;
168 }
169 
170 /// nWishLen = 0 indicates that we want a whole word
GetDropLen(TextFrameIndex const nWishLen) const171 TextFrameIndex SwTextFrame::GetDropLen(TextFrameIndex const nWishLen) const
172 {
173     TextFrameIndex nEnd(GetText().getLength());
174     if (nWishLen && nWishLen < nEnd)
175         nEnd = nWishLen;
176 
177     if (! nWishLen)
178     {
179         // find first word
180         const SwAttrSet& rAttrSet = GetTextNodeForParaProps()->GetSwAttrSet();
181         const sal_uInt16 nTextScript = g_pBreakIt->GetRealScriptOfText(GetText(), 0);
182 
183         LanguageType eLanguage;
184 
185         switch ( nTextScript )
186         {
187         case i18n::ScriptType::ASIAN :
188             eLanguage = rAttrSet.GetCJKLanguage().GetLanguage();
189             break;
190         case i18n::ScriptType::COMPLEX :
191             eLanguage = rAttrSet.GetCTLLanguage().GetLanguage();
192             break;
193         default :
194             eLanguage = rAttrSet.GetLanguage().GetLanguage();
195             break;
196         }
197 
198         Boundary aBound = g_pBreakIt->GetBreakIter()->getWordBoundary(
199             GetText(), 0, g_pBreakIt->GetLocale(eLanguage),
200             WordType::DICTIONARY_WORD, true );
201 
202         nEnd = TextFrameIndex(aBound.endPos);
203     }
204 
205     TextFrameIndex i(0);
206     for ( ; i < nEnd; ++i)
207     {
208         sal_Unicode const cChar = GetText()[sal_Int32(i)];
209         if (CH_TAB == cChar || CH_BREAK == cChar ||
210             CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar)
211         {
212 #ifndef NDEBUG
213             if (CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar)
214             {
215                 std::pair<SwTextNode const*, sal_Int32> const pos(MapViewToModel(i));
216                 assert(pos.first->GetTextAttrForCharAt(pos.second) != nullptr);
217             }
218 #endif
219             break;
220         }
221     }
222     return i;
223 }
224 
225 /**
226  * If a dropcap is found the return value is true otherwise false. The
227  * drop cap sizes passed back by reference are font height, drop height
228  * and drop descent.
229  */
GetDropSize(int & rFontHeight,int & rDropHeight,int & rDropDescent) const230 bool SwTextNode::GetDropSize(int& rFontHeight, int& rDropHeight, int& rDropDescent) const
231 {
232     rFontHeight = 0;
233     rDropHeight = 0;
234     rDropDescent =0;
235 
236     const SwAttrSet& rSet = GetSwAttrSet();
237     const SwFormatDrop& rDrop = rSet.GetDrop();
238 
239     // Return (0,0) if there is no drop cap at this paragraph
240     if( 1 >= rDrop.GetLines() ||
241         ( !rDrop.GetChars() && !rDrop.GetWholeWord() ) )
242     {
243         return false;
244     }
245 
246     // get text frame
247     SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this);
248     for( SwTextFrame* pLastFrame = aIter.First(); pLastFrame; pLastFrame = aIter.Next() )
249     {
250         // Only (master-) text frames can have a drop cap.
251         if (!pLastFrame->IsFollow() &&
252             pLastFrame->GetTextNodeForFirstText() == this)
253         {
254 
255             if( !pLastFrame->HasPara() )
256                 pLastFrame->GetFormatted();
257 
258             if ( !pLastFrame->IsEmpty() )
259             {
260                 const SwParaPortion* pPara = pLastFrame->GetPara();
261                 OSL_ENSURE( pPara, "GetDropSize could not find the ParaPortion, I'll guess the drop cap size" );
262 
263                 if ( pPara )
264                 {
265                     const SwLinePortion* pFirstPor = pPara->GetFirstPortion();
266                     if (pFirstPor && pFirstPor->IsDropPortion())
267                     {
268                         const SwDropPortion* pDrop = static_cast<const SwDropPortion*>(pFirstPor);
269                         rDropHeight = pDrop->GetDropHeight();
270                         rDropDescent = pDrop->GetDropDescent();
271                         if (const SwFont *pFont = pDrop->GetFnt())
272                             rFontHeight = pFont->GetSize(pFont->GetActual()).Height();
273                         else
274                         {
275                             const SvxFontHeightItem& rItem = rSet.Get(RES_CHRATR_FONTSIZE);
276                             rFontHeight = rItem.GetHeight();
277                         }
278                     }
279                 }
280             }
281             break;
282         }
283     }
284 
285     if (rFontHeight==0 && rDropHeight==0 && rDropDescent==0)
286     {
287         const sal_uInt16 nLines = rDrop.GetLines();
288 
289         const SvxFontHeightItem& rItem = rSet.Get( RES_CHRATR_FONTSIZE );
290         rFontHeight = rItem.GetHeight();
291         rDropHeight = nLines * rFontHeight;
292         rDropDescent = rFontHeight / 5;
293         return false;
294     }
295 
296     return true;
297 }
298 
299 /// Manipulate the width, otherwise the chars are being stretched
PaintText(const SwTextPaintInfo & rInf) const300 void SwDropPortion::PaintText( const SwTextPaintInfo &rInf ) const
301 {
302     OSL_ENSURE( nDropHeight && pPart && nLines != 1, "Drop Portion painted twice" );
303 
304     const SwDropPortionPart* pCurrPart = GetPart();
305     const TextFrameIndex nOldLen = GetLen();
306     const sal_uInt16 nOldWidth = Width();
307     const sal_uInt16 nOldAscent = GetAscent();
308 
309     const SwTwips nBasePosY  = rInf.Y();
310     const_cast<SwTextPaintInfo&>(rInf).Y( nBasePosY + nY );
311     const_cast<SwDropPortion*>(this)->SetAscent( nOldAscent + nY );
312     SwDropSave aSave( rInf );
313     // for text inside drop portions we let vcl handle the text directions
314     SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() );
315     aLayoutModeModifier.SetAuto();
316 
317     while ( pCurrPart )
318     {
319         const_cast<SwDropPortion*>(this)->SetLen( pCurrPart->GetLen() );
320         const_cast<SwDropPortion*>(this)->Width( pCurrPart->GetWidth() );
321         const_cast<SwTextPaintInfo&>(rInf).SetLen( pCurrPart->GetLen() );
322         SwFontSave aFontSave( rInf, &pCurrPart->GetFont() );
323         const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(pCurrPart->GetJoinBorderWithNext());
324         const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(pCurrPart->GetJoinBorderWithPrev());
325 
326         if ( rInf.OnWin() &&
327             !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && SwViewOption::IsFieldShadings() &&
328             (!pCurrPart->GetFont().GetBackColor() || *pCurrPart->GetFont().GetBackColor() == COL_TRANSPARENT) )
329         {
330             rInf.DrawBackground( *this );
331         }
332 
333         SwTextPortion::Paint( rInf );
334 
335         const_cast<SwTextPaintInfo&>(rInf).SetIdx( rInf.GetIdx() + pCurrPart->GetLen() );
336         const_cast<SwTextPaintInfo&>(rInf).X( rInf.X() + pCurrPart->GetWidth() );
337         pCurrPart = pCurrPart->GetFollow();
338     }
339 
340     const_cast<SwTextPaintInfo&>(rInf).Y( nBasePosY );
341     const_cast<SwDropPortion*>(this)->Width( nOldWidth );
342     const_cast<SwDropPortion*>(this)->SetLen( nOldLen );
343     const_cast<SwDropPortion*>(this)->SetAscent( nOldAscent );
344     const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(false);
345     const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(false);
346 }
347 
PaintDrop(const SwTextPaintInfo & rInf) const348 void SwDropPortion::PaintDrop( const SwTextPaintInfo &rInf ) const
349 {
350     // normal output is being done during the normal painting
351     if( ! nDropHeight || ! pPart || nLines == 1 )
352         return;
353 
354     // set the lying values
355     const sal_uInt16 nOldHeight = Height();
356     const sal_uInt16 nOldWidth  = Width();
357     const sal_uInt16 nOldAscent = GetAscent();
358     const SwTwips nOldPosY  = rInf.Y();
359     const SwTwips nOldPosX  = rInf.X();
360     const SwParaPortion *pPara = rInf.GetParaPortion();
361     const Point aOutPos( nOldPosX, nOldPosY - pPara->GetAscent()
362                          - pPara->GetRealHeight() + pPara->Height() );
363     // make good for retouching
364 
365     // Set baseline
366     const_cast<SwTextPaintInfo&>(rInf).Y( aOutPos.Y() + nDropHeight );
367 
368     // for background
369     const_cast<SwDropPortion*>(this)->Height( nDropHeight + nDropDescent );
370     const_cast<SwDropPortion*>(this)->SetAscent( nDropHeight );
371 
372     // Always adapt Clipregion to us, never set it off using the existing ClipRect
373     // as that could be set for the line
374     SwRect aClipRect;
375     if ( rInf.OnWin() )
376     {
377         aClipRect = SwRect( aOutPos, SvLSize() );
378         aClipRect.Intersection( rInf.GetPaintRect() );
379     }
380     SwSaveClip aClip( const_cast<OutputDevice*>(rInf.GetOut()) );
381     aClip.ChgClip( aClipRect, rInf.GetTextFrame() );
382 
383     // Just do, what we always do ...
384     PaintText( rInf );
385 
386     // save old values
387     const_cast<SwDropPortion*>(this)->Height( nOldHeight );
388     const_cast<SwDropPortion*>(this)->Width( nOldWidth );
389     const_cast<SwDropPortion*>(this)->SetAscent( nOldAscent );
390     const_cast<SwTextPaintInfo&>(rInf).Y( nOldPosY );
391 }
392 
Paint(const SwTextPaintInfo & rInf) const393 void SwDropPortion::Paint( const SwTextPaintInfo &rInf ) const
394 {
395     // normal output is being done here
396     if( ! nDropHeight || ! pPart || 1 == nLines )
397     {
398         if ( rInf.OnWin() &&
399             !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && SwViewOption::IsFieldShadings()       )
400             rInf.DrawBackground( *this );
401 
402         // make sure that font is not rotated
403         std::unique_ptr<SwFont> pTmpFont;
404         if ( rInf.GetFont()->GetOrientation( rInf.GetTextFrame()->IsVertical() ) )
405         {
406             pTmpFont.reset(new SwFont( *rInf.GetFont() ));
407             pTmpFont->SetVertical( 0, rInf.GetTextFrame()->IsVertical() );
408         }
409 
410         SwFontSave aFontSave( rInf, pTmpFont.get() );
411         // for text inside drop portions we let vcl handle the text directions
412         SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() );
413         aLayoutModeModifier.SetAuto();
414 
415         SwTextPortion::Paint( rInf );
416     }
417 }
418 
FormatText(SwTextFormatInfo & rInf)419 bool SwDropPortion::FormatText( SwTextFormatInfo &rInf )
420 {
421     const TextFrameIndex nOldLen = GetLen();
422     const TextFrameIndex nOldInfLen = rInf.GetLen();
423     if (!SwTextPortion::Format( rInf ))
424         return false;
425 
426     // looks like shit, but what can we do?
427     rInf.SetUnderflow( nullptr );
428     Truncate();
429     SetLen( nOldLen );
430     rInf.SetLen( nOldInfLen );
431 
432     return true;
433 }
434 
GetTextSize(const SwTextSizeInfo & rInf) const435 SwPosSize SwDropPortion::GetTextSize( const SwTextSizeInfo &rInf ) const
436 {
437     sal_uInt16 nMyX = 0;
438     TextFrameIndex nIdx(0);
439 
440     const SwDropPortionPart* pCurrPart = GetPart();
441 
442     // skip parts
443     while ( pCurrPart && nIdx + pCurrPart->GetLen() < rInf.GetLen() )
444     {
445         nMyX = nMyX + pCurrPart->GetWidth();
446         nIdx = nIdx + pCurrPart->GetLen();
447         pCurrPart = pCurrPart->GetFollow();
448     }
449 
450     TextFrameIndex const nOldIdx = rInf.GetIdx();
451     TextFrameIndex const nOldLen = rInf.GetLen();
452 
453     const_cast<SwTextSizeInfo&>(rInf).SetIdx( nIdx );
454     const_cast<SwTextSizeInfo&>(rInf).SetLen( rInf.GetLen() - nIdx );
455 
456     if( pCurrPart )
457     {
458         const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(pCurrPart->GetJoinBorderWithNext());
459         const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(pCurrPart->GetJoinBorderWithPrev());
460     }
461 
462     // robust
463     SwFontSave aFontSave( rInf, pCurrPart ? &pCurrPart->GetFont() : nullptr );
464     SwPosSize aPosSize( SwTextPortion::GetTextSize( rInf ) );
465     aPosSize.Width( aPosSize.Width() + nMyX );
466 
467     const_cast<SwTextSizeInfo&>(rInf).SetIdx( nOldIdx );
468     const_cast<SwTextSizeInfo&>(rInf).SetLen( nOldLen );
469     if( pCurrPart )
470     {
471         const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(false);
472         const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(false);
473     }
474 
475     return aPosSize;
476 }
477 
GetCursorOfst(const sal_uInt16) const478 TextFrameIndex SwDropPortion::GetCursorOfst(const sal_uInt16) const
479 {
480     return TextFrameIndex(0);
481 }
482 
CalcDropHeight(const sal_uInt16 nLines)483 void SwTextFormatter::CalcDropHeight( const sal_uInt16 nLines )
484 {
485     const SwLinePortion *const pOldCurr = GetCurr();
486     sal_uInt16 nDropHght = 0;
487     sal_uInt16 nAscent = 0;
488     sal_uInt16 nHeight = 0;
489     sal_uInt16 nDropLns = 0;
490     const bool bRegisterOld = IsRegisterOn();
491     m_bRegisterOn = false;
492 
493     Top();
494 
495     while( GetCurr()->IsDummy() )
496     {
497         if ( !Next() )
498             break;
499     }
500 
501     // If we have only one line we return 0
502     if( GetNext() || GetDropLines() == 1 )
503     {
504         for( ; nDropLns < nLines; nDropLns++ )
505         {
506             if ( GetCurr()->IsDummy() )
507                 break;
508             else
509             {
510                 CalcAscentAndHeight( nAscent, nHeight );
511                 nDropHght = nDropHght + nHeight;
512                 m_bRegisterOn = bRegisterOld;
513             }
514             if ( !Next() )
515             {
516                 nDropLns++;
517                 break;
518             }
519         }
520 
521         // We hit the line ascent when reaching the last line!
522         nDropHght = nDropHght - nHeight;
523         nDropHght = nDropHght + nAscent;
524         Top();
525     }
526     m_bRegisterOn = bRegisterOld;
527     SetDropDescent( nHeight - nAscent );
528     SetDropHeight( nDropHght );
529     SetDropLines( nDropLns );
530     // Find old position!
531     while( pOldCurr != GetCurr() )
532     {
533         if( !Next() )
534         {
535             OSL_ENSURE( false, "SwTextFormatter::_CalcDropHeight: left Toulouse" );
536             break;
537         }
538     }
539 }
540 
541 /**
542  * We assume that the font height doesn't change and that at first there
543  * are at least as many lines, as the DropCap-setting claims
544  */
GuessDropHeight(const sal_uInt16 nLines)545 void SwTextFormatter::GuessDropHeight( const sal_uInt16 nLines )
546 {
547     OSL_ENSURE( nLines, "GuessDropHeight: Give me more Lines!" );
548     sal_uInt16 nAscent = 0;
549     sal_uInt16 nHeight = 0;
550     SetDropLines( nLines );
551     if ( GetDropLines() > 1 )
552     {
553         CalcRealHeight();
554         CalcAscentAndHeight( nAscent, nHeight );
555     }
556     SetDropDescent( nHeight - nAscent );
557     SetDropHeight( nHeight * nLines - GetDropDescent() );
558 }
559 
NewDropPortion(SwTextFormatInfo & rInf)560 SwDropPortion *SwTextFormatter::NewDropPortion( SwTextFormatInfo &rInf )
561 {
562     if( !m_pDropFormat )
563         return nullptr;
564 
565     TextFrameIndex nPorLen(m_pDropFormat->GetWholeWord() ? 0 : m_pDropFormat->GetChars());
566     nPorLen = m_pFrame->GetDropLen( nPorLen );
567     if( !nPorLen )
568     {
569         ClearDropFormat();
570         return nullptr;
571     }
572 
573     SwDropPortion *pDropPor = nullptr;
574 
575     // first or second round?
576     if ( !( GetDropHeight() || IsOnceMore() ) )
577     {
578         if ( GetNext() )
579             CalcDropHeight( m_pDropFormat->GetLines() );
580         else
581             GuessDropHeight( m_pDropFormat->GetLines() );
582     }
583 
584     // the DropPortion
585     if( GetDropHeight() )
586         pDropPor = new SwDropPortion( GetDropLines(), GetDropHeight(),
587                                       GetDropDescent(), m_pDropFormat->GetDistance() );
588     else
589        pDropPor = new SwDropPortion( 0,0,0,m_pDropFormat->GetDistance() );
590 
591     pDropPor->SetLen( nPorLen );
592 
593     // If it was not possible to create a proper drop cap portion
594     // due to avoiding endless loops. We return a drop cap portion
595     // with an empty SwDropCapPart. For these portions the current
596     // font is used.
597     if ( GetDropLines() < 2 )
598     {
599         SetPaintDrop( true );
600         return pDropPor;
601     }
602 
603     // build DropPortionParts:
604     OSL_ENSURE( ! rInf.GetIdx(), "Drop Portion not at 0 position!" );
605     TextFrameIndex nNextChg(0);
606     const SwCharFormat* pFormat = m_pDropFormat->GetCharFormat();
607     SwDropPortionPart* pCurrPart = nullptr;
608 
609     while ( nNextChg  < nPorLen )
610     {
611         // check for attribute changes and if the portion has to split:
612         Seek( nNextChg );
613 
614         // the font is deleted in the destructor of the drop portion part
615         SwFont* pTmpFnt = new SwFont( *rInf.GetFont() );
616         if ( pFormat )
617         {
618             const SwAttrSet& rSet = pFormat->GetAttrSet();
619             pTmpFnt->SetDiffFnt(&rSet, &m_pFrame->GetDoc().getIDocumentSettingAccess());
620         }
621 
622         // we do not allow a vertical font for the drop portion
623         pTmpFnt->SetVertical( 0, rInf.GetTextFrame()->IsVertical() );
624 
625         // find next attribute change / script change
626         const TextFrameIndex nTmpIdx = nNextChg;
627         TextFrameIndex nNextAttr = GetNextAttr();
628         nNextChg = m_pScriptInfo->NextScriptChg( nTmpIdx );
629         if( nNextChg > nNextAttr )
630             nNextChg = nNextAttr;
631         if ( nNextChg > nPorLen )
632             nNextChg = nPorLen;
633 
634         std::unique_ptr<SwDropPortionPart> pPart(
635                 new SwDropPortionPart( *pTmpFnt, nNextChg - nTmpIdx ) );
636         auto pPartTemp = pPart.get();
637 
638         if ( ! pCurrPart )
639             pDropPor->SetPart( std::move(pPart) );
640         else
641             pCurrPart->SetFollow( std::move(pPart) );
642 
643         pCurrPart = pPartTemp;
644     }
645 
646     SetPaintDrop( true );
647     return pDropPor;
648 }
649 
PaintDropPortion()650 void SwTextPainter::PaintDropPortion()
651 {
652     const SwDropPortion *pDrop = GetInfo().GetParaPortion()->FindDropPortion();
653     OSL_ENSURE( pDrop, "DrapCop-Portion not available." );
654     if( !pDrop )
655         return;
656 
657     const SwTwips nOldY = GetInfo().Y();
658 
659     Top();
660 
661     GetInfo().SetpSpaceAdd( m_pCurr->GetpLLSpaceAdd() );
662     GetInfo().ResetSpaceIdx();
663     GetInfo().SetKanaComp( m_pCurr->GetpKanaComp() );
664     GetInfo().ResetKanaIdx();
665 
666     // 8047: Drops and Dummies
667     while( !m_pCurr->GetLen() && Next() )
668         ;
669 
670     // MarginPortion and Adjustment!
671     const SwLinePortion *pPor = m_pCurr->GetFirstPortion();
672     long nX = 0;
673     while( pPor && !pPor->IsDropPortion() )
674     {
675         nX = nX + pPor->Width();
676         pPor = pPor->GetNextPortion();
677     }
678     Point aLineOrigin( GetTopLeft() );
679 
680     aLineOrigin.AdjustX(nX );
681     sal_uInt16 nTmpAscent, nTmpHeight;
682     CalcAscentAndHeight( nTmpAscent, nTmpHeight );
683     aLineOrigin.AdjustY(nTmpAscent );
684     GetInfo().SetIdx( GetStart() );
685     GetInfo().SetPos( aLineOrigin );
686     GetInfo().SetLen( pDrop->GetLen() );
687 
688     pDrop->PaintDrop( GetInfo() );
689 
690     GetInfo().Y( nOldY );
691 }
692 
693 // Since the calculation of the font size is expensive, this is being
694 // channeled through a DropCapCache
695 #define DROP_CACHE_SIZE 10
696 
697 class SwDropCapCache
698 {
699     const void* aFontCacheId[ DROP_CACHE_SIZE ] = {};
700     OUString aText[ DROP_CACHE_SIZE ];
701     sal_uInt16 aFactor[ DROP_CACHE_SIZE ];
702     sal_uInt16 aWishedHeight[ DROP_CACHE_SIZE ] = {};
703     short aDescent[ DROP_CACHE_SIZE ];
704     sal_uInt16 nIndex = 0;
705 public:
706     SwDropCapCache() = default;
707     void CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf );
708 };
709 
DeleteDropCapCache()710 void SwDropPortion::DeleteDropCapCache()
711 {
712     delete pDropCapCache;
713 }
714 
CalcFontSize(SwDropPortion * pDrop,SwTextFormatInfo & rInf)715 void SwDropCapCache::CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf )
716 {
717     const void* nFntCacheId = nullptr;
718     sal_uInt16 nTmpIdx = 0;
719 
720     OSL_ENSURE( pDrop->GetPart(),"DropPortion without part during font calculation");
721 
722     SwDropPortionPart* pCurrPart = pDrop->GetPart();
723     const bool bUseCache = ! pCurrPart->GetFollow() && !pCurrPart->GetFont().HasBorder();
724     TextFrameIndex nIdx = rInf.GetIdx();
725     OUString aStr(rInf.GetText().copy(sal_Int32(nIdx), sal_Int32(pCurrPart->GetLen())));
726 
727     long nDescent = 0;
728     long nFactor = -1;
729 
730     if ( bUseCache )
731     {
732         SwFont& rFnt = pCurrPart->GetFont();
733         rFnt.CheckFontCacheId( rInf.GetVsh(), rFnt.GetActual() );
734         rFnt.GetFontCacheId( nFntCacheId, nTmpIdx, rFnt.GetActual() );
735 
736         nTmpIdx = 0;
737 
738         while( nTmpIdx < DROP_CACHE_SIZE &&
739             ( aText[ nTmpIdx ] != aStr || aFontCacheId[ nTmpIdx ] != nFntCacheId ||
740             aWishedHeight[ nTmpIdx ] != pDrop->GetDropHeight() ) )
741             ++nTmpIdx;
742     }
743 
744     // we have to calculate a new font scaling factor if
745     // 1. we did not find a scaling factor in the cache or
746     // 2. we are not allowed to use the cache because the drop portion
747     //    consists of more than one part
748     if( nTmpIdx >= DROP_CACHE_SIZE || ! bUseCache )
749     {
750         ++nIndex;
751         nIndex %= DROP_CACHE_SIZE;
752         nTmpIdx = nIndex;
753 
754         long nWishedHeight = pDrop->GetDropHeight();
755         long nAscent = 0;
756 
757         // find out biggest font size for initial scaling factor
758         long nMaxFontHeight = 1;
759         while ( pCurrPart )
760         {
761             const SwFont& rFnt = pCurrPart->GetFont();
762             const long nCurrHeight = rFnt.GetHeight( rFnt.GetActual() );
763             if ( nCurrHeight > nMaxFontHeight )
764                 nMaxFontHeight = nCurrHeight;
765 
766             pCurrPart = pCurrPart->GetFollow();
767         }
768 
769         nFactor = ( 1000 * nWishedHeight ) / nMaxFontHeight;
770 
771         if ( bUseCache )
772         {
773             // save keys for cache
774             aFontCacheId[ nTmpIdx ] = nFntCacheId;
775             aText[ nTmpIdx ] = aStr;
776             aWishedHeight[ nTmpIdx ] = sal_uInt16(nWishedHeight);
777             // save initial scaling factor
778             aFactor[ nTmpIdx ] = static_cast<sal_uInt16>(nFactor);
779         }
780 
781         bool bGrow = (pDrop->GetLen() != TextFrameIndex(0));
782 
783         // for growing control
784         long nMax = USHRT_MAX;
785         long nMin = 0;
786 #if OSL_DEBUG_LEVEL > 1
787         long nGrow = 0;
788 #endif
789 
790         bool bWinUsed = false;
791         vcl::Font aOldFnt;
792         MapMode aOldMap( MapUnit::MapTwip );
793         OutputDevice* pOut = rInf.GetOut();
794         OutputDevice* pWin;
795         if( rInf.GetVsh() && rInf.GetVsh()->GetWin() )
796             pWin = rInf.GetVsh()->GetWin();
797         else
798             pWin = Application::GetDefaultDevice();
799 
800         while( bGrow )
801         {
802             // reset pCurrPart to first part
803             pCurrPart = pDrop->GetPart();
804             bool bFirstGlyphRect = true;
805             tools::Rectangle aCommonRect, aRect;
806 
807             while ( pCurrPart )
808             {
809                 // current font
810                 SwFont& rFnt = pCurrPart->GetFont();
811 
812                 // Get height including proportion
813                 const long nCurrHeight = rFnt.GetHeight( rFnt.GetActual() );
814 
815                 // Get without proportion
816                 const sal_uInt8 nOldProp = rFnt.GetPropr();
817                 rFnt.SetProportion( 100 );
818                 Size aOldSize( 0, rFnt.GetHeight( rFnt.GetActual() ) );
819 
820                 Size aNewSize( 0, ( nFactor * nCurrHeight ) / 1000 );
821                 rFnt.SetSize( aNewSize, rFnt.GetActual() );
822                 rFnt.ChgPhysFnt( rInf.GetVsh(), *pOut );
823 
824                 nAscent = rFnt.GetAscent( rInf.GetVsh(), *pOut );
825 
826                 // we get the rectangle that covers all chars
827                 bool bHaveGlyphRect = pOut->GetTextBoundRect( aRect, rInf.GetText(), 0,
828                             sal_Int32(nIdx), sal_Int32(pCurrPart->GetLen()))
829                     && ! aRect.IsEmpty();
830 
831                 if ( ! bHaveGlyphRect )
832                 {
833                     // getting glyph boundaries failed for some reason,
834                     // we take the window for calculating sizes
835                     if ( pWin )
836                     {
837                         if ( ! bWinUsed )
838                         {
839                             bWinUsed = true;
840                             aOldMap = pWin->GetMapMode( );
841                             pWin->SetMapMode( MapMode( MapUnit::MapTwip ) );
842                             aOldFnt = pWin->GetFont();
843                         }
844                         pWin->SetFont( rFnt.GetActualFont() );
845 
846                         bHaveGlyphRect = pWin->GetTextBoundRect( aRect, rInf.GetText(), 0,
847                                 sal_Int32(nIdx), sal_Int32(pCurrPart->GetLen()))
848                             && ! aRect.IsEmpty();
849                     }
850                     if (!bHaveGlyphRect)
851                     {
852                         // We do not have a window or our window could not
853                         // give us glyph boundaries.
854                         aRect = tools::Rectangle( Point( 0, 0 ), Size( 0, nAscent ) );
855                     }
856                 }
857 
858                 // Now we (hopefully) have a bounding rectangle for the
859                 // glyphs of the current portion and the ascent of the current
860                 // font
861 
862                 // reset font size and proportion
863                 rFnt.SetSize( aOldSize, rFnt.GetActual() );
864                 rFnt.SetProportion( nOldProp );
865 
866                 // Modify the bounding rectangle with the borders
867                 // Robust: If the padding is so big as drop cap letter has no enough space than
868                 // remove all padding.
869                 if( rFnt.GetTopBorderSpace() + rFnt.GetBottomBorderSpace() >= nWishedHeight )
870                 {
871                     rFnt.SetTopBorderDist(0);
872                     rFnt.SetBottomBorderDist(0);
873                     rFnt.SetRightBorderDist(0);
874                     rFnt.SetLeftBorderDist(0);
875                 }
876 
877                 if( rFnt.GetTopBorder() )
878                 {
879                     aRect.setHeight(aRect.GetHeight() + rFnt.GetTopBorderSpace());
880                     aRect.setY(aRect.getY() - rFnt.GetTopBorderSpace());
881                 }
882 
883                 if( rFnt.GetBottomBorder() )
884                 {
885                     aRect.setHeight(aRect.GetHeight() + rFnt.GetBottomBorderSpace());
886                 }
887 
888                 if ( bFirstGlyphRect )
889                 {
890                     aCommonRect = aRect;
891                     bFirstGlyphRect = false;
892                 }
893                 else
894                     aCommonRect.Union( aRect );
895 
896                 nIdx = nIdx + pCurrPart->GetLen();
897                 pCurrPart = pCurrPart->GetFollow();
898             }
899 
900             // now we have a union ( aCommonRect ) of all glyphs with
901             // respect to a common baseline : 0
902 
903             // get descent and ascent from union
904             if ( rInf.GetTextFrame()->IsVertical() )
905             {
906                 nDescent = aCommonRect.Left();
907                 nAscent = aCommonRect.Right();
908 
909                 if ( nDescent < 0 )
910                     nDescent = -nDescent;
911             }
912             else
913             {
914                 nDescent = aCommonRect.Bottom();
915                 nAscent = aCommonRect.Top();
916             }
917             if ( nAscent < 0 )
918                 nAscent = -nAscent;
919 
920             const long nHght = nAscent + nDescent;
921             if ( nHght )
922             {
923                 if ( nHght > nWishedHeight )
924                     nMax = nFactor;
925                 else
926                 {
927                     if ( bUseCache )
928                         aFactor[ nTmpIdx ] = static_cast<sal_uInt16>(nFactor);
929                     nMin = nFactor;
930                 }
931 
932                 nFactor = ( nFactor * nWishedHeight ) / nHght;
933                 bGrow = ( nFactor > nMin ) && ( nFactor < nMax );
934 #if OSL_DEBUG_LEVEL > 1
935                 if ( bGrow )
936                     nGrow++;
937 #endif
938                 nIdx = rInf.GetIdx();
939             }
940             else
941                 bGrow = false;
942         }
943 
944         if ( bWinUsed )
945         {
946             // reset window if it has been used
947             pWin->SetMapMode( aOldMap );
948             pWin->SetFont( aOldFnt );
949         }
950 
951         if ( bUseCache )
952             aDescent[ nTmpIdx ] = -short( nDescent );
953     }
954 
955     pCurrPart = pDrop->GetPart();
956 
957     // did made any new calculations or did we use the cache?
958     if ( -1 == nFactor )
959     {
960         nFactor = aFactor[ nTmpIdx ];
961         nDescent = aDescent[ nTmpIdx ];
962     }
963     else
964         nDescent = -nDescent;
965 
966     while ( pCurrPart )
967     {
968         // scale current font
969         SwFont& rFnt = pCurrPart->GetFont();
970         Size aNewSize( 0, ( nFactor * rFnt.GetHeight( rFnt.GetActual() ) ) / 1000 );
971 
972         const sal_uInt8 nOldProp = rFnt.GetPropr();
973         rFnt.SetProportion( 100 );
974         rFnt.SetSize( aNewSize, rFnt.GetActual() );
975         rFnt.SetProportion( nOldProp );
976 
977         pCurrPart = pCurrPart->GetFollow();
978     }
979     pDrop->SetY( static_cast<short>(nDescent) );
980 }
981 
Format(SwTextFormatInfo & rInf)982 bool SwDropPortion::Format( SwTextFormatInfo &rInf )
983 {
984     bool bFull = false;
985     nFix = static_cast<sal_uInt16>(rInf.X());
986 
987     SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() );
988     aLayoutModeModifier.SetAuto();
989 
990     if( nDropHeight && pPart && nLines!=1 )
991     {
992         if( !pDropCapCache )
993             pDropCapCache = new SwDropCapCache;
994 
995         // adjust font sizes to fit into the rectangle
996         pDropCapCache->CalcFontSize( this, rInf );
997 
998         const long nOldX = rInf.X();
999         {
1000             SwDropSave aSave( rInf );
1001             SwDropPortionPart* pCurrPart = pPart.get();
1002 
1003             while ( pCurrPart )
1004             {
1005                 rInf.SetLen( pCurrPart->GetLen() );
1006                 SwFont& rFnt = pCurrPart->GetFont();
1007                 {
1008                     SwFontSave aFontSave( rInf, &rFnt );
1009                     SetJoinBorderWithNext(pCurrPart->GetJoinBorderWithNext());
1010                     SetJoinBorderWithPrev(pCurrPart->GetJoinBorderWithPrev());
1011                     bFull = FormatText( rInf );
1012 
1013                     if ( bFull )
1014                         break;
1015                 }
1016 
1017                 const SwTwips nTmpWidth =
1018                         ( InSpaceGrp() && rInf.GetSpaceAdd() ) ?
1019                         Width() + CalcSpacing( rInf.GetSpaceAdd(), rInf ) :
1020                         Width();
1021 
1022                 // set values
1023                 pCurrPart->SetWidth( static_cast<sal_uInt16>(nTmpWidth) );
1024 
1025                 // Move
1026                 rInf.SetIdx( rInf.GetIdx() + pCurrPart->GetLen() );
1027                 rInf.X( rInf.X() + nTmpWidth );
1028                 pCurrPart = pCurrPart->GetFollow();
1029             }
1030             SetJoinBorderWithNext(false);
1031             SetJoinBorderWithPrev(false);
1032             Width( static_cast<sal_uInt16>(rInf.X() - nOldX) );
1033         }
1034 
1035         // reset my length
1036         SetLen( rInf.GetLen() );
1037 
1038         // Quit when Flys are overlapping
1039         if( ! bFull )
1040             bFull = lcl_IsDropFlyInter( rInf, Width(), nDropHeight );
1041 
1042         if( bFull )
1043         {
1044             // FormatText could have caused nHeight to be 0
1045             if ( !Height() )
1046                 Height( rInf.GetTextHeight() );
1047 
1048             // And now for another round
1049             nDropHeight = nLines = 0;
1050             pPart.reset();
1051 
1052             // Meanwhile use normal formatting
1053             bFull = SwTextPortion::Format( rInf );
1054         }
1055         else
1056             rInf.SetDropInit( true );
1057 
1058         Height( rInf.GetTextHeight() );
1059         SetAscent( rInf.GetAscent() );
1060     }
1061     else
1062         bFull = SwTextPortion::Format( rInf );
1063 
1064     if( bFull )
1065         nDistance = 0;
1066     else
1067     {
1068         const sal_uInt16 nWant = Width() + GetDistance();
1069         const sal_uInt16 nRest = static_cast<sal_uInt16>(rInf.Width() - rInf.X());
1070         if( ( nWant > nRest ) ||
1071             lcl_IsDropFlyInter( rInf, Width() + GetDistance(), nDropHeight ) )
1072             nDistance = 0;
1073 
1074         Width( Width() + nDistance );
1075     }
1076     return bFull;
1077 }
1078 
1079 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1080