Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sw / source / core / txtnode / fntcache.cxx
blobbc5dd603a4338b8625aff3237e02a44dd88b3dda
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
22 #include <i18nlangtag/mslangid.hxx>
23 #include <officecfg/Office/Common.hxx>
24 #include <vcl/outdev.hxx>
25 #include <vcl/kernarray.hxx>
26 #include <vcl/lineinfo.hxx>
27 #include <vcl/metric.hxx>
28 #include <vcl/svapp.hxx>
29 #include <vcl/lazydelete.hxx>
30 #include <vcl/glyphitemcache.hxx>
31 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
32 #include <com/sun/star/i18n/WordType.hpp>
33 #include <com/sun/star/i18n/XBreakIterator.hpp>
34 #include <breakit.hxx>
35 #include <paintfrm.hxx>
36 #include <viewsh.hxx>
37 #include <viewopt.hxx>
38 #include <fntcache.hxx>
39 #include <IDocumentSettingAccess.hxx>
40 #include <swfont.hxx>
41 #include <wrong.hxx>
42 #include <txtfrm.hxx>
43 #include <pagefrm.hxx>
44 #include <tgrditem.hxx>
45 #include <scriptinfo.hxx>
46 #include <editeng/brushitem.hxx>
47 #include <accessibilityoptions.hxx>
48 #include <svx/sdr/attribute/sdrallfillattributeshelper.hxx>
49 #include <doc.hxx>
50 #include <editeng/fhgtitem.hxx>
51 #include <vcl/glyphitem.hxx>
52 #include <vcl/vcllayout.hxx>
53 #include <docsh.hxx>
54 #include <strings.hrc>
55 #include <fntcap.hxx>
56 #include <vcl/outdev/ScopedStates.hxx>
57 #include <o3tl/hash_combine.hxx>
58 #include <cstdint>
59 #include <memory>
60 #include "justify.hxx"
62 using namespace ::com::sun::star;
64 // global variables declared in fntcache.hxx
65 // FontCache is created in txtinit.cxx TextInit_ and deleted in TextFinit
66 SwFntCache *pFntCache = nullptr;
67 // last Font set by ChgFntCache
68 SwFntObj *pLastFont = nullptr;
70 constexpr Color gWaveCol(COL_GRAY);
72 tools::Long SwFntObj::s_nPixWidth;
73 MapMode* SwFntObj::s_pPixMap = nullptr;
74 static vcl::DeleteOnDeinit< VclPtr<OutputDevice> > s_pFntObjPixOut {};
76 void SwFntCache::Flush( )
78 if ( pLastFont )
80 pLastFont->Unlock();
81 pLastFont = nullptr;
83 SwCache::Flush( );
86 SwFntObj::SwFntObj(const SwSubFont &rFont, std::uintptr_t nFontCacheId, SwViewShell const *pSh)
87 : SwCacheObj(reinterpret_cast<void *>(nFontCacheId))
88 , m_aFont(rFont)
89 , m_pScrFont(nullptr)
90 , m_pPrtFont(&m_aFont)
91 , m_pPrinter(nullptr)
92 , m_nGuessedLeading(USHRT_MAX)
93 , m_nExtLeading(USHRT_MAX)
94 , m_nScrAscent(0)
95 , m_nPrtAscent(USHRT_MAX)
96 , m_nScrHeight(0)
97 , m_nPrtHeight(USHRT_MAX)
98 , m_nPropWidth(rFont.GetPropWidth())
99 , m_nScrHangingBaseline(0)
100 , m_nPrtHangingBaseline(0)
102 m_nZoom = pSh ? pSh->GetViewOptions()->GetZoom() : USHRT_MAX;
103 m_bSymbol = RTL_TEXTENCODING_SYMBOL == m_aFont.GetCharSet();
104 m_bPaintBlank = ( LINESTYLE_NONE != m_aFont.GetUnderline()
105 || LINESTYLE_NONE != m_aFont.GetOverline()
106 || STRIKEOUT_NONE != m_aFont.GetStrikeout() )
107 && !m_aFont.IsWordLineMode();
108 m_aFont.SetLanguage(rFont.GetLanguage());
111 SwFntObj::~SwFntObj()
113 if ( m_pScrFont != m_pPrtFont )
114 delete m_pScrFont;
115 if ( m_pPrtFont != &m_aFont )
116 delete m_pPrtFont;
119 void SwFntObj::CreatePrtFont( const OutputDevice& rPrt )
121 if ( m_nPropWidth == 100 || m_pPrinter == &rPrt )
122 return;
124 if( m_pScrFont != m_pPrtFont )
125 delete m_pScrFont;
126 if( m_pPrtFont != &m_aFont )
127 delete m_pPrtFont;
129 const vcl::Font aOldFnt( rPrt.GetFont() );
130 const_cast<OutputDevice&>(rPrt).SetFont( m_aFont );
131 const FontMetric aWinMet( rPrt.GetFontMetric() );
132 const_cast<OutputDevice&>(rPrt).SetFont( aOldFnt );
133 auto nWidth = ( aWinMet.GetFontSize().Width() * m_nPropWidth ) / 100;
135 if( !nWidth )
136 ++nWidth;
137 m_pPrtFont = new vcl::Font( m_aFont );
138 m_pPrtFont->SetFontSize( Size( nWidth, m_aFont.GetFontSize().Height() ) );
139 m_pScrFont = nullptr;
144 * returns whether we have to adjust the output font to resemble
145 * the formatting font
147 * _Not_ necessary if
149 * 1. RefDef == OutDev (text formatting, online layout...)
150 * 2. PDF export from online layout
151 * 3. Prospect/PagePreview printing
153 static bool lcl_IsFontAdjustNecessary( const vcl::RenderContext& rOutDev,
154 const vcl::RenderContext& rRefDev )
156 return &rRefDev != &rOutDev &&
157 OUTDEV_WINDOW != rRefDev.GetOutDevType() &&
158 ( OUTDEV_PRINTER != rRefDev.GetOutDevType() ||
159 OUTDEV_PRINTER != rOutDev.GetOutDevType() );
162 namespace {
164 struct CalcLinePosData
166 SwDrawTextInfo& rInf;
167 vcl::Font& rFont;
168 TextFrameIndex nCnt;
169 const bool bSwitchH2V;
170 const bool bSwitchH2VLRBT;
171 const bool bSwitchL2R;
172 tools::Long nHalfSpace;
173 KernArray& rKernArray;
174 const bool bBidiPor;
176 CalcLinePosData( SwDrawTextInfo& _rInf, vcl::Font& _rFont,
177 TextFrameIndex const _nCnt, const bool _bSwitchH2V, const bool _bSwitchH2VLRBT, const bool _bSwitchL2R,
178 tools::Long _nHalfSpace, KernArray& _rKernArray, const bool _bBidiPor) :
179 rInf( _rInf ),
180 rFont( _rFont ),
181 nCnt( _nCnt ),
182 bSwitchH2V( _bSwitchH2V ),
183 bSwitchH2VLRBT( _bSwitchH2VLRBT ),
184 bSwitchL2R( _bSwitchL2R ),
185 nHalfSpace( _nHalfSpace ),
186 rKernArray( _rKernArray ),
187 bBidiPor( _bBidiPor )
194 // Computes the start and end position of an underline. This function is called
195 // from the DrawText-method (for underlining misspelled words or smarttag terms).
196 static void lcl_calcLinePos( const CalcLinePosData &rData,
197 Point &rStart, Point &rEnd, TextFrameIndex const nStart, TextFrameIndex const nWrLen)
199 tools::Long nBlank = 0;
200 const TextFrameIndex nEnd = nStart + nWrLen;
201 const tools::Long nTmpSpaceAdd = rData.rInf.GetSpace() / SPACING_PRECISION_FACTOR;
203 if ( nEnd < rData.nCnt
204 && CH_BLANK == rData.rInf.GetText()[sal_Int32(rData.rInf.GetIdx() + nEnd)] )
206 if (nEnd + TextFrameIndex(1) == rData.nCnt)
207 nBlank -= nTmpSpaceAdd;
208 else
209 nBlank -= rData.nHalfSpace;
212 // determine start, end and length of wave line
213 sal_Int32 nKernStart = nStart ? rData.rKernArray[sal_Int32(nStart) - 1] : 0;
214 sal_Int32 nKernEnd = rData.rKernArray[sal_Int32(nEnd) - 1];
216 const Degree10 nDir = rData.bBidiPor ? 1800_deg10
217 : UnMapDirection(rData.rFont.GetOrientation(),
218 rData.bSwitchH2V, rData.bSwitchH2VLRBT);
220 switch ( nDir.get() )
222 case 0 :
223 rStart.AdjustX(nKernStart );
224 rEnd.setX( nBlank + rData.rInf.GetPos().X() + nKernEnd );
225 rEnd.setY( rData.rInf.GetPos().Y() );
226 break;
227 case 900 :
228 rStart.AdjustY( -nKernStart );
229 rEnd.setX( rData.rInf.GetPos().X() );
230 rEnd.setY( nBlank + rData.rInf.GetPos().Y() - nKernEnd );
231 break;
232 case 1800 :
233 rStart.AdjustX( -nKernStart );
234 rEnd.setX( rData.rInf.GetPos().X() - nKernEnd - nBlank );
235 rEnd.setY( rData.rInf.GetPos().Y() );
236 break;
237 case 2700 :
238 rStart.AdjustY(nKernStart );
239 rEnd.setX( rData.rInf.GetPos().X() );
240 rEnd.setY( nBlank + rData.rInf.GetPos().Y() + nKernEnd );
241 break;
244 // tdf#151968
245 // if start < end, OutputDevice::DrawWaveLine() will think it is a rotated
246 // line, so we swap nStart and nEnd to avoid this.
247 if ( rData.bBidiPor )
248 std::swap(rStart, rEnd);
250 if ( rData.bSwitchL2R )
252 rData.rInf.GetFrame()->SwitchLTRtoRTL( rStart );
253 rData.rInf.GetFrame()->SwitchLTRtoRTL( rEnd );
255 // tdf#151968
256 // We need to do this here as well for LTR text in a RTL paragraph.
257 std::swap(rStart, rEnd);
260 if ( rData.bSwitchH2V )
262 rData.rInf.GetFrame()->SwitchHorizontalToVertical( rStart );
263 rData.rInf.GetFrame()->SwitchHorizontalToVertical( rEnd );
267 // Returns the Ascent of the Font on the given output device;
268 // it may be necessary to create the screen font first.
269 sal_uInt16 SwFntObj::GetFontAscent( const SwViewShell *pSh, const OutputDevice& rOut )
271 sal_uInt16 nRet = 0;
272 const OutputDevice& rRefDev = pSh ? pSh->GetRefDev() : rOut;
274 if ( pSh && lcl_IsFontAdjustNecessary( rOut, rRefDev ) )
276 CreateScrFont( *pSh, rOut );
277 OSL_ENSURE( USHRT_MAX != m_nScrAscent, "nScrAscent is going berzerk" );
278 nRet = m_nScrAscent;
280 else
282 if (m_nPrtAscent == USHRT_MAX) // printer ascent unknown?
284 CreatePrtFont( rOut );
285 const vcl::Font aOldFnt( rRefDev.GetFont() );
286 const_cast<OutputDevice&>(rRefDev).SetFont( *m_pPrtFont );
287 const FontMetric aOutMet( rRefDev.GetFontMetric() );
288 m_nPrtAscent = o3tl::narrowing<sal_uInt16>(aOutMet.GetAscent());
289 m_nPrtHangingBaseline = o3tl::narrowing<sal_uInt16>(aOutMet.GetHangingBaseline());
290 const_cast<OutputDevice&>(rRefDev).SetFont( aOldFnt );
293 nRet = m_nPrtAscent;
296 #if !defined(MACOSX) // #i89844# extleading is below the line for Mac
297 // TODO: move extleading below the line for all platforms too
298 nRet += GetFontLeading( pSh, rRefDev );
299 #endif
301 OSL_ENSURE( USHRT_MAX != nRet, "GetFontAscent returned USHRT_MAX" );
302 return nRet;
305 // Returns the height of the Font on the given output device;
306 // it may be necessary to create the screen font first.
307 sal_uInt16 SwFntObj::GetFontHeight( const SwViewShell* pSh, const OutputDevice& rOut )
309 sal_uInt16 nRet = 0;
310 const OutputDevice& rRefDev = pSh ? pSh->GetRefDev() : rOut;
312 if ( pSh && lcl_IsFontAdjustNecessary( rOut, rRefDev ) )
314 CreateScrFont( *pSh, rOut );
315 OSL_ENSURE( USHRT_MAX != m_nScrHeight, "nScrHeight is going berzerk" );
316 nRet = m_nScrHeight + GetFontLeading( pSh, rRefDev );
318 else
320 if (m_nPrtHeight == USHRT_MAX) // printer height unknown?
322 CreatePrtFont( rOut );
323 const vcl::Font aOldFnt( rRefDev.GetFont() );
324 const_cast<OutputDevice&>(rRefDev).SetFont( *m_pPrtFont );
325 m_nPrtHeight = o3tl::narrowing<sal_uInt16>(rRefDev.GetTextHeight());
327 #if OSL_DEBUG_LEVEL > 0
328 // Check if vcl did not change the meaning of GetTextHeight
329 const FontMetric aOutMet( rRefDev.GetFontMetric() );
330 tools::Long nTmpPrtHeight = o3tl::narrowing<sal_uInt16>(aOutMet.GetAscent()) + aOutMet.GetDescent();
331 // #i106098#: do not compare with == here due to rounding error
332 OSL_ENSURE( std::abs(nTmpPrtHeight - m_nPrtHeight) < 3,
333 "GetTextHeight != Ascent + Descent" );
334 #endif
336 const_cast<OutputDevice&>(rRefDev).SetFont( aOldFnt );
339 nRet = m_nPrtHeight + GetFontLeading( pSh, rRefDev );
342 OSL_ENSURE( USHRT_MAX != nRet, "GetFontHeight returned USHRT_MAX" );
343 return nRet;
346 sal_uInt16 SwFntObj::GetFontLeading( const SwViewShell *pSh, const OutputDevice& rOut )
348 sal_uInt16 nRet = 0;
350 if ( pSh )
352 if ( USHRT_MAX == m_nGuessedLeading || USHRT_MAX == m_nExtLeading )
354 SolarMutexGuard aGuard;
356 const vcl::Font aOldFnt( rOut.GetFont() );
357 const_cast<OutputDevice&>(rOut).SetFont( *m_pPrtFont );
358 const FontMetric aMet( rOut.GetFontMetric() );
359 const_cast<OutputDevice&>(rOut).SetFont( aOldFnt );
360 m_bSymbol = RTL_TEXTENCODING_SYMBOL == aMet.GetCharSet();
361 GuessLeading( *pSh, aMet );
362 m_nExtLeading = o3tl::narrowing<sal_uInt16>(aMet.GetExternalLeading());
363 /* HACK: FIXME There is something wrong with Writer's bullet rendering, causing lines
364 with bullets to be higher than they should be. I think this is because
365 Writer uses font's external leading incorrect, as the vertical distance
366 added to every line instead of only a distance between multiple lines,
367 which means a single bullet has external leading added even though it
368 shouldn't, but frankly this is just an educated guess rather than understanding
369 Writer's layout (heh).
370 Symbol font in some documents is 'StarSymbol; Arial Unicode MS', and Windows
371 machines often do not have StarSymbol, falling back to Arial Unicode MS, which
372 has unusually high external leading. So just reset external leading for fonts
373 which are used to bullets, as those should not be used on multiple lines anyway,
374 so in correct rendering external leading should be irrelevant anyway.
375 Interestingly enough, bSymbol is false for 'StarSymbol; Arial Unicode MS', so
376 also check explicitly.
378 if( m_bSymbol || IsOpenSymbol( m_pPrtFont->GetFamilyName()))
379 m_nExtLeading = 0;
382 const IDocumentSettingAccess& rIDSA = pSh->getIDocumentSettingAccess();
383 const bool bBrowse = ( pSh->GetWin() &&
384 pSh->GetViewOptions()->getBrowseMode() &&
385 !pSh->GetViewOptions()->IsPrtFormat() );
387 if ( !bBrowse && rIDSA.get(DocumentSettingId::ADD_EXT_LEADING) )
388 nRet = m_nExtLeading;
389 else
390 nRet = m_nGuessedLeading;
393 OSL_ENSURE( USHRT_MAX != nRet, "GetFontLeading returned USHRT_MAX" );
394 return nRet;
397 sal_uInt16 SwFntObj::GetFontHangingBaseline( const SwViewShell* pSh, const OutputDevice& rOut )
399 sal_uInt16 nRet = 0;
400 const OutputDevice& rRefDev = pSh ? pSh->GetRefDev() : rOut;
402 GetFontAscent(pSh, rOut);
404 if ( pSh && lcl_IsFontAdjustNecessary( rOut, rRefDev ) )
405 nRet = m_nScrHangingBaseline;
406 else
407 nRet = m_nPrtHangingBaseline;
409 return nRet;
412 // pOut is the output device, not the reference device
413 void SwFntObj::CreateScrFont( const SwViewShell& rSh, const OutputDevice& rOut )
415 if ( m_pScrFont )
416 return;
418 // any changes to the output device are reset at the end of the function
419 OutputDevice* pOut = const_cast<OutputDevice*>(&rOut);
421 // Save old font
422 vcl::Font aOldOutFont( pOut->GetFont() );
424 m_nScrHeight = USHRT_MAX;
426 // Condition for output font / refdev font adjustment
427 OutputDevice* pPrt = &rSh.GetRefDev();
429 if( !rSh.GetWin() ||
430 !rSh.GetViewOptions()->getBrowseMode() ||
431 rSh.GetViewOptions()->IsPrtFormat() )
433 // After CreatePrtFont m_pPrtFont is the font which is actually used
434 // by the reference device
435 CreatePrtFont( *pPrt );
436 m_pPrinter = pPrt;
438 // save old reference device font
439 vcl::Font aOldPrtFnt( pPrt->GetFont() );
441 // set the font used at the reference device at the reference device
442 // and the output device
443 pPrt->SetFont( *m_pPrtFont );
444 pOut->SetFont( *m_pPrtFont );
446 // This should be the default for pScrFont.
447 m_pScrFont = m_pPrtFont;
449 FontMetric aMet = pPrt->GetFontMetric( );
450 // Don't lose "faked" properties of the logical font that don't truly
451 // exist in the physical font metrics which vcl which fake up for us
452 aMet.SetWeight(m_pScrFont->GetWeight());
453 aMet.SetItalic(m_pScrFont->GetItalic());
455 m_bSymbol = RTL_TEXTENCODING_SYMBOL == aMet.GetCharSet();
457 if ( USHRT_MAX == m_nGuessedLeading )
458 GuessLeading( rSh, aMet );
460 if ( USHRT_MAX == m_nExtLeading )
461 m_nExtLeading = o3tl::narrowing<sal_uInt16>(aMet.GetExternalLeading());
463 // reset the original reference device font
464 pPrt->SetFont( aOldPrtFnt );
466 else
468 m_bSymbol = RTL_TEXTENCODING_SYMBOL == m_aFont.GetCharSet();
469 if ( m_nGuessedLeading == USHRT_MAX )
470 m_nGuessedLeading = 0;
472 // no external leading in browse mode
473 if ( m_nExtLeading == USHRT_MAX )
474 m_nExtLeading = 0;
476 m_pScrFont = m_pPrtFont;
479 m_nScrAscent = o3tl::narrowing<sal_uInt16>(pOut->GetFontMetric().GetAscent());
480 m_nScrHangingBaseline = o3tl::narrowing<sal_uInt16>(pOut->GetFontMetric().GetHangingBaseline());
481 if ( USHRT_MAX == m_nScrHeight )
482 m_nScrHeight = o3tl::narrowing<sal_uInt16>(pOut->GetTextHeight());
484 // reset original output device font
485 pOut->SetFont( aOldOutFont );
488 void SwFntObj::GuessLeading( const SwViewShell&
489 #if defined(_WIN32)
491 #endif
492 , const FontMetric& rMet )
494 // If leading >= 5, this seems to be enough leading.
495 // Nothing has to be done.
496 if ( rMet.GetInternalLeading() >= 5 )
498 m_nGuessedLeading = 0;
499 return;
502 #if defined(_WIN32)
503 OutputDevice *pWin = rSh.GetWin() ?
504 rSh.GetWin()->GetOutDev() :
505 Application::GetDefaultDevice();
506 if ( pWin )
508 MapMode aTmpMap( MapUnit::MapTwip );
509 MapMode aOldMap = pWin->GetMapMode( );
510 pWin->SetMapMode( aTmpMap );
511 const vcl::Font aOldFnt( pWin->GetFont() );
512 pWin->SetFont( *m_pPrtFont );
513 const FontMetric aWinMet( pWin->GetFontMetric() );
514 const sal_uInt16 nWinHeight = sal_uInt16( aWinMet.GetFontSize().Height() );
515 if( m_pPrtFont->GetFamilyName().indexOf( aWinMet.GetFamilyName() ) != -1 )
517 // If the Leading on the Window is also 0, then it has to stay
518 // that way (see also StarMath).
519 tools::Long nTmpLeading = aWinMet.GetInternalLeading();
520 if( nTmpLeading <= 0 )
522 pWin->SetFont( rMet );
523 nTmpLeading = pWin->GetFontMetric().GetInternalLeading();
524 if( nTmpLeading < 0 )
525 m_nGuessedLeading = 0;
526 else
527 m_nGuessedLeading = sal_uInt16(nTmpLeading);
529 else
531 m_nGuessedLeading = sal_uInt16(nTmpLeading);
532 // Manta-Hack #50153#:
533 // Wer beim Leading luegt, luegt moeglicherweise auch beim
534 // Ascent/Descent, deshalb wird hier ggf. der Font ein wenig
535 // tiefergelegt, ohne dabei seine Hoehe zu aendern.
536 // (above original comment preserved for cultural reasons)
537 // Those who lie about their Leading, may lie about their
538 // Ascent/Descent as well, hence the Font will be lowered a
539 // little without changing its height.
540 tools::Long nDiff = std::min( rMet.GetDescent() - aWinMet.GetDescent(),
541 aWinMet.GetAscent() - rMet.GetAscent() - nTmpLeading );
542 if( nDiff > 0 )
544 OSL_ENSURE( m_nPrtAscent < USHRT_MAX, "GuessLeading: PrtAscent-Fault" );
545 if ( m_nPrtAscent < USHRT_MAX )
546 m_nPrtAscent = m_nPrtAscent + o3tl::narrowing<sal_uInt16>(( 2 * nDiff ) / 5);
550 else
552 // If all else fails, take 15% of the height, as empirically
553 // determined by CL
554 m_nGuessedLeading = (nWinHeight * 15) / 100;
556 pWin->SetFont( aOldFnt );
557 pWin->SetMapMode( aOldMap );
559 else
560 m_nGuessedLeading = 0;
561 #else
562 m_nGuessedLeading = 0;
563 #endif
566 // Set the font at the given output device; for screens it may be
567 // necessary to do some adjustment first.
568 void SwFntObj::SetDevFont( const SwViewShell *pSh, OutputDevice& rOut )
570 const OutputDevice& rRefDev = pSh ? pSh->GetRefDev() : rOut;
572 if ( pSh && lcl_IsFontAdjustNecessary( rOut, rRefDev ) )
574 CreateScrFont( *pSh, rOut );
575 if( !GetScrFont()->IsSameInstance( rOut.GetFont() ) )
576 rOut.SetFont( *m_pScrFont );
577 if( m_pPrinter && ( !m_pPrtFont->IsSameInstance( m_pPrinter->GetFont() ) ) )
578 m_pPrinter->SetFont( *m_pPrtFont );
580 else
582 CreatePrtFont( rOut );
583 if( !m_pPrtFont->IsSameInstance( rOut.GetFont() ) )
584 rOut.SetFont( *m_pPrtFont );
587 // Here, we actually do not need the leading values, but by calling
588 // GetFontLeading() we assure that the values are calculated for later use.
589 GetFontLeading( pSh, rRefDev );
592 #define WRONG_SHOW_MIN 5
595 * Output text:
596 * on screen => DrawTextArray
597 * on printer, !Kerning => DrawText
598 * on printer + Kerning => DrawStretchText
600 static bool lcl_IsMonoSpaceFont( const vcl::RenderContext& rOut )
602 const tools::Long nWidth1 = rOut.GetTextWidth( OUString( u'\x3008' ) );
603 const tools::Long nWidth2 = rOut.GetTextWidth( OUString( u'\x307C' ) );
604 return nWidth1 == nWidth2;
607 static bool lcl_IsFullstopCentered( const vcl::RenderContext& rOut )
609 const FontMetric aMetric( rOut.GetFontMetric() );
610 return aMetric.IsFullstopCentered() ;
613 /* This helper structure (SwForbidden) contains the already marked parts of the string
614 to avoid double lines (e.g grammar + spell check error) */
616 typedef std::vector<std::pair<TextFrameIndex, TextFrameIndex>> SwForbidden;
618 static void lcl_DrawLineForWrongListData(
619 SwForbidden &rForbidden,
620 const SwDrawTextInfo &rInf,
621 sw::WrongListIterator *pWList,
622 const CalcLinePosData &rCalcLinePosData,
623 const Size &rPrtFontSize )
625 if (!pWList) return;
627 TextFrameIndex nStart = rInf.GetIdx();
628 TextFrameIndex nWrLen = rInf.GetLen();
630 // check if respective data is available in the current text range
631 if (!pWList->Check( nStart, nWrLen ))
633 return;
636 tools::Long nHght = rInf.GetOut().LogicToPixel( rPrtFontSize ).Height();
638 // Draw wavy lines for spell and grammar errors only if font is large enough.
639 // Lines for smart tags will always be drawn.
640 if (pWList != rInf.GetSmartTags() && WRONG_SHOW_MIN >= nHght)
642 return;
645 SwForbidden::iterator pIter = rForbidden.begin();
646 if (rInf.GetOut().GetConnectMetaFile())
647 rInf.GetOut().Push();
649 const Color aCol( rInf.GetOut().GetLineColor() );
651 // iterate over all ranges stored in the respective SwWrongList
654 nStart -= rInf.GetIdx();
656 const TextFrameIndex nEnd = nStart + nWrLen;
657 TextFrameIndex nNext = nStart;
658 while( nNext < nEnd )
660 while( pIter != rForbidden.end() && pIter->second <= nNext )
661 ++pIter;
663 const TextFrameIndex nNextStart = nNext;
664 TextFrameIndex nNextEnd = nEnd;
666 if( pIter == rForbidden.end() || nNextEnd <= pIter->first )
668 // No overlapping mark up found
669 rForbidden.insert(pIter, std::make_pair(nNextStart, nNextEnd));
670 pIter = rForbidden.begin();
671 nNext = nEnd;
673 else
675 nNext = pIter->second;
676 if( nNextStart < pIter->first )
678 nNextEnd = pIter->first;
679 pIter->first = nNextStart;
681 else
682 continue;
684 // determine line pos
685 Point aStart( rInf.GetPos() );
686 Point aEnd;
687 lcl_calcLinePos( rCalcLinePosData, aStart, aEnd, nNextStart, nNextEnd - nNextStart );
689 SwWrongArea const*const wrongArea = pWList->GetWrongElement(nNextStart + rInf.GetIdx());
690 if (wrongArea != nullptr)
692 const SwViewShell* pShell = rInf.GetShell();
693 sal_uInt16 nZoom = pShell ? round(pShell->GetViewOptions()->GetZoom()/100) : 1;
694 if (WRONGAREA_WAVE == wrongArea->mLineType)
696 vcl::ScopedAntialiasing a(rInf.GetOut(), true);
697 rInf.GetOut().SetLineColor( wrongArea->mColor );
698 rInf.GetOut().DrawWaveLine( aStart, aEnd, 1 + nZoom, 3 + nZoom );
700 else if (WRONGAREA_BOLDWAVE == wrongArea->mLineType)
702 vcl::ScopedAntialiasing a(rInf.GetOut(), true);
703 rInf.GetOut().SetLineColor( wrongArea->mColor );
704 rInf.GetOut().DrawWaveLine( aStart, aEnd, 2 + nZoom, 4 + nZoom );
706 else if (WRONGAREA_BOLD == wrongArea->mLineType)
708 rInf.GetOut().SetLineColor( wrongArea->mColor );
710 aStart.AdjustY(30 );
711 aEnd.AdjustY(30 );
713 LineInfo aLineInfo( LineStyle::Solid, 26 );
715 rInf.GetOut().DrawLine( aStart, aEnd, aLineInfo );
717 else if (WRONGAREA_DASHED == wrongArea->mLineType)
719 rInf.GetOut().SetLineColor( wrongArea->mColor );
721 aStart.AdjustY(30 );
722 aEnd.AdjustY(30 );
724 LineInfo aLineInfo( LineStyle::Dash );
725 aLineInfo.SetDistance( 40 );
726 aLineInfo.SetDashLen( 1 );
727 aLineInfo.SetDashCount(1);
729 rInf.GetOut().DrawLine( aStart, aEnd, aLineInfo );
734 nStart = nEnd + rInf.GetIdx();
735 nWrLen = rInf.GetIdx() + rInf.GetLen() - nStart;
737 while (nWrLen && pWList->Check( nStart, nWrLen ));
739 rInf.GetOut().SetLineColor( aCol );
741 if (rInf.GetOut().GetConnectMetaFile())
742 rInf.GetOut().Pop();
745 static void GetTextArray(const OutputDevice& rDevice, const OUString& rStr, KernArray& rDXAry,
746 sal_Int32 nIndex, sal_Int32 nLen, bool bCaret = false,
747 const vcl::text::TextLayoutCache* layoutCache = nullptr)
749 const SalLayoutGlyphs* pLayoutCache = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(&rDevice, rStr, nIndex, nLen,
750 0, layoutCache);
751 rDevice.GetTextArray(rStr, &rDXAry, nIndex, nLen, bCaret, layoutCache, pLayoutCache);
754 static void GetTextArray(const OutputDevice& rOutputDevice, const SwDrawTextInfo& rInf, KernArray& rDXAry,
755 bool bCaret = false)
757 return GetTextArray(rOutputDevice, rInf.GetText(), rDXAry, rInf.GetIdx().get(), rInf.GetLen().get(),
758 bCaret, rInf.GetVclCache());
761 static void GetTextArray(const OutputDevice& rOutputDevice, const SwDrawTextInfo& rInf, KernArray& rDXAry,
762 sal_Int32 nLen, bool bCaret = false)
764 // Substring is fine.
765 assert( nLen <= rInf.GetLen().get());
766 return GetTextArray(rOutputDevice, rInf.GetText(), rDXAry, rInf.GetIdx().get(), nLen, bCaret, rInf.GetVclCache());
769 void SwFntObj::DrawText( SwDrawTextInfo &rInf )
771 OSL_ENSURE( rInf.GetShell(), "SwFntObj::DrawText without shell" );
773 OutputDevice& rRefDev = rInf.GetShell()->GetRefDev();
774 vcl::Window* pWin = rInf.GetShell()->GetWin();
776 // true if pOut is the printer and the printer has been used for formatting
777 const bool bPrt = OUTDEV_PRINTER == rInf.GetOut().GetOutDevType() &&
778 OUTDEV_PRINTER == rRefDev.GetOutDevType();
779 const bool bBrowse = ( pWin &&
780 rInf.GetShell()->GetViewOptions()->getBrowseMode() &&
781 !rInf.GetShell()->GetViewOptions()->IsPrtFormat() &&
782 !rInf.GetBullet() &&
783 ( rInf.GetSpace() || !rInf.GetKern() ) &&
784 !rInf.GetWrong() &&
785 !rInf.GetGrammarCheck() &&
786 !rInf.GetSmartTags() &&
787 !rInf.GetGreyWave() );
789 // bDirectPrint indicates that we can enter the branch which calls
790 // the DrawText functions instead of calling the DrawTextArray functions
791 const bool bDirectPrint = bPrt || bBrowse;
793 // Condition for output font / refdev font adjustment
794 const bool bUseScrFont =
795 lcl_IsFontAdjustNecessary( rInf.GetOut(), rRefDev );
797 vcl::Font* pTmpFont = bUseScrFont ? m_pScrFont : m_pPrtFont;
799 // bDirectPrint and bUseScrFont should have these values:
801 // Outdev / RefDef | Printer | VirtPrinter | Window
803 // Printer | 1 - 0 | 0 - 1 | -
805 // VirtPrinter/PDF | 0 - 1 | 0 - 1 | -
807 // Window/VirtWindow| 0 - 1 | 0 - 1 | 1 - 0
809 // Exception: During painting of a Writer OLE object, we do not have
810 // a window. Therefore bUseSrcFont is always 0 in this case.
812 #if OSL_DEBUG_LEVEL > 0
814 const bool bNoAdjust = bPrt ||
815 ( pWin &&
816 rInf.GetShell()->GetViewOptions()->getBrowseMode() &&
817 !rInf.GetShell()->GetViewOptions()->IsPrtFormat() );
819 if ( OUTDEV_PRINTER == rInf.GetOut().GetOutDevType() )
821 // Printer output
822 if ( OUTDEV_PRINTER == rRefDev.GetOutDevType() )
824 OSL_ENSURE( bNoAdjust && !bUseScrFont, "Outdev Check failed" );
826 else if ( rRefDev.IsVirtual() )
828 OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" );
830 else
832 OSL_FAIL( "Outdev Check failed" );
835 else if ( rInf.GetOut().IsVirtual() && ! pWin )
837 // PDF export
838 if ( OUTDEV_PRINTER == rRefDev.GetOutDevType() )
840 OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" );
842 else if ( rRefDev.IsVirtual() )
844 OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" );
846 else
848 OSL_FAIL( "Outdev Check failed" );
851 else if ( OUTDEV_WINDOW == rInf.GetOut().GetOutDevType() ||
852 ( rInf.GetOut().IsVirtual() && pWin ) )
854 // Window or virtual window
855 if ( OUTDEV_PRINTER == rRefDev.GetOutDevType() )
857 OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" );
859 else if ( rRefDev.IsVirtual() )
861 OSL_ENSURE( !bNoAdjust && bUseScrFont, "Outdev Check failed" );
863 else if ( OUTDEV_WINDOW == rRefDev.GetOutDevType() )
865 OSL_ENSURE( bNoAdjust && !bUseScrFont, "Outdev Check failed" );
867 else
869 OSL_FAIL( "Outdev Check failed" );
872 else
874 OSL_FAIL( "Outdev Check failed" );
877 #endif
879 // robust: better use the printer font instead of using no font at all
880 OSL_ENSURE( pTmpFont, "No screen or printer font?" );
881 if ( ! pTmpFont )
882 pTmpFont = m_pPrtFont;
884 // HACK: LINESTYLE_WAVE must not be abused any more, hence the grey wave
885 // line of the ExtendedAttributeSets will appear in the font color first
887 const bool bSwitchH2V = rInf.GetFrame() && rInf.GetFrame()->IsVertical();
888 const bool bSwitchH2VLRBT = rInf.GetFrame() && rInf.GetFrame()->IsVertLRBT();
889 const bool bSwitchL2R = rInf.GetFrame() && rInf.GetFrame()->IsRightToLeft() &&
890 ! rInf.IsIgnoreFrameRTL();
891 const vcl::text::ComplexTextLayoutFlags nMode = rInf.GetOut().GetLayoutMode();
892 const bool bBidiPor = ( bSwitchL2R !=
893 ( vcl::text::ComplexTextLayoutFlags::Default != ( vcl::text::ComplexTextLayoutFlags::BiDiRtl & nMode ) ) );
895 // be sure to have the correct layout mode at the printer
896 if ( m_pPrinter )
898 m_pPrinter->SetLayoutMode( rInf.GetOut().GetLayoutMode() );
899 m_pPrinter->SetDigitLanguage( rInf.GetOut().GetDigitLanguage() );
902 Point aTextOriginPos( rInf.GetPos() );
903 if( !bPrt )
905 if( rInf.GetpOut() != *s_pFntObjPixOut.get() || rInf.GetOut().GetMapMode() != *s_pPixMap )
907 *s_pPixMap = rInf.GetOut().GetMapMode();
908 (*s_pFntObjPixOut.get()) = rInf.GetpOut();
909 Size aTmp( 1, 1 );
910 s_nPixWidth = rInf.GetOut().PixelToLogic( aTmp ).Width();
913 aTextOriginPos.AdjustX(rInf.GetFrame()->IsRightToLeft() ? 0 : s_nPixWidth );
916 Color aOldColor( pTmpFont->GetColor() );
917 bool bChgColor = rInf.ApplyAutoColor( pTmpFont );
918 if( !pTmpFont->IsSameInstance( rInf.GetOut().GetFont() ) )
919 rInf.GetOut().SetFont( *pTmpFont );
920 if ( bChgColor )
921 pTmpFont->SetColor( aOldColor );
923 if (TextFrameIndex(COMPLETE_STRING) == rInf.GetLen())
924 rInf.SetLen( TextFrameIndex(rInf.GetText().getLength()) );
926 // ASIAN LINE AND CHARACTER GRID MODE START
928 if ( rInf.GetFrame() && rInf.SnapToGrid() && rInf.GetFont() &&
929 SwFontScript::CJK == rInf.GetFont()->GetActual() )
931 SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame()));
933 // ASIAN LINE AND CHARACTER GRID MODE
934 if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() )
936 //for textgrid refactor
937 const SwDoc* pDoc = rInf.GetShell()->GetDoc();
938 const tools::Long nGridWidth = GetGridWidth(*pGrid, *pDoc);
939 tools::Long nSpaceAdd = rInf.GetSpace() / SPACING_PRECISION_FACTOR;
941 // kerning array - gives the absolute position of end of each character
942 KernArray aKernArray;
944 if ( m_pPrinter )
945 GetTextArray(*m_pPrinter, rInf, aKernArray);
946 else
947 GetTextArray(rInf.GetOut(), rInf, aKernArray);
949 tools::Long nDelta = 0;
951 if (pGrid->IsSnapToChars())
953 nDelta = sw::Justify::SnapToGrid(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()),
954 sal_Int32(rInf.GetLen()), nGridWidth, false);
956 else
958 sw::Justify::SnapToGridEdge(aKernArray, sal_Int32(rInf.GetLen()), nGridWidth,
959 nSpaceAdd, rInf.GetKern());
962 if (nDelta)
963 aTextOriginPos.AdjustX(nDelta);
965 if ( bSwitchH2V )
966 rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos );
968 rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
969 aKernArray, {}, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
971 return;
975 // DIRECT PAINTING WITHOUT SCREEN ADJUSTMENT
977 if ( bDirectPrint )
979 const Fraction aTmp( 1, 1 );
980 bool bStretch = rInf.GetWidth() && (rInf.GetLen() > TextFrameIndex(1)) && bPrt
981 && ( aTmp != rInf.GetOut().GetMapMode().GetScaleX() );
983 if ( bSwitchL2R )
984 rInf.GetFrame()->SwitchLTRtoRTL( aTextOriginPos );
986 if ( bSwitchH2V )
987 rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos );
989 // In the good old days we used to have a simple DrawText if the
990 // output device is the printer. Now we need a DrawTextArray if
991 // 1. KanaCompression is enabled
992 // 2. Justified alignment
993 // Simple kerning is handled by DrawStretchText
994 if( rInf.GetSpace() || rInf.GetKanaComp() )
996 KernArray aKernArray;
997 GetTextArray(rInf.GetOut(), rInf, aKernArray);
998 std::vector<sal_Bool> aKashidaArray;
1000 if( bStretch )
1002 sal_Int32 nZwi = sal_Int32(rInf.GetLen()) - 1;
1003 tools::Long nDiff = rInf.GetWidth() - aKernArray[ nZwi ]
1004 - sal_Int32(rInf.GetLen()) * rInf.GetKern();
1005 tools::Long nRest = nDiff % nZwi;
1006 tools::Long nAdd;
1007 if( nRest < 0 )
1009 nAdd = -1;
1010 nRest += nZwi;
1012 else
1014 nAdd = +1;
1015 nRest = nZwi - nRest;
1017 nDiff /= nZwi;
1018 tools::Long nSum = nDiff;
1019 for( sal_Int32 i = 0; i < nZwi; )
1021 aKernArray.adjust(i, nSum);
1022 if( ++i == nRest )
1023 nDiff += nAdd;
1024 nSum += nDiff;
1028 // Modify Array for special justifications
1030 tools::Long nSpaceAdd = rInf.GetSpace() / SPACING_PRECISION_FACTOR;
1031 bool bSpecialJust = false;
1033 if ( rInf.GetFont() && rInf.GetLen() )
1035 const SwScriptInfo* pSI = rInf.GetScriptInfo();
1036 const SwFontScript nActual = rInf.GetFont()->GetActual();
1038 // Kana Compression
1039 if ( SwFontScript::CJK == nActual && rInf.GetKanaComp() &&
1040 pSI && pSI->CountCompChg() &&
1041 lcl_IsMonoSpaceFont( rInf.GetOut() ) )
1043 pSI->Compress( aKernArray, rInf.GetIdx(), rInf.GetLen(),
1044 rInf.GetKanaComp(),
1045 o3tl::narrowing<sal_uInt16>(m_aFont.GetFontSize().Height()), lcl_IsFullstopCentered( rInf.GetOut() ), &aTextOriginPos );
1046 bSpecialJust = true;
1049 // Asian Justification
1050 if ( SwFontScript::CJK == nActual && nSpaceAdd )
1052 LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CJK );
1054 if (!MsLangId::isKorean(aLang))
1056 SwScriptInfo::CJKJustify( rInf.GetText(), aKernArray,
1057 rInf.GetIdx(), rInf.GetLen(), aLang, nSpaceAdd, rInf.IsSpaceStop() );
1059 bSpecialJust = true;
1060 nSpaceAdd = 0;
1064 // Kashida Justification
1065 if ( SwFontScript::CTL == nActual && nSpaceAdd )
1067 if ( SwScriptInfo::IsArabicText( rInf.GetText(), rInf.GetIdx(), rInf.GetLen() ) )
1069 aKashidaArray.resize(aKernArray.size(), false);
1070 if ( pSI && pSI->CountKashida() &&
1071 pSI->KashidaJustify( &aKernArray, aKashidaArray.data(), rInf.GetIdx(),
1072 rInf.GetLen(), nSpaceAdd ) != -1 )
1074 bSpecialJust = true;
1075 nSpaceAdd = 0;
1077 else
1078 aKashidaArray.clear();
1082 // Thai Justification
1083 if ( SwFontScript::CTL == nActual && nSpaceAdd )
1085 LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CTL );
1087 if ( LANGUAGE_THAI == aLang )
1089 // Use rInf.GetSpace() because it has more precision than
1090 // nSpaceAdd:
1091 SwScriptInfo::ThaiJustify( rInf.GetText(), &aKernArray,
1092 rInf.GetIdx(), rInf.GetLen(),
1093 rInf.GetNumberOfBlanks(),
1094 rInf.GetSpace() );
1096 // adding space to blanks is already done
1097 bSpecialJust = true;
1098 nSpaceAdd = 0;
1103 tools::Long nKernSum = rInf.GetKern();
1105 if ( bStretch || m_bPaintBlank || rInf.GetKern() || bSpecialJust )
1107 for (sal_Int32 i = 0; i < sal_Int32(rInf.GetLen()); i++,
1108 nKernSum += rInf.GetKern() )
1110 if (CH_BLANK == rInf.GetText()[sal_Int32(rInf.GetIdx()) + i])
1111 nKernSum += nSpaceAdd;
1112 aKernArray.adjust(i, nKernSum);
1115 // In case of underlined/strike-through justified text
1116 // a blank at the end requires special handling:
1117 if( m_bPaintBlank && rInf.GetLen() && ( CH_BLANK ==
1118 rInf.GetText()[sal_Int32(rInf.GetIdx() + rInf.GetLen())-1]))
1120 // If it is a single underlined space, output 2 spaces:
1121 if (TextFrameIndex(1) == rInf.GetLen())
1123 aKernArray.set(0, rInf.GetWidth() + nSpaceAdd);
1125 rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
1126 aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), 1 );
1128 else
1130 sal_Int32 nIndex(sal_Int32(rInf.GetLen()) - 2);
1131 aKernArray.adjust(nIndex, nSpaceAdd);
1132 rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
1133 aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
1136 else
1137 rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
1138 aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
1140 else
1142 Point aTmpPos( aTextOriginPos );
1143 sal_Int32 j = 0;
1144 sal_Int32 i;
1145 for( i = 0; i < sal_Int32(rInf.GetLen()); i++ )
1147 if (CH_BLANK == rInf.GetText()[sal_Int32(rInf.GetIdx()) + i])
1149 nKernSum += nSpaceAdd;
1150 if( j < i )
1151 rInf.GetOut().DrawText( aTmpPos, rInf.GetText(),
1152 sal_Int32(rInf.GetIdx()) + j, i - j);
1153 j = i + 1;
1154 SwTwips nAdd = aKernArray[ i ] + nKernSum;
1155 if ( ( vcl::text::ComplexTextLayoutFlags::BiDiStrong | vcl::text::ComplexTextLayoutFlags::BiDiRtl ) == nMode )
1156 nAdd *= -1;
1157 aTmpPos.setX( aTextOriginPos.X() + nAdd );
1160 if( j < i )
1161 rInf.GetOut().DrawText( aTmpPos, rInf.GetText(),
1162 sal_Int32(rInf.GetIdx()) + j, i - j);
1165 else if( bStretch )
1167 tools::Long nTmpWidth = rInf.GetWidth();
1168 if( rInf.GetKern() && rInf.GetLen() && nTmpWidth > rInf.GetKern() )
1169 nTmpWidth -= rInf.GetKern();
1170 rInf.GetOut().DrawStretchText( aTextOriginPos, nTmpWidth,
1171 rInf.GetText(),
1172 sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
1174 else if( rInf.GetKern() )
1176 const tools::Long nTmpWidth = GetTextSize( rInf ).Width();
1178 const Color aSaveColor( pTmpFont->GetColor() );
1179 const bool bColorChanged = rInf.ApplyAutoColor( pTmpFont );
1181 if( bColorChanged )
1183 if( !pTmpFont->IsSameInstance( rInf.GetOut().GetFont() ) )
1184 rInf.GetOut().SetFont( *pTmpFont );
1185 pTmpFont->SetColor( aSaveColor );
1188 rInf.GetOut().DrawStretchText( aTextOriginPos, nTmpWidth,
1189 rInf.GetText(),
1190 sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
1192 else
1193 rInf.GetOut().DrawText( aTextOriginPos, rInf.GetText(),
1194 sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
1197 // PAINTING WITH FORMATTING DEVICE/SCREEN ADJUSTMENT
1199 else
1201 OUString aBulletOverlay;
1202 bool bBullet = rInf.GetBullet();
1203 if( m_bSymbol )
1204 bBullet = false;
1205 CreateScrFont( *rInf.GetShell(), rInf.GetOut() );
1207 VclPtr<OutputDevice> xFormattingDevice;
1209 // OLE: no printer available
1210 // OSL_ENSURE( pPrinter, "DrawText needs pPrinter" )
1211 if ( m_pPrinter )
1213 // pTmpFont has already been set as current font for rInf.GetOut()
1214 if ( m_pPrinter.get() != rInf.GetpOut() || pTmpFont != m_pPrtFont )
1216 if( !m_pPrtFont->IsSameInstance( m_pPrinter->GetFont() ) )
1217 m_pPrinter->SetFont( *m_pPrtFont );
1219 xFormattingDevice = m_pPrinter;
1221 else
1223 xFormattingDevice = &rInf.GetOut();
1226 //tdf#152094 see if we can retain a subpixel factor
1227 int nSubPixels = 1;
1228 MapMode aMapMode(xFormattingDevice->GetMapMode());
1229 if (aMapMode.IsSimple() && aMapMode.GetMapUnit() == MapUnit::MapTwip)
1231 if (xFormattingDevice->GetDPIX() == xFormattingDevice->GetDPIY())
1233 int nRatio = xFormattingDevice->GetDPIX() / 1440;
1234 if (nRatio * 1440 == xFormattingDevice->GetDPIX())
1235 nSubPixels = nRatio;
1238 KernArray aKernArray(nSubPixels);
1239 GetTextArray(*xFormattingDevice, rInf, aKernArray);
1241 std::vector<sal_Bool> aKashidaArray;
1243 // Modify Printer and ScreenArrays for special justifications
1245 tools::Long nSpaceAdd = rInf.GetSpace() / SPACING_PRECISION_FACTOR;
1246 bool bNoHalfSpace = false;
1248 if ( rInf.GetFont() && rInf.GetLen() )
1250 const SwFontScript nActual = rInf.GetFont()->GetActual();
1251 const SwScriptInfo* pSI = rInf.GetScriptInfo();
1253 // Kana Compression
1254 if ( SwFontScript::CJK == nActual && rInf.GetKanaComp() &&
1255 pSI && pSI->CountCompChg() &&
1256 lcl_IsMonoSpaceFont( rInf.GetOut() ) )
1258 pSI->Compress( aKernArray, rInf.GetIdx(), rInf.GetLen(),
1259 rInf.GetKanaComp(),
1260 o3tl::narrowing<sal_uInt16>(m_aFont.GetFontSize().Height()), lcl_IsFullstopCentered( rInf.GetOut() ), &aTextOriginPos );
1263 // Asian Justification
1264 if ( SwFontScript::CJK == nActual && nSpaceAdd )
1266 LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CJK );
1268 if (!MsLangId::isKorean(aLang))
1270 SwScriptInfo::CJKJustify( rInf.GetText(), aKernArray,
1271 rInf.GetIdx(), rInf.GetLen(), aLang, nSpaceAdd, rInf.IsSpaceStop() );
1273 nSpaceAdd = 0;
1277 // Kashida Justification
1278 if ( SwFontScript::CTL == nActual && nSpaceAdd )
1280 if ( SwScriptInfo::IsArabicText( rInf.GetText(), rInf.GetIdx(), rInf.GetLen() ) )
1282 aKashidaArray.resize(aKernArray.size(), false);
1283 if ( pSI && pSI->CountKashida() &&
1284 pSI->KashidaJustify( &aKernArray, aKashidaArray.data(), rInf.GetIdx(),
1285 rInf.GetLen(), nSpaceAdd ) != -1 )
1286 nSpaceAdd = 0;
1287 else
1289 aKashidaArray.clear();
1290 bNoHalfSpace = true;
1295 // Thai Justification
1296 if ( SwFontScript::CTL == nActual && nSpaceAdd )
1298 LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CTL );
1300 if ( LANGUAGE_THAI == aLang )
1302 SwScriptInfo::ThaiJustify( rInf.GetText(), &aKernArray,
1303 rInf.GetIdx(),
1304 rInf.GetLen(),
1305 rInf.GetNumberOfBlanks(),
1306 rInf.GetSpace() );
1308 // adding space to blanks is already done
1309 nSpaceAdd = 0;
1314 if( bBullet )
1316 // Copy the substring that will be painted, and replace spaces with
1317 // bullets, and everything else with space.
1318 sal_Int32 nCopyStart = sal_Int32(rInf.GetIdx());
1319 sal_Int32 nCopyLen = sal_Int32(rInf.GetLen());
1321 aBulletOverlay = rInf.GetText().copy( nCopyStart, nCopyLen );
1323 for( sal_Int32 i = 0; i < aBulletOverlay.getLength(); ++i )
1324 if( CH_BLANK == aBulletOverlay[ i ] )
1326 /* fdo#72488 Hack: try to see if the space is zero width
1327 * and don't bother with inserting a bullet in this case.
1329 if ((i + nCopyStart + 1 >= sal_Int32(rInf.GetLen())) ||
1330 aKernArray[i + nCopyStart] != aKernArray[ i + nCopyStart + 1])
1332 aBulletOverlay = aBulletOverlay.replaceAt(i, 1, rtl::OUStringChar(CH_BULLET));
1334 else
1336 aBulletOverlay = aBulletOverlay.replaceAt(i, 1, rtl::OUStringChar(CH_BLANK));
1339 else
1341 aBulletOverlay = aBulletOverlay.replaceAt(i, 1, rtl::OUStringChar(CH_BLANK));
1345 TextFrameIndex nCnt(rInf.GetText().getLength());
1346 if ( nCnt < rInf.GetIdx() )
1347 assert(false); // layout bug, not handled below
1348 else
1349 nCnt = nCnt - rInf.GetIdx();
1350 nCnt = std::min(nCnt, rInf.GetLen());
1351 sal_Unicode cChPrev = rInf.GetText()[sal_Int32(rInf.GetIdx())];
1353 // In case of a single underlined space in justified text,
1354 // have to output 2 spaces:
1355 if ((nCnt == TextFrameIndex(1)) && rInf.GetSpace() && (cChPrev == CH_BLANK))
1357 aKernArray.set(0, rInf.GetWidth() +
1358 rInf.GetKern() +
1359 (rInf.GetSpace() / SPACING_PRECISION_FACTOR));
1361 if ( bSwitchL2R )
1362 rInf.GetFrame()->SwitchLTRtoRTL( aTextOriginPos );
1364 if ( bSwitchH2V )
1365 rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos );
1367 rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
1368 aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), 1 );
1369 if( bBullet )
1370 rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(), aKernArray, {},
1371 sal_Int32(rInf.GetIdx()), 1 );
1373 else
1375 if (m_pPrtFont->IsWordLineMode())
1376 bNoHalfSpace = true;
1378 sw::Justify::SpaceDistribution(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()),
1379 sal_Int32(nCnt), nSpaceAdd, rInf.GetKern(), bNoHalfSpace);
1381 if( rInf.GetGreyWave() )
1383 if( rInf.GetLen() )
1385 tools::Long nHght = rInf.GetOut().LogicToPixel(
1386 m_pPrtFont->GetFontSize() ).Height();
1387 if( WRONG_SHOW_MIN < nHght )
1389 if ( rInf.GetOut().GetConnectMetaFile() )
1390 rInf.GetOut().Push();
1392 Color aCol( rInf.GetOut().GetLineColor() );
1393 bool bColSave = aCol != gWaveCol;
1394 if ( bColSave )
1395 rInf.GetOut().SetLineColor( gWaveCol );
1397 Point aEnd;
1398 tools::Long nKernVal = aKernArray[sal_Int32(rInf.GetLen()) - 1];
1400 const Degree10 nDir = bBidiPor
1401 ? 1800_deg10
1402 : UnMapDirection(GetFont().GetOrientation(),
1403 bSwitchH2V, bSwitchH2VLRBT);
1405 switch ( nDir.get() )
1407 case 0 :
1408 aEnd.setX( rInf.GetPos().X() + nKernVal );
1409 aEnd.setY( rInf.GetPos().Y() );
1410 break;
1411 case 900 :
1412 aEnd.setX( rInf.GetPos().X() );
1413 aEnd.setY( rInf.GetPos().Y() - nKernVal );
1414 break;
1415 case 1800 :
1416 aEnd.setX( rInf.GetPos().X() - nKernVal );
1417 aEnd.setY( rInf.GetPos().Y() );
1418 break;
1419 case 2700 :
1420 aEnd.setX( rInf.GetPos().X() );
1421 aEnd.setY( rInf.GetPos().Y() + nKernVal );
1422 break;
1425 Point aCurrPos( rInf.GetPos() );
1427 if ( bSwitchL2R )
1429 rInf.GetFrame()->SwitchLTRtoRTL( aCurrPos );
1430 rInf.GetFrame()->SwitchLTRtoRTL( aEnd );
1433 if ( bSwitchH2V )
1435 rInf.GetFrame()->SwitchHorizontalToVertical( aCurrPos );
1436 rInf.GetFrame()->SwitchHorizontalToVertical( aEnd );
1439 vcl::ScopedAntialiasing a(rInf.GetOut(), true);
1440 rInf.GetOut().DrawWaveLine( aCurrPos, aEnd );
1442 if ( bColSave )
1443 rInf.GetOut().SetLineColor( aCol );
1445 if ( rInf.GetOut().GetConnectMetaFile() )
1446 rInf.GetOut().Pop();
1450 else if( !m_bSymbol && rInf.GetLen() )
1452 // anything to do?
1453 if (rInf.GetWrong() || rInf.GetGrammarCheck() || rInf.GetSmartTags())
1455 const tools::Long nHalfSpace = bNoHalfSpace ? 0 : nSpaceAdd / 2;
1456 CalcLinePosData aCalcLinePosData(rInf, GetFont(), nCnt, bSwitchH2V,
1457 bSwitchH2VLRBT, bSwitchL2R, nHalfSpace,
1458 aKernArray, bBidiPor);
1460 SwForbidden aForbidden;
1461 // draw line for smart tag data
1462 lcl_DrawLineForWrongListData( aForbidden, rInf, rInf.GetSmartTags(), aCalcLinePosData, Size() );
1463 // draw wave line for spell check errors
1464 // draw them BEFORE the grammar check lines to 'override' the latter in case of conflict.
1465 // reason: some grammar errors can only be found if spelling errors are fixed,
1466 // therefore we don't want the user to miss a spelling error.
1467 lcl_DrawLineForWrongListData( aForbidden, rInf, rInf.GetWrong(), aCalcLinePosData, m_pPrtFont->GetFontSize() );
1468 // draw wave line for grammar check errors
1469 lcl_DrawLineForWrongListData( aForbidden, rInf, rInf.GetGrammarCheck(), aCalcLinePosData, m_pPrtFont->GetFontSize() );
1473 sal_Int32 nLen = sal_Int32(rInf.GetLen());
1475 if( nLen > 0 )
1478 if ( bSwitchL2R )
1479 rInf.GetFrame()->SwitchLTRtoRTL( aTextOriginPos );
1481 if ( bSwitchH2V )
1482 rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos );
1484 sal_Int32 nIdx = sal_Int32(rInf.GetIdx());
1485 const SalLayoutGlyphs* pGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(&rInf.GetOut(),
1486 rInf.GetText(), nIdx, nLen);
1487 rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(), aKernArray, aKashidaArray,
1488 nIdx, nLen, SalLayoutFlags::NONE, pGlyphs );
1489 if (bBullet)
1491 rInf.GetOut().Push();
1492 Color aPreviousColor = pTmpFont->GetColor();
1494 FontLineStyle aPreviousUnderline = pTmpFont->GetUnderline();
1495 FontLineStyle aPreviousOverline = pTmpFont->GetOverline();
1496 FontStrikeout aPreviousStrikeout = pTmpFont->GetStrikeout();
1498 pTmpFont->SetColor( NON_PRINTING_CHARACTER_COLOR );
1499 pTmpFont->SetUnderline(LINESTYLE_NONE);
1500 pTmpFont->SetOverline(LINESTYLE_NONE);
1501 pTmpFont->SetStrikeout(STRIKEOUT_NONE);
1502 rInf.GetOut().SetFont( *pTmpFont );
1503 tools::Long nShift = rInf.GetOut( ).GetFontMetric( ).GetBulletOffset( );
1504 if ( nShift )
1506 tools::Long nAdd = 0;
1508 if ( aBulletOverlay[ 0 ] == CH_BULLET )
1510 if (bSwitchH2V)
1511 aTextOriginPos.AdjustY(nShift ) ;
1512 else
1513 aTextOriginPos.AdjustX(nShift ) ;
1514 nAdd = nShift ;
1516 for( sal_Int32 i = 1 ; i < nLen ; ++i )
1518 if ( aBulletOverlay[ i ] == CH_BULLET )
1519 aKernArray.adjust(i - 1, nShift);
1520 if ( nAdd )
1521 aKernArray.adjust(i - 1, -nAdd);
1524 rInf.GetOut().DrawTextArray( aTextOriginPos, aBulletOverlay, aKernArray,
1525 {}, 0, nLen );
1526 pTmpFont->SetColor( aPreviousColor );
1528 pTmpFont->SetUnderline(aPreviousUnderline);
1529 pTmpFont->SetOverline(aPreviousOverline);
1530 pTmpFont->SetStrikeout(aPreviousStrikeout);
1531 rInf.GetOut().Pop();
1538 Size SwFntObj::GetTextSize( SwDrawTextInfo& rInf )
1540 Size aTextSize;
1541 const TextFrameIndex nLn = (TextFrameIndex(COMPLETE_STRING) != rInf.GetLen())
1542 ? rInf.GetLen()
1543 : TextFrameIndex(rInf.GetText().getLength());
1545 const TextFrameIndex nMsrLn = (TextFrameIndex(COMPLETE_STRING) != rInf.GetMeasureLen())
1546 ? rInf.GetMeasureLen()
1547 : nLn;
1549 // If the measure length is different from the length, then we are
1550 // measuring substring width for caret positioning, see SetMeasureLength()
1551 // use in TextCursor::GetCharRect_().
1552 bool bCaret(nMsrLn != nLn);
1554 // be sure to have the correct layout mode at the printer
1555 if ( m_pPrinter )
1557 m_pPrinter->SetLayoutMode( rInf.GetOut().GetLayoutMode() );
1558 m_pPrinter->SetDigitLanguage( rInf.GetOut().GetDigitLanguage() );
1561 if ( rInf.GetFrame() && nLn && rInf.SnapToGrid() && rInf.GetFont() &&
1562 SwFontScript::CJK == rInf.GetFont()->GetActual() )
1564 SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame()));
1565 if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() )
1567 const SwDoc* pDoc = rInf.GetShell()->GetDoc();
1568 const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc);
1570 OutputDevice* pOutDev;
1572 if ( m_pPrinter )
1574 if( !m_pPrtFont->IsSameInstance( m_pPrinter->GetFont() ) )
1575 m_pPrinter->SetFont(*m_pPrtFont);
1576 pOutDev = m_pPrinter;
1578 else
1579 pOutDev = rInf.GetpOut();
1581 aTextSize.setWidth( pOutDev->GetTextWidth(rInf.GetText(),
1582 sal_Int32(rInf.GetIdx()), sal_Int32(nLn)) );
1584 OSL_ENSURE( !rInf.GetShell() ||
1585 ( USHRT_MAX != GetGuessedLeading() && USHRT_MAX != GetExternalLeading() ),
1586 "Leading values should be already calculated" );
1587 aTextSize.setHeight( pOutDev->GetTextHeight() +
1588 GetFontLeading( rInf.GetShell(), rInf.GetOut() ) );
1590 KernArray aKernArray;
1591 GetTextArray(*pOutDev, rInf, aKernArray, sal_Int32(nLn), bCaret);
1592 if (pGrid->IsSnapToChars())
1594 sw::Justify::SnapToGrid(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()),
1595 sal_Int32(rInf.GetLen()), nGridWidth, true);
1597 else
1599 // use 0 to calculate raw width without rInf.GetSpace().
1600 sw::Justify::SnapToGridEdge(aKernArray, sal_Int32(rInf.GetLen()), nGridWidth, 0,
1601 rInf.GetKern());
1604 aTextSize.setWidth(aKernArray[sal_Int32(nMsrLn) - 1]);
1605 rInf.SetKanaDiff( 0 );
1606 return aTextSize;
1610 const bool bCompress = rInf.GetKanaComp() && nLn &&
1611 rInf.GetFont() &&
1612 SwFontScript::CJK == rInf.GetFont()->GetActual() &&
1613 rInf.GetScriptInfo() &&
1614 rInf.GetScriptInfo()->CountCompChg() &&
1615 lcl_IsMonoSpaceFont( rInf.GetOut() );
1617 OSL_ENSURE( !bCompress || ( rInf.GetScriptInfo() && rInf.GetScriptInfo()->
1618 CountCompChg()), "Compression without info" );
1620 // This is the part used e.g., for cursor travelling
1621 // See condition for DrawText or DrawTextArray (bDirectPrint)
1622 KernArray aKernArray;
1623 if ( m_pPrinter && m_pPrinter.get() != rInf.GetpOut() )
1625 if( !m_pPrtFont->IsSameInstance( m_pPrinter->GetFont() ) )
1626 m_pPrinter->SetFont(*m_pPrtFont);
1627 aTextSize.setHeight( m_pPrinter->GetTextHeight() );
1629 CreateScrFont( *rInf.GetShell(), rInf.GetOut() );
1630 if( !GetScrFont()->IsSameInstance( rInf.GetOut().GetFont() ) )
1631 rInf.GetOut().SetFont( *m_pScrFont );
1633 GetTextArray(*m_pPrinter, rInf.GetText(), aKernArray,
1634 sal_Int32(rInf.GetIdx()), sal_Int32(nLn), bCaret);
1636 else
1638 if( !m_pPrtFont->IsSameInstance( rInf.GetOut().GetFont() ) )
1639 rInf.GetOut().SetFont( *m_pPrtFont );
1640 aTextSize.setHeight( rInf.GetOut().GetTextHeight() );
1642 GetTextArray(rInf.GetOut(), rInf, aKernArray, nLn.get(), bCaret);
1645 if (bCompress)
1647 rInf.SetKanaDiff(rInf.GetScriptInfo()->Compress(aKernArray, rInf.GetIdx(), nLn, rInf.GetKanaComp(),
1648 o3tl::narrowing<sal_uInt16>(m_aFont.GetFontSize().Height()), lcl_IsFullstopCentered(rInf.GetOut())));
1650 else
1651 rInf.SetKanaDiff( 0 );
1653 if (nMsrLn)
1655 aTextSize.setWidth(aKernArray[sal_Int32(nMsrLn) - 1]);
1657 // Note that we can't simply use sal_Int(nMsrLn) - 1 as nSpaceCount
1658 // because a glyph may be made up of more than one characters.
1659 sal_Int32 nSpaceCount = 0;
1660 tools::Long nOldValue = aKernArray[0];
1662 for(sal_Int32 i = 1; i < sal_Int32(nMsrLn); ++i)
1664 if (nOldValue != aKernArray[i])
1666 ++nSpaceCount;
1667 nOldValue = aKernArray[i];
1671 if (rInf.GetKern())
1672 aTextSize.AdjustWidth(nSpaceCount * rInf.GetKern());
1675 OSL_ENSURE( !rInf.GetShell() ||
1676 ( USHRT_MAX != GetGuessedLeading() && USHRT_MAX != GetExternalLeading() ),
1677 "Leading values should be already calculated" );
1678 aTextSize.AdjustHeight(GetFontLeading( rInf.GetShell(), rInf.GetOut() ) );
1679 return aTextSize;
1682 TextFrameIndex SwFntObj::GetModelPositionForViewPoint(SwDrawTextInfo &rInf)
1684 tools::Long nSpaceAdd = rInf.GetSpace() / SPACING_PRECISION_FACTOR;
1685 const tools::Long nCharacterSpacing = -rInf.GetCharacterSpacing() / SPACING_PRECISION_FACTOR;
1686 tools::Long nKern = rInf.GetKern();
1688 if( 0 != nCharacterSpacing )
1689 nKern -= nCharacterSpacing;
1691 KernArray aKernArray;
1693 // be sure to have the correct layout mode at the printer
1694 if ( m_pPrinter )
1696 m_pPrinter->SetLayoutMode( rInf.GetOut().GetLayoutMode() );
1697 m_pPrinter->SetDigitLanguage( rInf.GetOut().GetDigitLanguage() );
1698 GetTextArray(*m_pPrinter, rInf, aKernArray);
1700 else
1702 GetTextArray(rInf.GetOut(), rInf, aKernArray);
1705 if ( rInf.GetFrame() && rInf.GetLen() && rInf.SnapToGrid() &&
1706 rInf.GetFont() && SwFontScript::CJK == rInf.GetFont()->GetActual() )
1708 SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame()));
1709 if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() )
1711 const SwDoc* pDoc = rInf.GetShell()->GetDoc();
1712 const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc);
1714 if (pGrid->IsSnapToChars())
1716 sw::Justify::SnapToGrid(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()),
1717 sal_Int32(rInf.GetLen()), nGridWidth, true);
1719 else
1721 sw::Justify::SnapToGridEdge(aKernArray, sal_Int32(rInf.GetLen()), nGridWidth,
1722 nSpaceAdd, rInf.GetKern());
1725 return TextFrameIndex(sw::Justify::GetModelPosition(aKernArray, sal_Int32(rInf.GetLen()),
1726 rInf.GetOffset()));
1730 const SwScriptInfo* pSI = rInf.GetScriptInfo();
1731 if ( rInf.GetFont() && rInf.GetLen() )
1733 const SwFontScript nActual = rInf.GetFont()->GetActual();
1735 // Kana Compression
1736 if ( SwFontScript::CJK == nActual && rInf.GetKanaComp() &&
1737 pSI && pSI->CountCompChg() &&
1738 lcl_IsMonoSpaceFont( rInf.GetOut() ) )
1740 pSI->Compress( aKernArray, rInf.GetIdx(), rInf.GetLen(),
1741 rInf.GetKanaComp(),
1742 o3tl::narrowing<sal_uInt16>(m_aFont.GetFontSize().Height()),
1743 lcl_IsFullstopCentered( rInf.GetOut() ) );
1746 // Asian Justification
1747 if ( SwFontScript::CJK == rInf.GetFont()->GetActual() )
1749 LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CJK );
1751 if (!MsLangId::isKorean(aLang))
1753 SwScriptInfo::CJKJustify( rInf.GetText(), aKernArray,
1754 rInf.GetIdx(), rInf.GetLen(), aLang, nSpaceAdd, rInf.IsSpaceStop() );
1756 nSpaceAdd = 0;
1761 // Kashida Justification
1762 if ( SwFontScript::CTL == nActual && rInf.GetSpace() )
1764 if ( SwScriptInfo::IsArabicText( rInf.GetText(), rInf.GetIdx(), rInf.GetLen() ) )
1766 if ( pSI && pSI->CountKashida() &&
1767 pSI->KashidaJustify( &aKernArray, nullptr, rInf.GetIdx(), rInf.GetLen(),
1768 nSpaceAdd ) != -1 )
1769 nSpaceAdd = 0;
1773 // Thai Justification
1774 if ( SwFontScript::CTL == nActual && nSpaceAdd )
1776 LanguageType aLang = rInf.GetFont()->GetLanguage( SwFontScript::CTL );
1778 if ( LANGUAGE_THAI == aLang )
1780 SwScriptInfo::ThaiJustify( rInf.GetText(), &aKernArray,
1781 rInf.GetIdx(), rInf.GetLen(),
1782 rInf.GetNumberOfBlanks(),
1783 rInf.GetSpace() );
1785 // adding space to blanks is already done
1786 nSpaceAdd = 0;
1791 tools::Long nLeft = 0;
1792 tools::Long nRight = 0;
1793 TextFrameIndex nCnt(0);
1794 tools::Long nSpaceSum = 0;
1795 tools::Long nKernSum = 0;
1797 sal_Int32 nDone = 0;
1798 TextFrameIndex nIdx = rInf.GetIdx();
1799 TextFrameIndex nLastIdx = nIdx;
1800 const TextFrameIndex nEnd = rInf.GetIdx() + rInf.GetLen();
1802 // #i105901#
1803 // skip character cells for all script types
1804 LanguageType aLang = rInf.GetFont()->GetLanguage();
1806 while ( ( nRight < tools::Long( rInf.GetOffset() ) ) && ( nIdx < nEnd ) )
1808 if (nSpaceAdd && CH_BLANK == rInf.GetText()[ sal_Int32(nIdx)])
1809 nSpaceSum += nSpaceAdd;
1811 // go to next character (cell).
1812 nLastIdx = nIdx;
1814 nIdx = TextFrameIndex(g_pBreakIt->GetBreakIter()->nextCharacters(
1815 rInf.GetText(), sal_Int32(nIdx),
1816 g_pBreakIt->GetLocale( aLang ),
1817 i18n::CharacterIteratorMode::SKIPCELL, 1, nDone));
1818 if ( nIdx <= nLastIdx )
1819 break;
1821 // the next character might be outside the layout range (e.g tdf124116-1.odt)
1822 if (nIdx > nEnd)
1823 nIdx = nEnd;
1825 nLeft = nRight;
1826 nRight = aKernArray[sal_Int32(nIdx - rInf.GetIdx()) - 1] + nKernSum + nSpaceSum;
1828 nKernSum += nKern;
1831 // step back if position is before the middle of the character
1832 // or if we do not want to go to the next character
1833 if ( nIdx > rInf.GetIdx() &&
1834 ( rInf.IsPosMatchesBounds() ||
1835 ( ( nRight > tools::Long( rInf.GetOffset() ) ) &&
1836 ( nRight - rInf.GetOffset() > rInf.GetOffset() - nLeft ) ) ) )
1837 nCnt = nLastIdx - rInf.GetIdx(); // first half
1838 else
1839 nCnt = nIdx - rInf.GetIdx(); // second half
1841 if ( pSI )
1842 rInf.SetCursorBidiLevel( pSI->DirType( nLastIdx ) );
1844 return nCnt;
1847 SwFntAccess::SwFntAccess( const void* & rnFontCacheId,
1848 sal_uInt16 &rIndex, const void *pOwn, SwViewShell const *pSh,
1849 bool bCheck ) :
1850 SwCacheAccess( *pFntCache, rnFontCacheId, rIndex ),
1851 m_pShell( pSh )
1853 // the used ctor of SwCacheAccess searches for rnFontCacheId+rIndex in the cache
1854 if ( m_pObj )
1856 // fast case: known Font (rnFontCacheId), no need to check printer and zoom
1857 if ( !bCheck )
1858 return;
1860 // Font is known, but has to be checked
1862 else
1863 { // Font not known, must be searched
1864 bCheck = false;
1868 OutputDevice* pOut = nullptr;
1869 sal_uInt16 nZoom = USHRT_MAX;
1871 // Get the reference device
1872 if ( pSh )
1874 pOut = &pSh->GetRefDev();
1875 nZoom = pSh->GetViewOptions()->GetZoom();
1878 SwFntObj *pFntObj;
1879 if ( bCheck )
1881 pFntObj = Get();
1882 if ( ( pFntObj->GetZoom( ) == nZoom ) &&
1883 ( pFntObj->m_pPrinter == pOut ) &&
1884 pFntObj->GetPropWidth() ==
1885 static_cast<SwSubFont const *>(pOwn)->GetPropWidth() )
1887 return; // result of Check: Drucker+Zoom okay.
1889 pFntObj->Unlock(); // forget this object, printer/zoom differs
1890 m_pObj = nullptr;
1893 // Search by font comparison, quite expensive!
1894 // Look for same font and same printer
1895 pFntObj = pFntCache->First();
1896 while ( pFntObj && !( pFntObj->m_aFont == *static_cast<vcl::Font const *>(pOwn) &&
1897 pFntObj->GetZoom() == nZoom &&
1898 pFntObj->GetPropWidth() ==
1899 static_cast<SwSubFont const *>(pOwn)->GetPropWidth() &&
1900 ( !pFntObj->m_pPrinter || pFntObj->m_pPrinter == pOut ) ) )
1901 pFntObj = SwFntCache::Next( pFntObj );
1903 if( pFntObj && pFntObj->m_pPrinter.get() != pOut )
1905 // found one without printer, let's see if there is one with
1906 // the same printer as well
1907 SwFntObj *pTmpObj = pFntObj;
1908 while( pTmpObj && !( pTmpObj->m_aFont == *static_cast<vcl::Font const *>(pOwn) &&
1909 pTmpObj->GetZoom()==nZoom && pTmpObj->m_pPrinter==pOut &&
1910 pTmpObj->GetPropWidth() ==
1911 static_cast<SwSubFont const *>(pOwn)->GetPropWidth() ) )
1912 pTmpObj = SwFntCache::Next( pTmpObj );
1913 if( pTmpObj )
1914 pFntObj = pTmpObj;
1917 if ( !pFntObj ) // Font has not been found, create one
1919 // Have to create new Object, hence Owner must be a SwFont, later
1920 // the Owner will be the "MagicNumber"
1921 SwCacheAccess::m_pOwner = pOwn;
1922 pFntObj = Get(); // will create via NewObj() and lock
1923 OSL_ENSURE(pFntObj, "No Font, no Fun.");
1925 else // Font has been found, so we lock it.
1927 pFntObj->Lock();
1928 if (pFntObj->m_pPrinter.get() != pOut) // if no printer is known by now
1930 OSL_ENSURE( !pFntObj->m_pPrinter, "SwFntAccess: Printer Changed" );
1931 pFntObj->CreatePrtFont( *pOut );
1932 pFntObj->m_pPrinter = pOut;
1933 pFntObj->m_pScrFont = nullptr;
1934 pFntObj->m_nGuessedLeading = USHRT_MAX;
1935 pFntObj->m_nExtLeading = USHRT_MAX;
1936 pFntObj->m_nPrtAscent = USHRT_MAX;
1937 pFntObj->m_nPrtHeight = USHRT_MAX;
1939 m_pObj = pFntObj;
1942 // no matter if new or found, now the Owner of the Object is a
1943 // MagicNumber, and will be given to the SwFont, as well as the Index
1944 // for later direct access
1945 rnFontCacheId = reinterpret_cast<void*>(reinterpret_cast<sal_IntPtr>(pFntObj->GetOwner()));
1946 SwCacheAccess::m_pOwner = pFntObj->GetOwner();
1947 rIndex = pFntObj->GetCachePos();
1951 SwCacheObj *SwFntAccess::NewObj( )
1953 // "MagicNumber" used to identify Fonts
1954 static std::uintptr_t fontCacheIdCounter = 0;
1955 // a new Font, a new "MagicNumber".
1956 return new SwFntObj( *static_cast<SwSubFont const *>(m_pOwner), ++fontCacheIdCounter, m_pShell );
1959 TextFrameIndex SwFont::GetTextBreak(SwDrawTextInfo const & rInf, tools::Long nTextWidth)
1961 ChgFnt( rInf.GetShell(), rInf.GetOut() );
1963 const bool bCompress = rInf.GetKanaComp() && rInf.GetLen() &&
1964 SwFontScript::CJK == GetActual() &&
1965 rInf.GetScriptInfo() &&
1966 rInf.GetScriptInfo()->CountCompChg() &&
1967 lcl_IsMonoSpaceFont( rInf.GetOut() );
1969 OSL_ENSURE( !bCompress || ( rInf.GetScriptInfo() && rInf.GetScriptInfo()->
1970 CountCompChg()), "Compression without info" );
1972 TextFrameIndex nTextBreak(0);
1973 tools::Long nKern = 0;
1975 TextFrameIndex nLn = rInf.GetLen() == TextFrameIndex(COMPLETE_STRING)
1976 ? TextFrameIndex(rInf.GetText().getLength()) : rInf.GetLen();
1978 if ( rInf.GetFrame() && nLn && rInf.SnapToGrid() &&
1979 rInf.GetFont() && SwFontScript::CJK == rInf.GetFont()->GetActual() )
1981 SwTextGridItem const*const pGrid(GetGridItem(rInf.GetFrame()->FindPageFrame()));
1982 if ( pGrid && GRID_LINES_CHARS == pGrid->GetGridType() )
1984 const SwDoc* pDoc = rInf.GetShell()->GetDoc();
1985 const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc);
1987 KernArray aKernArray;
1988 GetTextArray( rInf.GetOut(), rInf.GetText(), aKernArray,
1989 sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
1991 if (pGrid->IsSnapToChars())
1993 sw::Justify::SnapToGrid(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()),
1994 sal_Int32(rInf.GetLen()), nGridWidth, true);
1996 else
1998 // use 0 to calculate raw width without rInf.GetSpace().
1999 sw::Justify::SnapToGridEdge(aKernArray, sal_Int32(rInf.GetLen()), nGridWidth,
2000 0, rInf.GetKern());
2003 while(nTextBreak < rInf.GetLen() && aKernArray[sal_Int32(nTextBreak)] <= nTextWidth)
2004 ++nTextBreak;
2006 return nTextBreak + rInf.GetIdx();
2010 if( m_aSub[m_nActual].IsCapital() && nLn )
2012 nTextBreak = GetCapitalBreak( rInf.GetShell(), rInf.GetpOut(),
2013 rInf.GetScriptInfo(), rInf.GetText(), nTextWidth, rInf.GetIdx(),
2014 nLn );
2016 else
2018 nKern = CheckKerning();
2020 const OUString* pTmpText;
2021 OUString aTmpText;
2022 TextFrameIndex nTmpIdx;
2023 TextFrameIndex nTmpLen;
2024 bool bTextReplaced = false;
2026 if ( !m_aSub[m_nActual].IsCaseMap() )
2028 pTmpText = &rInf.GetText();
2029 nTmpIdx = rInf.GetIdx();
2030 nTmpLen = nLn;
2032 else
2034 const OUString aSnippet(rInf.GetText().copy(sal_Int32(rInf.GetIdx()), sal_Int32(nLn)));
2035 aTmpText = m_aSub[m_nActual].CalcCaseMap( aSnippet );
2036 const bool bTitle = SvxCaseMap::Capitalize == m_aSub[m_nActual].GetCaseMap();
2038 // Uaaaaahhhh!!! In title case mode, we would get wrong results
2039 if ( bTitle && nLn )
2041 // check if rInf.GetIdx() is begin of word
2042 if ( !g_pBreakIt->GetBreakIter()->isBeginWord(
2043 rInf.GetText(), sal_Int32(rInf.GetIdx()),
2044 g_pBreakIt->GetLocale( m_aSub[m_nActual].GetLanguage() ),
2045 i18n::WordType::ANYWORD_IGNOREWHITESPACES ) )
2047 // In this case, the beginning of aTmpText is wrong.
2048 OUString aSnippetTmp(aSnippet.copy(0, 1));
2049 aSnippetTmp = m_aSub[m_nActual].CalcCaseMap( aSnippetTmp );
2050 aTmpText = aTmpText.replaceAt( 0, aSnippetTmp.getLength(), rtl::OUStringChar(aSnippet[0]) );
2054 pTmpText = &aTmpText;
2055 nTmpIdx = TextFrameIndex(0);
2056 nTmpLen = TextFrameIndex(aTmpText.getLength());
2057 bTextReplaced = true;
2060 if( rInf.GetHyphPos() ) {
2061 sal_Int32 nHyphPos = sal_Int32(*rInf.GetHyphPos());
2062 const SalLayoutGlyphs* pGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
2063 &rInf.GetOut(), *pTmpText, nTmpIdx.get(), nTmpLen.get(), 0, rInf.GetVclCache());
2064 nTextBreak = TextFrameIndex(rInf.GetOut().GetTextBreak(
2065 *pTmpText, nTextWidth,
2066 u'-', nHyphPos,
2067 sal_Int32(nTmpIdx), sal_Int32(nTmpLen),
2068 nKern, rInf.GetVclCache(), pGlyphs));
2069 *rInf.GetHyphPos() = TextFrameIndex((nHyphPos == -1) ? COMPLETE_STRING : nHyphPos);
2071 else
2073 SwFntAccess aFntAccess(m_aSub[m_nActual].m_nFontCacheId, m_aSub[m_nActual].m_nFontIndex,
2074 &m_aSub[m_nActual], rInf.GetShell());
2075 const SalLayoutGlyphs* pGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(&rInf.GetOut(),
2076 *pTmpText, nTmpIdx.get(), nTmpLen.get(), 0, rInf.GetVclCache());
2077 nTextBreak = TextFrameIndex(rInf.GetOut().GetTextBreak(
2078 *pTmpText, nTextWidth,
2079 sal_Int32(nTmpIdx), sal_Int32(nTmpLen),
2080 nKern, rInf.GetVclCache(), pGlyphs));
2083 if (bTextReplaced && sal_Int32(nTextBreak) != -1)
2085 if ( nTmpLen != nLn )
2086 nTextBreak = sw_CalcCaseMap( *this, rInf.GetText(),
2087 rInf.GetIdx(), nLn, nTextBreak );
2088 else
2089 nTextBreak = nTextBreak + rInf.GetIdx();
2093 TextFrameIndex nTextBreak2 = sal_Int32(nTextBreak) == -1
2094 ? TextFrameIndex(COMPLETE_STRING)
2095 : nTextBreak;
2097 // tdf112290 tdf136588 Break the line correctly only if there is an image inline,
2098 // and the image wider than the line...
2099 if (GetCaseMap() == SvxCaseMap::SmallCaps && TextFrameIndex(COMPLETE_STRING) == nTextBreak2 &&
2100 ! bCompress && nTextWidth == 0)
2101 // If nTextWidth == 0 means the line is full, we have to break it
2102 nTextBreak2 = TextFrameIndex(1);
2104 if ( ! bCompress )
2105 return nTextBreak2;
2107 nTextBreak2 = nTextBreak2 - rInf.GetIdx();
2109 if( nTextBreak2 < nLn )
2111 if( !nTextBreak2 && nLn )
2112 nLn = TextFrameIndex(1);
2113 else if (nLn > nTextBreak2 + nTextBreak2)
2114 nLn = nTextBreak2 + nTextBreak2;
2115 KernArray aKernArray;
2116 GetTextArray( rInf.GetOut(), rInf.GetText(), aKernArray,
2117 sal_Int32(rInf.GetIdx()), sal_Int32(nLn));
2118 if( rInf.GetScriptInfo()->Compress( aKernArray, rInf.GetIdx(), nLn,
2119 rInf.GetKanaComp(), o3tl::narrowing<sal_uInt16>(GetHeight( m_nActual )),
2120 lcl_IsFullstopCentered( rInf.GetOut() ) ) )
2122 tools::Long nKernAdd = nKern;
2123 TextFrameIndex const nTmpBreak = nTextBreak2;
2124 if( nKern && nTextBreak2 )
2125 nKern *= sal_Int32(nTextBreak2) - 1;
2126 while (nTextBreak2 < nLn && nTextWidth >= aKernArray[sal_Int32(nTextBreak2)] + nKern)
2128 nKern += nKernAdd;
2129 ++nTextBreak2;
2131 if( rInf.GetHyphPos() )
2132 *rInf.GetHyphPos() += nTextBreak2 - nTmpBreak; // It's not perfect
2135 nTextBreak2 = nTextBreak2 + rInf.GetIdx();
2137 return nTextBreak2;
2140 bool SwDrawTextInfo::ApplyAutoColor( vcl::Font* pFont )
2142 const vcl::Font& rFnt = pFont ? *pFont : GetOut().GetFont();
2143 Color nNewColor = COL_BLACK;
2144 bool bChgFntColor = false;
2145 bool bChgLineColor = false;
2147 const SwViewShell *pVSh = GetShell();
2148 const bool bOutputToWindow(pVSh && (pVSh->GetWin() || pVSh->isOutputToWindow()));
2150 if (pVSh && !bOutputToWindow && pVSh->GetViewOptions()->IsBlackFont())
2152 if ( COL_BLACK != rFnt.GetColor() )
2153 bChgFntColor = true;
2155 if ( (COL_BLACK != GetOut().GetLineColor()) ||
2156 (COL_BLACK != GetOut().GetOverlineColor()) )
2157 bChgLineColor = true;
2159 else
2161 // FontColor has to be changed if:
2162 // 1. FontColor = AUTO or 2. IsAlwaysAutoColor is set
2163 // LineColor has to be changed if:
2164 // 1. IsAlwaysAutoColor is set
2166 bChgLineColor = pVSh && bOutputToWindow &&
2167 pVSh->GetAccessibilityOptions()->IsAlwaysAutoColor();
2169 bChgFntColor = COL_AUTO == rFnt.GetColor() || bChgLineColor;
2171 if ( bChgFntColor )
2173 // check if current background has a user defined setting
2174 std::optional<Color> pCol;
2175 if (GetFont())
2176 pCol = GetFont()->GetBackColor();
2177 if( ! pCol || COL_TRANSPARENT == *pCol )
2179 const SvxBrushItem* pItem;
2180 SwRect aOrigBackRect;
2181 drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes;
2183 /// OD 21.08.2002
2184 /// consider, that [GetBackgroundBrush(...)] can set <pCol>
2185 /// - see implementation in /core/layout/paintfrm.cxx
2186 /// OD 21.08.2002 #99657#
2187 /// There is a user defined setting for the background, if there
2188 /// is a background brush and its color is *not* "no fill"/"auto fill".
2189 if( GetFrame()->GetBackgroundBrush( aFillAttributes, pItem, pCol, aOrigBackRect, false, /*bConsiderTextBox=*/true ) )
2191 if (aFillAttributes && aFillAttributes->isUsed())
2193 // First see if fill attributes provide a color.
2194 pCol = Color(aFillAttributes->getAverageColor(aGlobalRetoucheColor.getBColor()));
2197 // If not, then fall back to the old brush item.
2198 if ( !pCol )
2200 pCol = pItem->GetColor();
2203 /// OD 30.08.2002 #99657#
2204 /// determined color <pCol> can be <COL_TRANSPARENT>. Thus, check it.
2205 if ( *pCol == COL_TRANSPARENT)
2206 pCol.reset();
2208 else
2209 pCol.reset();
2212 // no user defined color at paragraph or font background
2213 if ( ! pCol )
2214 pCol = aGlobalRetoucheColor;
2216 if (pVSh && bOutputToWindow)
2218 // here we determine the preferred window text color for painting
2219 const SwViewOption* pViewOption = pVSh->GetViewOptions();
2220 if(pViewOption->IsPagePreview() &&
2221 !officecfg::Office::Common::Accessibility::IsForPagePreviews::get())
2222 nNewColor = COL_BLACK;
2223 else
2224 // we take the font color from the appearance page
2225 nNewColor = pViewOption->GetFontColor();
2228 // change painting color depending of dark/bright background
2229 Color aTmpColor( nNewColor );
2230 if ( pCol->IsDark() && aTmpColor.IsDark() )
2231 nNewColor = COL_WHITE;
2232 else if ( pCol->IsBright() && aTmpColor.IsBright() )
2233 nNewColor = COL_BLACK;
2237 if ( bChgFntColor || bChgLineColor )
2239 Color aNewColor( nNewColor );
2241 if ( bChgFntColor )
2243 if ( pFont && aNewColor != pFont->GetColor() )
2245 // only set the new color at the font passed as argument
2246 pFont->SetColor( aNewColor );
2248 else if ( aNewColor != GetOut().GetFont().GetColor() )
2250 // set new font with new color at output device
2251 vcl::Font aFont( rFnt );
2252 aFont.SetColor( aNewColor );
2253 GetOut().SetFont( aFont );
2257 // the underline and overline colors have to be set separately
2258 if ( bChgLineColor )
2260 // get current font color or color set at output device
2261 aNewColor = pFont ? pFont->GetColor() : GetOut().GetFont().GetColor();
2262 if ( aNewColor != GetOut().GetLineColor() )
2263 GetOut().SetLineColor( aNewColor );
2264 if ( aNewColor != GetOut().GetOverlineColor() )
2265 GetOut().SetOverlineColor( aNewColor );
2268 return true;
2271 return false;
2274 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */