1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <sal/log.hxx>
24 #include <font/PhysicalFontCollection.hxx>
25 #include <font/PhysicalFontFace.hxx>
26 #include <font/PhysicalFontFamily.hxx>
27 #include <font/LogicalFontInstance.hxx>
28 #include <o3tl/test_info.hxx>
29 #include <tools/debug.hxx>
30 #include <impfontcache.hxx>
32 using namespace vcl::font
;
34 size_t ImplFontCache::IFSD_Hash::operator()( const vcl::font::FontSelectPattern
& rFSD
) const
36 return rFSD
.hashCode();
39 bool ImplFontCache::IFSD_Equal::operator()(const vcl::font::FontSelectPattern
& rA
, const vcl::font::FontSelectPattern
& rB
) const
41 // check normalized font family name
42 if( rA
.maSearchName
!= rB
.maSearchName
)
45 // check font transformation
46 if( (rA
.mnHeight
!= rB
.mnHeight
)
47 || (rA
.mnWidth
!= rB
.mnWidth
)
48 || (rA
.mnOrientation
!= rB
.mnOrientation
) )
51 // check mapping relevant attributes
52 if( (rA
.mbVertical
!= rB
.mbVertical
)
53 || (rA
.meLanguage
!= rB
.meLanguage
) )
56 // check font face attributes
57 if( (rA
.GetWeight() != rB
.GetWeight())
58 || (rA
.GetItalic() != rB
.GetItalic())
59 // || (rA.meFamily != rB.meFamily) // TODO: remove this mostly obsolete member
60 || (rA
.GetPitch() != rB
.GetPitch()) )
64 if( rA
.GetStyleName() != rB
.GetStyleName() )
67 // Symbol fonts may recode from one type to another So they are only
68 // safely equivalent for equal targets
69 if (rA
.IsMicrosoftSymbolEncoded() || rB
.IsMicrosoftSymbolEncoded())
71 if (rA
.maTargetName
!= rB
.maTargetName
)
76 if ((rA
.maTargetName
.indexOf(vcl::font::FontSelectPattern::FEAT_PREFIX
)
78 rB
.maTargetName
.indexOf(vcl::font::FontSelectPattern::FEAT_PREFIX
)
79 != -1) && rA
.maTargetName
!= rB
.maTargetName
)
82 if (rA
.mbEmbolden
!= rB
.mbEmbolden
)
85 if (rA
.maItalicMatrix
!= rB
.maItalicMatrix
)
91 ImplFontCache::ImplFontCache()
92 : mpLastHitCacheEntry( nullptr )
93 , maFontInstanceList(std::numeric_limits
<size_t>::max()) // "unlimited", i.e. no cleanup
94 // The cache limit is set by the rough number of characters needed to read your average Asian newspaper.
95 , m_aBoundRectCache(3000)
98 ImplFontCache::~ImplFontCache()
100 DBG_TESTSOLARMUTEX();
101 for (const auto & rLFI
: maFontInstanceList
)
103 rLFI
.second
->mpFontCache
= nullptr;
107 rtl::Reference
<LogicalFontInstance
> ImplFontCache::GetFontInstance( PhysicalFontCollection
const * pFontList
,
108 const vcl::Font
& rFont
, const Size
& rSize
, float fExactHeight
, bool bNonAntialias
)
110 // initialize internal font request object
111 vcl::font::FontSelectPattern
aFontSelData(rFont
, rFont
.GetFamilyName(), rSize
, fExactHeight
, bNonAntialias
);
112 return GetFontInstance( pFontList
, aFontSelData
);
115 rtl::Reference
<LogicalFontInstance
> ImplFontCache::GetFontInstance( PhysicalFontCollection
const * pFontList
,
116 vcl::font::FontSelectPattern
& aFontSelData
)
118 DBG_TESTSOLARMUTEX();
119 rtl::Reference
<LogicalFontInstance
> pFontInstance
;
120 PhysicalFontFamily
* pFontFamily
= nullptr;
122 // check if a directly matching logical font instance is already cached,
123 // the most recently used font usually has a hit rate of >50%
124 if (mpLastHitCacheEntry
&& IFSD_Equal()(aFontSelData
, mpLastHitCacheEntry
->GetFontSelectPattern()))
125 pFontInstance
= mpLastHitCacheEntry
;
128 FontInstanceList::const_iterator it
= maFontInstanceList
.find( aFontSelData
);
129 if( it
!= maFontInstanceList
.end() )
130 pFontInstance
= (*it
).second
;
133 if( !pFontInstance
) // no direct cache hit
135 // find the best matching logical font family and update font selector accordingly
136 pFontFamily
= pFontList
->FindFontFamily( aFontSelData
);
137 SAL_WARN_IF( (pFontFamily
== nullptr), "vcl", "ImplFontCache::Get() No logical font found!" );
140 aFontSelData
.maSearchName
= pFontFamily
->GetSearchName();
142 // check if an indirectly matching logical font instance is already cached
143 FontInstanceList::const_iterator it
= maFontInstanceList
.find( aFontSelData
);
144 if( it
!= maFontInstanceList
.end() )
145 pFontInstance
= (*it
).second
;
149 if( !pFontInstance
&& pFontFamily
) // still no cache hit => create a new font instance
151 vcl::font::PhysicalFontFace
* pFontData
= pFontFamily
->FindBestFontFace(aFontSelData
);
153 // create a new logical font instance from this physical font face
154 pFontInstance
= pFontData
->CreateFontInstance( aFontSelData
);
155 pFontInstance
->mpFontCache
= this;
157 // if we're substituting from or to a symbol font we may need a symbol
159 if( pFontData
->IsMicrosoftSymbolEncoded() || aFontSelData
.IsMicrosoftSymbolEncoded() || IsOpenSymbol(aFontSelData
.maSearchName
) )
161 if( aFontSelData
.maTargetName
!= aFontSelData
.maSearchName
)
162 pFontInstance
->mpConversion
= ConvertChar::GetRecodeData( aFontSelData
.maTargetName
, aFontSelData
.maSearchName
);
166 //It might be better to dig out the font version of the target font
167 //to see if it's a modern re-coded apple symbol font in case that
168 //font shows up on a different platform
169 if (!pFontInstance
->mpConversion
&&
170 aFontSelData
.maTargetName
.equalsIgnoreAsciiCase("symbol") &&
171 aFontSelData
.maSearchName
.equalsIgnoreAsciiCase("symbol"))
173 pFontInstance
->mpConversion
= ConvertChar::GetRecodeData( u
"Symbol", u
"AppleSymbol" );
177 static const size_t FONTCACHE_MAX
= o3tl::IsRunningUnitTest() ? 1 : 50;
179 if (maFontInstanceList
.size() >= FONTCACHE_MAX
)
181 struct limit_exception
: public std::exception
{};
184 maFontInstanceList
.remove_if([this] (FontInstanceList::key_value_pair_t
const& rFontPair
)
186 if (maFontInstanceList
.size() < FONTCACHE_MAX
)
187 throw limit_exception();
188 LogicalFontInstance
* pFontEntry
= rFontPair
.second
.get();
189 if (pFontEntry
->m_nCount
> 1)
191 m_aBoundRectCache
.remove_if([&pFontEntry
] (GlyphBoundRectCache::key_value_pair_t
const& rGlyphPair
)
192 { return rGlyphPair
.first
.m_pFont
== pFontEntry
; });
193 if (mpLastHitCacheEntry
== pFontEntry
)
194 mpLastHitCacheEntry
= nullptr;
198 catch (limit_exception
&) {}
201 assert(pFontInstance
);
202 // add the new entry to the cache
203 maFontInstanceList
.insert({aFontSelData
, pFontInstance
.get()});
206 mpLastHitCacheEntry
= pFontInstance
.get();
207 return pFontInstance
;
210 rtl::Reference
<LogicalFontInstance
> ImplFontCache::GetGlyphFallbackFont( PhysicalFontCollection
const * pFontCollection
,
211 vcl::font::FontSelectPattern
& rFontSelData
, LogicalFontInstance
* pFontInstance
, int nFallbackLevel
, OUString
& rMissingCodes
)
213 // get a candidate font for glyph fallback
214 // unless the previously selected font got a device specific substitution
215 // e.g. PsPrint Arial->Helvetica for udiaeresis when Helvetica doesn't support it
216 if( nFallbackLevel
>= 1)
218 PhysicalFontFamily
* pFallbackData
= nullptr;
220 //fdo#33898 If someone has EUDC installed then they really want that to
221 //be used as the first-choice glyph fallback seeing as it's filled with
222 //private area codes with don't make any sense in any other font so
223 //prioritize it here if it's available. Ideally we would remove from
224 //rMissingCodes all the glyphs which it is able to resolve as an
225 //optimization, but that's tricky to achieve cross-platform without
226 //sufficient heavy-weight code that's likely to undo the value of the
228 if (nFallbackLevel
== 1)
229 pFallbackData
= pFontCollection
->FindFontFamily(u
"EUDC");
231 pFallbackData
= pFontCollection
->GetGlyphFallbackFont(rFontSelData
, pFontInstance
, rMissingCodes
, nFallbackLevel
-1);
232 // escape when there are no font candidates
235 // override the font name
236 rFontSelData
.SetFamilyName( pFallbackData
->GetFamilyName() );
237 // clear the cached normalized name
238 rFontSelData
.maSearchName
.clear();
241 rtl::Reference
<LogicalFontInstance
> pFallbackFont
= GetFontInstance( pFontCollection
, rFontSelData
);
242 return pFallbackFont
;
245 void ImplFontCache::Invalidate()
247 DBG_TESTSOLARMUTEX();
248 // #112304# make sure the font cache is really clean
249 mpLastHitCacheEntry
= nullptr;
250 for (auto const & pair
: maFontInstanceList
)
251 pair
.second
->mpFontCache
= nullptr;
252 maFontInstanceList
.clear();
253 m_aBoundRectCache
.clear();
256 bool ImplFontCache::GetCachedGlyphBoundRect(const LogicalFontInstance
*pFont
, sal_GlyphId nID
, basegfx::B2DRectangle
&rRect
)
258 if (!pFont
->GetFontCache())
260 assert(pFont
->GetFontCache() == this);
261 if (pFont
->GetFontCache() != this)
264 auto it
= m_aBoundRectCache
.find({pFont
, nID
});
265 if (it
!= m_aBoundRectCache
.end())
273 void ImplFontCache::CacheGlyphBoundRect(const LogicalFontInstance
*pFont
, sal_GlyphId nID
, basegfx::B2DRectangle
&rRect
)
275 if (!pFont
->GetFontCache())
277 assert(pFont
->GetFontCache() == this);
278 if (pFont
->GetFontCache() != this)
281 m_aBoundRectCache
.insert({{pFont
, nID
}, rRect
});
284 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */