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 <fontinstance.hxx>
21 #include <impfontcache.hxx>
22 #include <PhysicalFontCollection.hxx>
23 #include <PhysicalFontFace.hxx>
24 #include <PhysicalFontFamily.hxx>
25 #include <sal/log.hxx>
27 #if !(defined(_WIN32) || defined(MACOSX) || defined(IOS))
28 #include <unx/glyphcache.hxx>
31 size_t ImplFontCache::IFSD_Hash::operator()( const FontSelectPattern
& rFSD
) const
33 return rFSD
.hashCode();
36 bool ImplFontCache::IFSD_Equal::operator()(const FontSelectPattern
& rA
, const FontSelectPattern
& rB
) const
38 // check normalized font family name
39 if( rA
.maSearchName
!= rB
.maSearchName
)
42 // check font transformation
43 if( (rA
.mnHeight
!= rB
.mnHeight
)
44 || (rA
.mnWidth
!= rB
.mnWidth
)
45 || (rA
.mnOrientation
!= rB
.mnOrientation
) )
48 // check mapping relevant attributes
49 if( (rA
.mbVertical
!= rB
.mbVertical
)
50 || (rA
.meLanguage
!= rB
.meLanguage
) )
53 // check font face attributes
54 if( (rA
.GetWeight() != rB
.GetWeight())
55 || (rA
.GetItalic() != rB
.GetItalic())
56 // || (rA.meFamily != rB.meFamily) // TODO: remove this mostly obsolete member
57 || (rA
.GetPitch() != rB
.GetPitch()) )
61 if( rA
.GetStyleName() != rB
.GetStyleName() )
64 // Symbol fonts may recode from one type to another So they are only
65 // safely equivalent for equal targets
66 if (rA
.IsSymbolFont() || rB
.IsSymbolFont())
68 if (rA
.maTargetName
!= rB
.maTargetName
)
73 if ((rA
.maTargetName
.indexOf(FontSelectPattern::FEAT_PREFIX
)
75 rB
.maTargetName
.indexOf(FontSelectPattern::FEAT_PREFIX
)
76 != -1) && rA
.maTargetName
!= rB
.maTargetName
)
79 if (rA
.mbEmbolden
!= rB
.mbEmbolden
)
82 if (rA
.maItalicMatrix
!= rB
.maItalicMatrix
)
88 ImplFontCache::ImplFontCache()
89 : mpLastHitCacheEntry( nullptr )
90 , maFontInstanceList(0)
91 // The cache limit is set by the rough number of characters needed to read your average Asian newspaper.
92 , m_aBoundRectCache(3000)
95 ImplFontCache::~ImplFontCache()
97 for (const auto & rLFI
: maFontInstanceList
)
99 #if !(defined(_WIN32) || defined(MACOSX) || defined(IOS))
100 GlyphCache::GetInstance().TryGarbageCollectFont(rLFI
.second
.get());
102 rLFI
.second
->mpFontCache
= nullptr;
106 rtl::Reference
<LogicalFontInstance
> ImplFontCache::GetFontInstance( PhysicalFontCollection
const * pFontList
,
107 const vcl::Font
& rFont
, const Size
& rSize
, float fExactHeight
, bool bNonAntialias
)
109 // initialize internal font request object
110 FontSelectPattern
aFontSelData(rFont
, rFont
.GetFamilyName(), rSize
, fExactHeight
, bNonAntialias
);
111 return GetFontInstance( pFontList
, aFontSelData
);
114 rtl::Reference
<LogicalFontInstance
> ImplFontCache::GetFontInstance( PhysicalFontCollection
const * pFontList
,
115 FontSelectPattern
& aFontSelData
)
117 rtl::Reference
<LogicalFontInstance
> pFontInstance
;
118 PhysicalFontFamily
* pFontFamily
= nullptr;
120 // check if a directly matching logical font instance is already cached,
121 // the most recently used font usually has a hit rate of >50%
122 if (mpLastHitCacheEntry
&& IFSD_Equal()(aFontSelData
, mpLastHitCacheEntry
->GetFontSelectPattern()))
123 pFontInstance
= mpLastHitCacheEntry
;
126 FontInstanceList::const_iterator it
= maFontInstanceList
.find( aFontSelData
);
127 if( it
!= maFontInstanceList
.end() )
128 pFontInstance
= (*it
).second
;
131 if( !pFontInstance
) // no direct cache hit
133 // find the best matching logical font family and update font selector accordingly
134 pFontFamily
= pFontList
->FindFontFamily( aFontSelData
);
135 SAL_WARN_IF( (pFontFamily
== nullptr), "vcl", "ImplFontCache::Get() No logical font found!" );
138 aFontSelData
.maSearchName
= pFontFamily
->GetSearchName();
140 // check if an indirectly matching logical font instance is already cached
141 FontInstanceList::const_iterator it
= maFontInstanceList
.find( aFontSelData
);
142 if( it
!= maFontInstanceList
.end() )
143 pFontInstance
= (*it
).second
;
147 if( !pFontInstance
&& pFontFamily
) // still no cache hit => create a new font instance
149 PhysicalFontFace
* pFontData
= pFontFamily
->FindBestFontFace(aFontSelData
);
151 // create a new logical font instance from this physical font face
152 pFontInstance
= pFontData
->CreateFontInstance( aFontSelData
);
153 pFontInstance
->mpFontCache
= this;
155 // if we're substituting from or to a symbol font we may need a symbol
157 if( pFontData
->IsSymbolFont() || aFontSelData
.IsSymbolFont() )
159 if( aFontSelData
.maTargetName
!= aFontSelData
.maSearchName
)
160 pFontInstance
->mpConversion
= ConvertChar::GetRecodeData( aFontSelData
.maTargetName
, aFontSelData
.maSearchName
);
164 //It might be better to dig out the font version of the target font
165 //to see if it's a modern re-coded apple symbol font in case that
166 //font shows up on a different platform
167 if (!pFontInstance
->mpConversion
&&
168 aFontSelData
.maTargetName
.equalsIgnoreAsciiCase("symbol") &&
169 aFontSelData
.maSearchName
.equalsIgnoreAsciiCase("symbol"))
171 pFontInstance
->mpConversion
= ConvertChar::GetRecodeData( "Symbol", "AppleSymbol" );
175 static const size_t FONTCACHE_MAX
= getenv("LO_TESTNAME") ? 1 : 50;
177 if (maFontInstanceList
.size() >= FONTCACHE_MAX
)
179 struct limit_exception
: public std::exception
{};
182 maFontInstanceList
.remove_if([this] (FontInstanceListPair
const& rFontPair
)
184 if (maFontInstanceList
.size() < FONTCACHE_MAX
)
185 throw limit_exception();
186 LogicalFontInstance
* pFontEntry
= rFontPair
.second
.get();
187 if (pFontEntry
->m_nCount
> 1)
189 m_aBoundRectCache
.remove_if([&pFontEntry
] (GlpyhBoundRectCachePair
const& rGlyphPair
)
190 { return rGlyphPair
.first
.m_pFont
== pFontEntry
; });
191 if (mpLastHitCacheEntry
== pFontEntry
)
192 mpLastHitCacheEntry
= nullptr;
196 catch (limit_exception
&) {}
199 assert(pFontInstance
);
200 // add the new entry to the cache
201 maFontInstanceList
.insert({aFontSelData
, pFontInstance
.get()});
204 mpLastHitCacheEntry
= pFontInstance
.get();
205 return pFontInstance
;
208 rtl::Reference
<LogicalFontInstance
> ImplFontCache::GetGlyphFallbackFont( PhysicalFontCollection
const * pFontCollection
,
209 FontSelectPattern
& rFontSelData
, LogicalFontInstance
* pFontInstance
, int nFallbackLevel
, OUString
& rMissingCodes
)
211 // get a candidate font for glyph fallback
212 // unless the previously selected font got a device specific substitution
213 // e.g. PsPrint Arial->Helvetica for udiaeresis when Helvetica doesn't support it
214 if( nFallbackLevel
>= 1)
216 PhysicalFontFamily
* pFallbackData
= nullptr;
218 //fdo#33898 If someone has EUDC installed then they really want that to
219 //be used as the first-choice glyph fallback seeing as it's filled with
220 //private area codes with don't make any sense in any other font so
221 //prioritize it here if it's available. Ideally we would remove from
222 //rMissingCodes all the glyphs which it is able to resolve as an
223 //optimization, but that's tricky to achieve cross-platform without
224 //sufficient heavy-weight code that's likely to undo the value of the
226 if (nFallbackLevel
== 1)
227 pFallbackData
= pFontCollection
->FindFontFamily("EUDC");
229 pFallbackData
= pFontCollection
->GetGlyphFallbackFont(rFontSelData
, pFontInstance
, rMissingCodes
, nFallbackLevel
-1);
230 // escape when there are no font candidates
233 // override the font name
234 rFontSelData
.SetFamilyName( pFallbackData
->GetFamilyName() );
235 // clear the cached normalized name
236 rFontSelData
.maSearchName
.clear();
239 rtl::Reference
<LogicalFontInstance
> pFallbackFont
= GetFontInstance( pFontCollection
, rFontSelData
);
240 return pFallbackFont
;
243 void ImplFontCache::Invalidate()
245 // #112304# make sure the font cache is really clean
246 mpLastHitCacheEntry
= nullptr;
247 for (auto const & pair
: maFontInstanceList
)
248 pair
.second
->mpFontCache
= nullptr;
249 maFontInstanceList
.clear();
250 m_aBoundRectCache
.clear();
253 bool ImplFontCache::GetCachedGlyphBoundRect(const LogicalFontInstance
*pFont
, sal_GlyphId nID
, tools::Rectangle
&rRect
)
255 if (!pFont
->GetFontCache())
257 assert(pFont
->GetFontCache() == this);
258 if (pFont
->GetFontCache() != this)
261 auto it
= m_aBoundRectCache
.find({pFont
, nID
});
262 if (it
!= m_aBoundRectCache
.end())
270 void ImplFontCache::CacheGlyphBoundRect(const LogicalFontInstance
*pFont
, sal_GlyphId nID
, tools::Rectangle
&rRect
)
272 if (!pFont
->GetFontCache())
274 assert(pFont
->GetFontCache() == this);
275 if (pFont
->GetFontCache() != this)
278 m_aBoundRectCache
.insert({{pFont
, nID
}, rRect
});
281 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */