Coverage Report

Created: 2026-03-31 11:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/font/fontcache.cxx
Line
Count
Source
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 <sal/config.h>
21
22
#include <sal/log.hxx>
23
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 <unotools/fontdefs.hxx>
31
#include <impfontcache.hxx>
32
33
using namespace vcl::font;
34
35
size_t ImplFontCache::IFSD_Hash::operator()( const vcl::font::FontSelectPattern& rFSD ) const
36
29.6M
{
37
29.6M
    return rFSD.hashCode();
38
29.6M
}
39
40
bool ImplFontCache::IFSD_Equal::operator()(const vcl::font::FontSelectPattern& rA, const vcl::font::FontSelectPattern& rB) const
41
30.7M
{
42
    // check normalized font family name
43
30.7M
    if( rA.maSearchName != rB.maSearchName )
44
14.7M
        return false;
45
46
    // check font transformation
47
16.0M
    if( (rA.mnHeight       != rB.mnHeight)
48
16.0M
    ||  (rA.mnWidth        != rB.mnWidth)
49
15.8M
    ||  (rA.mnOrientation  != rB.mnOrientation) )
50
215k
        return false;
51
52
    // check mapping relevant attributes
53
15.8M
    if( (rA.mbVertical     != rB.mbVertical)
54
15.8M
    ||  (rA.meLanguage     != rB.meLanguage) )
55
0
        return false;
56
57
    // check font face attributes
58
15.8M
    if( (rA.GetWeight()    != rB.GetWeight())
59
15.8M
    ||  (rA.GetItalic()    != rB.GetItalic())
60
//  ||  (rA.meFamily       != rB.meFamily) // TODO: remove this mostly obsolete member
61
15.8M
    ||  (rA.GetPitch()     != rB.GetPitch()) )
62
1.19M
        return false;
63
64
    // check style name
65
14.6M
    if( rA.GetStyleName() != rB.GetStyleName() )
66
862
        return false;
67
68
    // Symbol fonts may recode from one type to another So they are only
69
    // safely equivalent for equal targets
70
14.6M
    if (rA.IsMicrosoftSymbolEncoded() || rB.IsMicrosoftSymbolEncoded())
71
172k
    {
72
172k
        if (rA.maTargetName != rB.maTargetName)
73
81.2k
            return false;
74
172k
    }
75
76
    // check for features
77
14.5M
    if ((rA.maTargetName.indexOf(vcl::font::FontSelectPattern::FEAT_PREFIX)
78
14.5M
         != -1 ||
79
14.2M
         rB.maTargetName.indexOf(vcl::font::FontSelectPattern::FEAT_PREFIX)
80
14.2M
         != -1) && rA.maTargetName != rB.maTargetName)
81
56
        return false;
82
83
14.5M
    if (rA.mbEmbolden != rB.mbEmbolden)
84
0
        return false;
85
86
14.5M
    if (rA.maItalicMatrix != rB.maItalicMatrix)
87
0
        return false;
88
89
14.5M
    return true;
90
14.5M
}
91
92
ImplFontCache::ImplFontCache()
93
150k
    : mpLastHitCacheEntry( nullptr )
94
150k
    , maFontInstanceList(std::numeric_limits<size_t>::max()) // "unlimited", i.e. no cleanup
95
    // The cache limit is set by the rough number of characters needed to read your average Asian newspaper.
96
150k
    , m_aBoundRectCache(3000)
97
150k
{}
98
99
ImplFontCache::~ImplFontCache()
100
150k
{
101
150k
    DBG_TESTSOLARMUTEX();
102
150k
    for (const auto & rLFI : maFontInstanceList)
103
53.6k
    {
104
53.6k
        rLFI.second->mpFontCache = nullptr;
105
53.6k
    }
106
150k
}
107
108
rtl::Reference<LogicalFontInstance> ImplFontCache::GetFontInstance( PhysicalFontCollection const * pFontList,
109
    const vcl::Font& rFont, const Size& rSize, float fExactHeight, bool bNonAntialias )
110
14.5M
{
111
    // initialize internal font request object
112
14.5M
    vcl::font::FontSelectPattern aFontSelData(rFont, rFont.GetFamilyName(), rSize, fExactHeight, bNonAntialias);
113
14.5M
    return GetFontInstance( pFontList, aFontSelData );
114
14.5M
}
115
116
rtl::Reference<LogicalFontInstance> ImplFontCache::GetFontInstance( PhysicalFontCollection const * pFontList,
117
    vcl::font::FontSelectPattern& aFontSelData )
118
14.5M
{
119
14.5M
    DBG_TESTSOLARMUTEX();
120
14.5M
    rtl::Reference<LogicalFontInstance> pFontInstance;
121
14.5M
    PhysicalFontFamily* pFontFamily = nullptr;
122
123
    // check if a directly matching logical font instance is already cached,
124
    // the most recently used font usually has a hit rate of >50%
125
14.5M
    if (mpLastHitCacheEntry && IFSD_Equal()(aFontSelData, mpLastHitCacheEntry->GetFontSelectPattern()))
126
0
        pFontInstance = mpLastHitCacheEntry;
127
14.5M
    else
128
14.5M
    {
129
14.5M
        FontInstanceList::const_iterator it = maFontInstanceList.find( aFontSelData );
130
14.5M
        if( it != maFontInstanceList.end() )
131
0
            pFontInstance = (*it).second;
132
14.5M
    }
133
134
14.5M
    if( !pFontInstance ) // no direct cache hit
135
14.5M
    {
136
        // find the best matching logical font family and update font selector accordingly
137
14.5M
        pFontFamily = pFontList->FindFontFamily( aFontSelData );
138
14.5M
        SAL_WARN_IF( (pFontFamily == nullptr), "vcl", "ImplFontCache::Get() No logical font found!" );
139
14.5M
        if( pFontFamily )
140
14.5M
        {
141
14.5M
            aFontSelData.maSearchName = pFontFamily->GetSearchName();
142
143
            // check if an indirectly matching logical font instance is already cached
144
14.5M
            FontInstanceList::const_iterator it = maFontInstanceList.find( aFontSelData );
145
14.5M
            if( it != maFontInstanceList.end() )
146
14.3M
                pFontInstance = (*it).second;
147
14.5M
        }
148
14.5M
    }
149
150
14.5M
    if( !pFontInstance && pFontFamily) // still no cache hit => create a new font instance
151
210k
    {
152
210k
        vcl::font::PhysicalFontFace* pFontData = pFontFamily->FindBestFontFace(aFontSelData);
153
154
        // create a new logical font instance from this physical font face
155
210k
        pFontInstance = pFontData->CreateFontInstance( aFontSelData );
156
210k
        pFontInstance->mpFontCache = this;
157
158
        // if we're substituting from or to a symbol font we may need a symbol
159
        // conversion table
160
210k
        if( pFontData->IsMicrosoftSymbolEncoded() || aFontSelData.IsMicrosoftSymbolEncoded() || IsOpenSymbol(aFontSelData.maSearchName) )
161
3.82k
        {
162
3.82k
            if( aFontSelData.maTargetName != aFontSelData.maSearchName )
163
3.82k
                pFontInstance->mpConversion = ConvertChar::GetRecodeData( aFontSelData.maTargetName, aFontSelData.maSearchName );
164
3.82k
        }
165
166
#ifdef MACOSX
167
        //It might be better to dig out the font version of the target font
168
        //to see if it's a modern re-coded apple symbol font in case that
169
        //font shows up on a different platform
170
        if (!pFontInstance->mpConversion &&
171
            aFontSelData.maTargetName.equalsIgnoreAsciiCase("symbol") &&
172
            aFontSelData.maSearchName.equalsIgnoreAsciiCase("symbol"))
173
        {
174
            pFontInstance->mpConversion = ConvertChar::GetRecodeData( u"Symbol", u"AppleSymbol" );
175
        }
176
#endif
177
178
210k
        static const size_t FONTCACHE_MAX = o3tl::IsRunningUnitTest() ? 1 : 50;
179
180
210k
        if (maFontInstanceList.size() >= FONTCACHE_MAX)
181
156k
        {
182
156k
            struct limit_exception : public std::exception {};
183
156k
            try
184
156k
            {
185
156k
                maFontInstanceList.remove_if([this] (FontInstanceList::key_value_pair_t const& rFontPair)
186
6.36M
                    {
187
6.36M
                        if (maFontInstanceList.size() < FONTCACHE_MAX)
188
109k
                            throw limit_exception();
189
6.25M
                        LogicalFontInstance* pFontEntry = rFontPair.second.get();
190
6.25M
                        if (pFontEntry->m_nCount > 1)
191
6.10M
                            return false;
192
155k
                        m_aBoundRectCache.remove_if([&pFontEntry] (GlyphBoundRectCache::key_value_pair_t const& rGlyphPair)
193
30.6M
                            { return rGlyphPair.first.m_pFont == pFontEntry; });
194
155k
                        if (mpLastHitCacheEntry == pFontEntry)
195
7.45k
                            mpLastHitCacheEntry = nullptr;
196
155k
                        return true;
197
6.25M
                    });
198
156k
            }
199
156k
            catch (limit_exception&) {}
200
156k
        }
201
202
210k
        assert(pFontInstance);
203
        // add the new entry to the cache
204
210k
        maFontInstanceList.insert({aFontSelData, pFontInstance.get()});
205
210k
    }
206
207
14.5M
    mpLastHitCacheEntry = pFontInstance.get();
208
14.5M
    return pFontInstance;
209
14.5M
}
210
211
rtl::Reference<LogicalFontInstance> ImplFontCache::GetGlyphFallbackFont( PhysicalFontCollection const * pFontCollection,
212
    vcl::font::FontSelectPattern& rFontSelData, LogicalFontInstance* pFontInstance, int nFallbackLevel, OUString& rMissingCodes )
213
0
{
214
    // get a candidate font for glyph fallback
215
    // unless the previously selected font got a device specific substitution
216
    // e.g. PsPrint Arial->Helvetica for udiaeresis when Helvetica doesn't support it
217
0
    if( nFallbackLevel >= 1)
218
0
    {
219
0
        PhysicalFontFamily* pFallbackData = nullptr;
220
221
        //fdo#33898 If someone has EUDC installed then they really want that to
222
        //be used as the first-choice glyph fallback seeing as it's filled with
223
        //private area codes with don't make any sense in any other font so
224
        //prioritize it here if it's available. Ideally we would remove from
225
        //rMissingCodes all the glyphs which it is able to resolve as an
226
        //optimization, but that's tricky to achieve cross-platform without
227
        //sufficient heavy-weight code that's likely to undo the value of the
228
        //optimization
229
0
        if (nFallbackLevel == 1)
230
0
            pFallbackData = pFontCollection->FindFontFamily(u"EUDC");
231
0
        if (!pFallbackData)
232
0
            pFallbackData = pFontCollection->GetGlyphFallbackFont(rFontSelData, pFontInstance, rMissingCodes, nFallbackLevel-1);
233
        // escape when there are no font candidates
234
0
        if( !pFallbackData  )
235
0
            return nullptr;
236
        // override the font name
237
0
        rFontSelData.SetFamilyName( pFallbackData->GetFamilyName() );
238
        // clear the cached normalized name
239
0
        rFontSelData.maSearchName.clear();
240
0
    }
241
242
0
    rtl::Reference<LogicalFontInstance> pFallbackFont = GetFontInstance( pFontCollection, rFontSelData );
243
0
    return pFallbackFont;
244
0
}
245
246
void ImplFontCache::Invalidate()
247
0
{
248
0
    DBG_TESTSOLARMUTEX();
249
    // #112304# make sure the font cache is really clean
250
0
    mpLastHitCacheEntry = nullptr;
251
0
    for (auto const & pair : maFontInstanceList)
252
0
        pair.second->mpFontCache = nullptr;
253
0
    maFontInstanceList.clear();
254
0
    m_aBoundRectCache.clear();
255
0
}
256
257
bool ImplFontCache::GetCachedGlyphBoundRect(const LogicalFontInstance *pFont, sal_GlyphId nID, basegfx::B2DRectangle &rRect)
258
50.8M
{
259
50.8M
    if (!pFont->GetFontCache())
260
0
        return false;
261
50.8M
    assert(pFont->GetFontCache() == this);
262
50.8M
    if (pFont->GetFontCache() != this)
263
0
        return false;
264
265
50.8M
    auto it = m_aBoundRectCache.find({pFont, nID});
266
50.8M
    if (it != m_aBoundRectCache.end())
267
50.1M
    {
268
50.1M
        rRect = it->second;
269
50.1M
        return true;
270
50.1M
    }
271
768k
    return false;
272
50.8M
}
273
274
void ImplFontCache::CacheGlyphBoundRect(const LogicalFontInstance *pFont, sal_GlyphId nID, const basegfx::B2DRectangle &rRect)
275
768k
{
276
768k
    if (!pFont->GetFontCache())
277
0
        return;
278
768k
    assert(pFont->GetFontCache() == this);
279
768k
    if (pFont->GetFontCache() != this)
280
0
        return;
281
282
768k
    m_aBoundRectCache.insert({{pFont, nID}, rRect});
283
768k
}
284
285
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */