Coverage Report

Created: 2026-06-30 11:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/unx/generic/fontmanager/fontconfig.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 <iostream>
23
#include <memory>
24
#include <string_view>
25
26
#include <o3tl/lru_map.hxx>
27
#include <unx/fontmanager.hxx>
28
#include <comphelper/sequence.hxx>
29
#include <vcl/dropcache.hxx>
30
#include <vcl/svapp.hxx>
31
#include <vcl/vclenum.hxx>
32
#include <font/FontSelectPattern.hxx>
33
#include <i18nlangtag/languagetag.hxx>
34
#include <i18nutil/unicode.hxx>
35
#include <rtl/strbuf.hxx>
36
#include <sal/log.hxx>
37
#include <comphelper/diagnose_ex.hxx>
38
#include <unicode/uchar.h>
39
#include <unicode/uscript.h>
40
#include <officecfg/Office/Common.hxx>
41
#include <org/freedesktop/PackageKit/SyncDbusSessionHelper.hpp>
42
#include <config_fonts.h>
43
44
#include <fontconfig/fontconfig.h>
45
46
#include <cstdio>
47
48
#include <unotools/configmgr.hxx>
49
#include <unotools/syslocaleoptions.hxx>
50
51
#include <osl/process.h>
52
53
#include <o3tl/hash_combine.hxx>
54
#include <set>
55
#include <utility>
56
#include <algorithm>
57
58
using namespace psp;
59
60
namespace
61
{
62
63
struct FontOptionsKey
64
{
65
    OUString m_sFamilyName;
66
    int m_nFontSize;
67
    FontItalic m_eItalic;
68
    FontWeight m_eWeight;
69
    FontWidth m_eWidth;
70
    FontPitch m_ePitch;
71
72
    bool operator==(const FontOptionsKey& rOther) const
73
105
    {
74
105
        return m_sFamilyName == rOther.m_sFamilyName &&
75
105
               m_nFontSize == rOther.m_nFontSize &&
76
105
               m_eItalic == rOther.m_eItalic &&
77
105
               m_eWeight == rOther.m_eWeight &&
78
105
               m_eWidth == rOther.m_eWidth &&
79
105
               m_ePitch == rOther.m_ePitch;
80
105
    }
81
};
82
83
}
84
85
namespace std
86
{
87
88
template <> struct hash<FontOptionsKey>
89
{
90
    std::size_t operator()(const FontOptionsKey& k) const noexcept
91
299
    {
92
299
        std::size_t seed = k.m_sFamilyName.hashCode();
93
299
        o3tl::hash_combine(seed, k.m_nFontSize);
94
299
        o3tl::hash_combine(seed, k.m_eItalic);
95
299
        o3tl::hash_combine(seed, k.m_eWeight);
96
299
        o3tl::hash_combine(seed, k.m_eWidth);
97
299
        o3tl::hash_combine(seed, k.m_ePitch);
98
299
        return seed;
99
299
    }
100
};
101
102
} // end std namespace
103
104
namespace
105
{
106
107
struct FcPatternDeleter
108
{
109
    void operator()(FcPattern* pPattern) const
110
54
    {
111
54
        FcPatternDestroy(pPattern);
112
54
    }
113
};
114
115
typedef std::unique_ptr<FcPattern, FcPatternDeleter> FcPatternUniquePtr;
116
117
class CachedFontConfigFontOptions : public CacheOwner
118
{
119
private:
120
    o3tl::lru_map<FontOptionsKey, FcPatternUniquePtr> lru_options_cache;
121
122
public:
123
    CachedFontConfigFontOptions()
124
        // arbitrary cache size of 10
125
#if defined __cpp_lib_memory_resource
126
106
        : lru_options_cache(10, &CacheOwner::GetMemoryResource())
127
#else
128
        : lru_options_cache(10)
129
#endif
130
106
    {
131
106
    }
132
133
    std::unique_ptr<FontConfigFontOptions> lookup(const FontOptionsKey& rKey)
134
117
    {
135
117
        auto it = lru_options_cache.find(rKey);
136
117
        if (it != lru_options_cache.end())
137
51
            return std::make_unique<FontConfigFontOptions>(FcPatternDuplicate(it->second.get()));
138
66
        return nullptr;
139
117
    }
140
141
    void cache(const FontOptionsKey& rKey, const FcPattern* pPattern)
142
66
    {
143
66
        lru_options_cache.insert(std::make_pair(rKey, FcPatternUniquePtr(FcPatternDuplicate(pPattern))));
144
66
    }
145
146
private:
147
    virtual OUString getCacheName() const override
148
0
    {
149
0
        return "CachedFontConfigFontOptions";
150
0
    }
151
152
    virtual bool dropCaches() override
153
0
    {
154
0
        lru_options_cache.clear();
155
0
        return true;
156
0
    }
157
158
    virtual void dumpState(rtl::OStringBuffer& rState) override
159
0
    {
160
0
        rState.append("\nCachedFontConfigFontOptions:\t");
161
0
        rState.append(static_cast<sal_Int32>(lru_options_cache.size()));
162
0
    }
163
};
164
165
typedef std::pair<FcChar8*, FcChar8*> lang_and_element;
166
167
class FontCfgWrapper
168
{
169
    FcFontSet* m_pFontSet;
170
    bool m_bRestrictFontSetToApplicationFonts;
171
172
    FontCfgWrapper();
173
    ~FontCfgWrapper();
174
175
public:
176
    static FontCfgWrapper& get();
177
    static void release();
178
179
    void addFontSet( FcSetName );
180
181
    FcFontSet* getFontSet();
182
    void replaceFontSet(FcFontSet* pFilteredFontSet);
183
184
    void clear();
185
186
    bool isRestrictingFontSetForTesting() const
187
0
    {
188
0
        return m_bRestrictFontSetToApplicationFonts;
189
0
    }
190
191
public:
192
    FcResult LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **family,
193
                                         const char *elementtype, const char *elementlangtype);
194
//to-do, make private and add some cleaner accessor methods
195
    std::unordered_map< OString, OString > m_aFontNameToLocalized;
196
    std::unordered_map< OString, OString > m_aLocalizedToCanonical;
197
    CachedFontConfigFontOptions m_aCachedFontOptions;
198
private:
199
    void cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname, const std::vector< lang_and_element > &lang_and_elements);
200
201
    std::unique_ptr<LanguageTag> m_pLanguageTag;
202
};
203
204
}
205
206
FontCfgWrapper::FontCfgWrapper()
207
106
    : m_pFontSet(nullptr)
208
106
    , m_bRestrictFontSetToApplicationFonts(false)
209
106
{
210
106
    FcInit();
211
106
}
212
213
#ifndef FC_FONT_WRAPPER
214
#define FC_FONT_WRAPPER "fontwrapper"
215
#endif
216
217
void FontCfgWrapper::addFontSet( FcSetName eSetName )
218
214
{
219
    // Add only acceptable fonts to our config, for future fontconfig use.
220
214
    FcFontSet* pOrig = FcConfigGetFonts( FcConfigGetCurrent(), eSetName );
221
214
    if( !pOrig )
222
0
        return;
223
224
    // filter the font sets to remove obsolete faces
225
1.59k
    for( int i = 0; i < pOrig->nfont; ++i )
226
1.38k
    {
227
1.38k
        FcPattern* pPattern = pOrig->fonts[i];
228
        // #i115131# ignore non-scalable fonts
229
        // Scalable fonts are usually outline fonts, but some bitmaps fonts
230
        // (like Noto Color Emoji) are also scalable.
231
1.38k
        FcBool bScalable = FcFalse;
232
1.38k
        FcResult eScalableRes = FcPatternGetBool(pPattern, FC_SCALABLE, 0, &bScalable);
233
1.38k
        if ((eScalableRes != FcResultMatch) || (bScalable == FcFalse))
234
0
            continue;
235
236
        // Ignore Type 1 fonts, too.
237
1.38k
        FcChar8* pFormat = nullptr;
238
1.38k
        FcResult eFormatRes = FcPatternGetString(pPattern, FC_FONTFORMAT, 0, &pFormat);
239
1.38k
        if ((eFormatRes == FcResultMatch) && (strcmp(reinterpret_cast<char*>(pFormat), "Type 1") == 0))
240
0
            continue;
241
242
        // Ignore any other non-SFNT wrapper format, including WOFF and WOFF2, too.
243
1.38k
        FcChar8* pWrapper = nullptr;
244
1.38k
        FcResult eWrapperRes = FcPatternGetString(pPattern, FC_FONT_WRAPPER, 0, &pWrapper);
245
1.38k
        if ((eWrapperRes == FcResultMatch) && (strcmp(reinterpret_cast<char*>(pWrapper), "SFNT") != 0))
246
0
            continue;
247
248
1.38k
        FcPatternReference( pPattern );
249
1.38k
        FcFontSetAdd( m_pFontSet, pPattern );
250
1.38k
    }
251
252
    // TODO?: FcFontSetDestroy( pOrig );
253
214
}
254
255
namespace
256
{
257
    int compareFontNames(const FcPattern *a, const FcPattern *b)
258
4.02k
    {
259
4.02k
        FcChar8 *pNameA=nullptr, *pNameB=nullptr;
260
261
4.02k
        bool bHaveA = FcPatternGetString(a, FC_FAMILY, 0, &pNameA) == FcResultMatch;
262
4.02k
        bool bHaveB = FcPatternGetString(b, FC_FAMILY, 0, &pNameB) == FcResultMatch;
263
264
4.02k
        if (bHaveA && bHaveB)
265
4.02k
            return strcmp(reinterpret_cast<const char*>(pNameA), reinterpret_cast<const char*>(pNameB));
266
267
0
        return int(bHaveA) - int(bHaveB);
268
4.02k
    }
269
270
    //Sort fonts so that fonts with the same family name are side-by-side, with
271
    //those with higher version numbers first
272
    class SortFont
273
    {
274
    public:
275
        bool operator()(const FcPattern *a, const FcPattern *b)
276
1.27k
        {
277
1.27k
            int comp = compareFontNames(a, b);
278
1.27k
            if (comp != 0)
279
318
                return comp < 0;
280
281
954
            int nVersionA=0, nVersionB=0;
282
283
954
            bool bHaveA = FcPatternGetInteger(a, FC_FONTVERSION, 0, &nVersionA) == FcResultMatch;
284
954
            bool bHaveB = FcPatternGetInteger(b, FC_FONTVERSION, 0, &nVersionB) == FcResultMatch;
285
286
954
            if (bHaveA && bHaveB)
287
954
                return nVersionA > nVersionB;
288
289
0
            return bHaveA > bHaveB;
290
954
        }
291
    };
292
293
    //See fdo#30729 for where an old opensymbol installed system-wide can
294
    //clobber the new opensymbol installed locally
295
296
    //See if this font is a duplicate with equal attributes which has already been
297
    //inserted, or if it an older version of an inserted fonts. Depends on FcFontSet
298
    //being sorted with SortFont
299
    bool isPreviouslyDuplicateOrObsoleted(FcFontSet const *pFSet, int i)
300
1.27k
    {
301
1.27k
        const FcPattern *a = pFSet->fonts[i];
302
303
1.27k
        FcPattern* pTestPatternA = FcPatternDuplicate(a);
304
1.27k
        FcPatternDel(pTestPatternA, FC_FILE);
305
1.27k
        FcPatternDel(pTestPatternA, FC_CHARSET);
306
1.27k
        FcPatternDel(pTestPatternA, FC_CAPABILITY);
307
1.27k
        FcPatternDel(pTestPatternA, FC_FONTVERSION);
308
1.27k
        FcPatternDel(pTestPatternA, FC_INDEX);
309
1.27k
        FcPatternDel(pTestPatternA, FC_LANG);
310
1.27k
        FcPatternDel(pTestPatternA, FC_POSTSCRIPT_NAME);
311
312
1.27k
        bool bIsDup(false);
313
314
        // fdo#66715: loop for case of several font files for same font
315
3.18k
        for (int j = i - 1; 0 <= j && !bIsDup; --j)
316
2.75k
        {
317
2.75k
            const FcPattern *b = pFSet->fonts[j];
318
319
2.75k
            if (compareFontNames(a, b) != 0)
320
848
                break;
321
322
1.90k
            FcPattern* pTestPatternB = FcPatternDuplicate(b);
323
1.90k
            FcPatternDel(pTestPatternB, FC_FILE);
324
1.90k
            FcPatternDel(pTestPatternB, FC_CHARSET);
325
1.90k
            FcPatternDel(pTestPatternB, FC_CAPABILITY);
326
1.90k
            FcPatternDel(pTestPatternB, FC_FONTVERSION);
327
1.90k
            FcPatternDel(pTestPatternB, FC_INDEX);
328
1.90k
            FcPatternDel(pTestPatternB, FC_LANG);
329
1.90k
            FcPatternDel(pTestPatternB, FC_POSTSCRIPT_NAME);
330
331
1.90k
            bIsDup = FcPatternEqual(pTestPatternA, pTestPatternB);
332
333
1.90k
            FcPatternDestroy(pTestPatternB);
334
1.90k
        }
335
336
1.27k
        FcPatternDestroy(pTestPatternA);
337
338
1.27k
        return bIsDup;
339
1.27k
    }
340
}
341
342
FcFontSet* FontCfgWrapper::getFontSet()
343
172
{
344
172
    if( !m_pFontSet )
345
106
    {
346
106
        m_pFontSet = FcFontSetCreate();
347
#if HAVE_MORE_FONTS
348
        m_bRestrictFontSetToApplicationFonts = [] {
349
            return getenv("SAL_NON_APPLICATION_FONT_USE") != nullptr;
350
        }();
351
#endif
352
        // Add the application fonts before the system fonts.
353
        // tdf#157939 We will remove duplicate fonts, where the duplicate is
354
        // the one with a smaller version number. If the same version font is
355
        // available system-wide or bundled with our application, then we
356
        // prefer via stable-sort the first one we see. Load application fonts
357
        // first to prefer the one we bundle in the application in that case.
358
106
        addFontSet( FcSetApplication );
359
106
        if (!m_bRestrictFontSetToApplicationFonts)
360
106
            addFontSet( FcSetSystem );
361
362
106
        std::stable_sort(m_pFontSet->fonts,m_pFontSet->fonts+m_pFontSet->nfont,SortFont());
363
106
    }
364
365
172
    return m_pFontSet;
366
172
}
367
368
void FontCfgWrapper::replaceFontSet(FcFontSet* pFilteredFontSet)
369
106
{
370
106
    if (m_pFontSet)
371
106
        FcFontSetDestroy(m_pFontSet);
372
106
    m_pFontSet = pFilteredFontSet;
373
106
}
374
375
FontCfgWrapper::~FontCfgWrapper()
376
0
{
377
0
    clear();
378
    //To-Do: get gtk vclplug smoketest to pass
379
    //FcFini();
380
0
}
381
382
static FontCfgWrapper* pOneInstance = nullptr;
383
384
FontCfgWrapper& FontCfgWrapper::get()
385
1.60k
{
386
1.60k
    if( ! pOneInstance )
387
106
        pOneInstance = new FontCfgWrapper();
388
1.60k
    return *pOneInstance;
389
1.60k
}
390
391
void FontCfgWrapper::release()
392
0
{
393
0
    if( pOneInstance )
394
0
    {
395
0
        delete pOneInstance;
396
0
        pOneInstance = nullptr;
397
0
    }
398
0
}
399
400
namespace
401
{
402
    FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag);
403
404
    FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag)
405
2.54k
    {
406
2.54k
        FcChar8* candidate = elements.begin()->second;
407
        /* FIXME-BCP47: once fontconfig supports language tags this
408
         * language-territory stuff needs to be changed! */
409
2.54k
        SAL_INFO_IF( !rLangTag.isIsoLocale(), "vcl.fonts", "localizedsorter::bestname - not an ISO locale");
410
2.54k
        OString sLangMatch(OUStringToOString(rLangTag.getLanguage().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8));
411
2.54k
        OString sFullMatch = sLangMatch +
412
2.54k
            "-" +
413
2.54k
            OUStringToOString(rLangTag.getCountry().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8);
414
415
2.54k
        bool alreadyclosematch = false;
416
2.54k
        bool found_fallback_englishname = false;
417
2.54k
        for (auto const& element : elements)
418
2.54k
        {
419
2.54k
            const char *pLang = reinterpret_cast<const char*>(element.first);
420
2.54k
            if( sFullMatch == pLang)
421
0
            {
422
                // both language and country match
423
0
                candidate = element.second;
424
0
                break;
425
0
            }
426
2.54k
            else if( alreadyclosematch )
427
0
            {
428
                // current candidate matches lang of lang-TERRITORY
429
                // override candidate only if there is a full match
430
0
                continue;
431
0
            }
432
2.54k
            else if( sLangMatch == pLang)
433
2.54k
            {
434
                // just the language matches
435
2.54k
                candidate = element.second;
436
2.54k
                alreadyclosematch = true;
437
2.54k
            }
438
0
            else if( found_fallback_englishname )
439
0
            {
440
                // already found an english fallback, don't override candidate
441
                // unless there is a better language match
442
0
                continue;
443
0
            }
444
0
            else if( rtl_str_compare( pLang, "en") == 0)
445
0
            {
446
                // select a fallback candidate of the first english element
447
                // name
448
0
                candidate = element.second;
449
0
                found_fallback_englishname = true;
450
0
            }
451
2.54k
        }
452
2.54k
        return candidate;
453
2.54k
    }
454
}
455
456
//Set up maps to quickly map between a fonts best UI name and all the rest of its names, and vice versa
457
void FontCfgWrapper::cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname,
458
    const std::vector< lang_and_element > &lang_and_elements)
459
1.27k
{
460
1.27k
    for (auto const& element : lang_and_elements)
461
1.27k
    {
462
1.27k
        const char *candidate = reinterpret_cast<const char*>(element.second);
463
1.27k
        if (rtl_str_compare(candidate, reinterpret_cast<const char*>(bestfontname)) != 0)
464
0
            m_aFontNameToLocalized[OString(candidate)] = OString(reinterpret_cast<const char*>(bestfontname));
465
1.27k
    }
466
1.27k
    if (rtl_str_compare(reinterpret_cast<const char*>(origfontname), reinterpret_cast<const char*>(bestfontname)) != 0)
467
0
        m_aLocalizedToCanonical[OString(reinterpret_cast<const char*>(bestfontname))] = OString(reinterpret_cast<const char*>(origfontname));
468
1.27k
}
469
470
FcResult FontCfgWrapper::LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **element,
471
                                                     const char *elementtype, const char *elementlangtype)
472
2.54k
{                                                /* e. g.:      ^ FC_FAMILY              ^ FC_FAMILYLANG */
473
2.54k
    FcChar8 *origelement;
474
2.54k
    FcResult eElementRes = FcPatternGetString( pPattern, elementtype, 0, &origelement );
475
2.54k
    *element = origelement;
476
477
2.54k
    if( eElementRes == FcResultMatch)
478
2.54k
    {
479
2.54k
        FcChar8* elementlang = nullptr;
480
2.54k
        if (FcPatternGetString( pPattern, elementlangtype, 0, &elementlang ) == FcResultMatch)
481
2.54k
        {
482
2.54k
            std::vector< lang_and_element > lang_and_elements;
483
2.54k
            lang_and_elements.emplace_back(elementlang, *element);
484
2.54k
            int k = 1;
485
2.54k
            while (true)
486
2.54k
            {
487
2.54k
                if (FcPatternGetString( pPattern, elementlangtype, k, &elementlang ) != FcResultMatch)
488
2.54k
                    break;
489
0
                if (FcPatternGetString( pPattern, elementtype, k, element ) != FcResultMatch)
490
0
                    break;
491
0
                lang_and_elements.emplace_back(elementlang, *element);
492
0
                ++k;
493
0
            }
494
495
2.54k
            if (!m_pLanguageTag)
496
106
                m_pLanguageTag.reset(new LanguageTag(SvtSysLocaleOptions().GetRealUILanguageTag()));
497
498
            // FontConfig orders Typographic Family/Subfamily before old
499
            // R/B/I/BI-compatible ones, but we want the later, so reverse the
500
            // names to match them first.
501
2.54k
            std::reverse(lang_and_elements.begin(), lang_and_elements.end());
502
503
2.54k
            *element = bestname(lang_and_elements, *m_pLanguageTag);
504
505
            //if this element is a fontname, map the other names to this best-name
506
2.54k
            if (rtl_str_compare(elementtype, FC_FAMILY) == 0)
507
1.27k
                cacheLocalizedFontNames(origelement, *element, lang_and_elements);
508
2.54k
        }
509
2.54k
    }
510
511
2.54k
    return eElementRes;
512
2.54k
}
513
514
void FontCfgWrapper::clear()
515
106
{
516
106
    m_aFontNameToLocalized.clear();
517
106
    m_aLocalizedToCanonical.clear();
518
106
    if( m_pFontSet )
519
0
    {
520
0
        FcFontSetDestroy( m_pFontSet );
521
0
        m_pFontSet = nullptr;
522
0
    }
523
106
    m_pLanguageTag.reset();
524
106
}
525
526
/*
527
 * PrintFontManager::initFontconfig
528
 */
529
void PrintFontManager::initFontconfig()
530
106
{
531
106
    FontCfgWrapper& rWrapper = FontCfgWrapper::get();
532
106
    rWrapper.clear();
533
106
}
534
535
namespace
536
{
537
    FontWeight convertWeight(int weight)
538
1.27k
    {
539
        // set weight
540
1.27k
        if( weight <= FC_WEIGHT_THIN )
541
0
            return WEIGHT_THIN;
542
1.27k
        else if( weight <= FC_WEIGHT_ULTRALIGHT )
543
0
            return WEIGHT_ULTRALIGHT;
544
1.27k
        else if( weight <= FC_WEIGHT_LIGHT )
545
0
            return WEIGHT_LIGHT;
546
1.27k
        else if( weight <= FC_WEIGHT_BOOK )
547
0
            return WEIGHT_SEMILIGHT;
548
1.27k
        else if( weight <= FC_WEIGHT_NORMAL )
549
638
            return WEIGHT_NORMAL;
550
636
        else if( weight <= FC_WEIGHT_MEDIUM )
551
0
            return WEIGHT_MEDIUM;
552
636
        else if( weight <= FC_WEIGHT_SEMIBOLD )
553
0
            return WEIGHT_SEMIBOLD;
554
636
        else if( weight <= FC_WEIGHT_BOLD )
555
636
            return WEIGHT_BOLD;
556
0
        else if( weight <= FC_WEIGHT_ULTRABOLD )
557
0
            return WEIGHT_ULTRABOLD;
558
0
        return WEIGHT_BLACK;
559
1.27k
    }
560
561
    FontItalic convertSlant(int slant)
562
1.27k
    {
563
        // set italic
564
1.27k
        if( slant == FC_SLANT_ITALIC )
565
636
            return ITALIC_NORMAL;
566
638
        else if( slant == FC_SLANT_OBLIQUE )
567
0
            return ITALIC_OBLIQUE;
568
638
        return ITALIC_NONE;
569
1.27k
    }
570
571
    FontPitch convertSpacing(int spacing)
572
425
    {
573
        // set pitch
574
425
        if( spacing == FC_MONO || spacing == FC_CHARCELL )
575
425
            return PITCH_FIXED;
576
0
        return PITCH_VARIABLE;
577
425
    }
578
579
    // translation: fontconfig enum -> vcl enum
580
    FontWidth convertWidth(int width)
581
1.27k
    {
582
1.27k
        if (width == FC_WIDTH_ULTRACONDENSED)
583
0
            return WIDTH_ULTRA_CONDENSED;
584
1.27k
        else if (width == FC_WIDTH_EXTRACONDENSED)
585
0
            return WIDTH_EXTRA_CONDENSED;
586
1.27k
        else if (width == FC_WIDTH_CONDENSED)
587
0
            return WIDTH_CONDENSED;
588
1.27k
        else if (width == FC_WIDTH_SEMICONDENSED)
589
0
            return WIDTH_SEMI_CONDENSED;
590
1.27k
        else if (width == FC_WIDTH_SEMIEXPANDED)
591
0
            return WIDTH_SEMI_EXPANDED;
592
1.27k
        else if (width == FC_WIDTH_EXPANDED)
593
0
            return WIDTH_EXPANDED;
594
1.27k
        else if (width == FC_WIDTH_EXTRAEXPANDED)
595
0
            return WIDTH_EXTRA_EXPANDED;
596
1.27k
        else if (width == FC_WIDTH_ULTRAEXPANDED)
597
0
            return WIDTH_ULTRA_EXPANDED;
598
1.27k
        return WIDTH_NORMAL;
599
1.27k
    }
600
}
601
602
namespace
603
{
604
    // for variable fonts, FC_INDEX has been changed such that the lower half is now the
605
    // index of the font within the collection, and the upper half has been repurposed
606
    // as the index within the variations
607
    unsigned int GetCollectionIndex(unsigned int nEntryId)
608
1.27k
    {
609
1.27k
        return nEntryId & 0xFFFF;
610
1.27k
    }
611
612
    unsigned int GetVariationIndex(unsigned int nEntryId)
613
1.27k
    {
614
1.27k
        return nEntryId >> 16;
615
1.27k
    }
616
}
617
618
std::optional<PrintFontManager::PrintFont> PrintFontManager::fontFromFcPattern(FcPattern* pPattern)
619
1.27k
{
620
1.27k
    FontCfgWrapper& rWrapper = FontCfgWrapper::get();
621
622
1.27k
    FcChar8* file = nullptr;
623
1.27k
    FcChar8* family = nullptr;
624
1.27k
    FcChar8* style = nullptr;
625
1.27k
    FcChar8* format = nullptr;
626
1.27k
    int slant = 0;
627
1.27k
    int weight = 0;
628
1.27k
    int width = 0;
629
1.27k
    int spacing = 0;
630
1.27k
    int symbol = 0;
631
1.27k
    int nEntryId = -1;
632
1.27k
    FcBool scalable = false;
633
634
1.27k
    FcResult eFileRes         = FcPatternGetString(pPattern, FC_FILE, 0, &file);
635
1.27k
    FcResult eFamilyRes       = rWrapper.LocalizedElementFromPattern( pPattern, &family, FC_FAMILY, FC_FAMILYLANG );
636
1.27k
    FcResult eStyleRes        = rWrapper.LocalizedElementFromPattern( pPattern, &style, FC_STYLE, FC_STYLELANG );
637
1.27k
    FcResult eSlantRes        = FcPatternGetInteger(pPattern, FC_SLANT, 0, &slant);
638
1.27k
    FcResult eWeightRes       = FcPatternGetInteger(pPattern, FC_WEIGHT, 0, &weight);
639
1.27k
    FcResult eWidthRes        = FcPatternGetInteger(pPattern, FC_WIDTH, 0, &width);
640
1.27k
    FcResult eSpacRes         = FcPatternGetInteger(pPattern, FC_SPACING, 0, &spacing);
641
1.27k
    FcResult eScalableRes     = FcPatternGetBool(pPattern, FC_SCALABLE, 0, &scalable);
642
1.27k
    FcResult eSymbolRes       = FcPatternGetBool(pPattern, FC_SYMBOL, 0, &symbol);
643
1.27k
    FcResult eIndexRes        = FcPatternGetInteger(pPattern, FC_INDEX, 0, &nEntryId);
644
1.27k
    FcResult eFormatRes       = FcPatternGetString(pPattern, FC_FONTFORMAT, 0, &format);
645
646
1.27k
    if( eFileRes != FcResultMatch || eFamilyRes != FcResultMatch || eScalableRes != FcResultMatch || eStyleRes != FcResultMatch )
647
0
        return std::nullopt;
648
649
#ifdef MACOSX
650
    // Skip system fonts not intended for app use
651
    if (family && family[0] == '.')
652
        return std::nullopt;
653
#endif
654
655
1.27k
    SAL_INFO(
656
1.27k
        "vcl.fonts.detail",
657
1.27k
        "found font \"" << family << "\" in file " << file << ", weight = "
658
1.27k
        << (eWeightRes == FcResultMatch ? weight : -1) << ", slant = "
659
1.27k
        << (eSpacRes == FcResultMatch ? slant : -1) << ", style = \""
660
1.27k
        << (eStyleRes == FcResultMatch ? reinterpret_cast<const char*>(style) : "<nil>")
661
1.27k
        << "\",  width = " << (eWeightRes == FcResultMatch ? width : -1) << ", spacing = "
662
1.27k
        << (eSpacRes == FcResultMatch ? spacing : -1) << ", scalable = "
663
1.27k
        << (eScalableRes == FcResultMatch ? scalable : -1) << ", format "
664
1.27k
        << (eFormatRes == FcResultMatch
665
1.27k
            ? reinterpret_cast<const char*>(format) : "<unknown>")
666
1.27k
        << " symbol = " << (eSymbolRes == FcResultMatch ? symbol : -1));
667
668
    // We support only scalable fonts
669
1.27k
    if( eScalableRes == FcResultMatch && ! scalable )
670
0
        return std::nullopt;
671
672
1.27k
    PrintFont aFont;
673
1.27k
    aFont.m_aFontFile = reinterpret_cast<char*>(file);
674
1.27k
    if (eIndexRes == FcResultMatch)
675
1.27k
    {
676
1.27k
        aFont.m_nCollectionEntry = GetCollectionIndex(nEntryId);
677
1.27k
        aFont.m_nVariationEntry = GetVariationIndex(nEntryId);
678
1.27k
    }
679
680
1.27k
    auto& rFA = aFont.m_aFontAttributes;
681
1.27k
    rFA.SetWeight(WEIGHT_NORMAL);
682
1.27k
    rFA.SetWidthType(WIDTH_NORMAL);
683
1.27k
    rFA.SetPitch(PITCH_VARIABLE);
684
1.27k
    rFA.SetQuality(512);
685
686
1.27k
    rFA.SetFamilyName(OStringToOUString(std::string_view(reinterpret_cast<char*>(family)), RTL_TEXTENCODING_UTF8));
687
1.27k
    if (eStyleRes == FcResultMatch)
688
1.27k
        rFA.SetStyleName(OStringToOUString(std::string_view(reinterpret_cast<char*>(style)), RTL_TEXTENCODING_UTF8));
689
1.27k
    if (eWeightRes == FcResultMatch)
690
1.27k
        rFA.SetWeight(convertWeight(weight));
691
1.27k
    if (eWidthRes == FcResultMatch)
692
1.27k
        rFA.SetWidthType(convertWidth(width));
693
1.27k
    if (eSpacRes == FcResultMatch)
694
425
        rFA.SetPitch(convertSpacing(spacing));
695
1.27k
    if (eSlantRes == FcResultMatch)
696
1.27k
        rFA.SetItalic(convertSlant(slant));
697
1.27k
    if (eSymbolRes == FcResultMatch)
698
1.27k
        rFA.SetMicrosoftSymbolEncoded(bool(symbol));
699
700
1.27k
    return aFont;
701
1.27k
}
702
703
std::vector<PrintFontManager::PrintFont> PrintFontManager::fontsFromFontconfigFile(std::string_view rFilePath)
704
9
{
705
9
    std::vector<PrintFont> aFonts;
706
707
9
    FcFontSet* pFSet = FcConfigGetFonts(FcConfigGetCurrent(), FcSetApplication);
708
9
    if (!pFSet)
709
0
        return aFonts;
710
711
19
    for (int i = 0; i < pFSet->nfont; i++)
712
10
    {
713
10
        FcChar8* file = nullptr;
714
10
        FcResult eFileRes = FcPatternGetString(pFSet->fonts[i], FC_FILE, 0, &file);
715
10
        if (eFileRes != FcResultMatch)
716
0
            continue;
717
10
        if (rFilePath != reinterpret_cast<char*>(file))
718
8
            continue;
719
720
2
        auto oFont = fontFromFcPattern(pFSet->fonts[i]);
721
2
        if (oFont)
722
2
            aFonts.push_back(std::move(*oFont));
723
2
    }
724
725
9
    return aFonts;
726
9
}
727
728
void PrintFontManager::countFontconfigFonts()
729
106
{
730
106
    int nFonts = 0;
731
106
    FontCfgWrapper& rWrapper = FontCfgWrapper::get();
732
733
106
    FcFontSet* pFSet = rWrapper.getFontSet();
734
106
    const bool bMinimalFontset = comphelper::IsFuzzing();
735
106
    if( pFSet )
736
106
    {
737
106
        SAL_INFO("vcl.fonts", "found " << pFSet->nfont << " entries in fontconfig fontset");
738
739
106
        FcFontSet* pFilteredSet = FcFontSetCreate();
740
741
1.48k
        for( int i = 0; i < pFSet->nfont; i++ )
742
1.37k
        {
743
1.37k
            if (bMinimalFontset)
744
1.37k
            {
745
1.37k
                FcChar8* family = nullptr;
746
1.37k
                (void)FcPatternGetString(pFSet->fonts[i], FC_FAMILY, 0, &family);
747
1.37k
                if (family && strncmp(reinterpret_cast<char*>(family), "Liberation", strlen("Liberation")))
748
106
                    continue;
749
1.37k
            }
750
751
1.27k
            if (isPreviouslyDuplicateOrObsoleted(pFSet, i))
752
0
            {
753
0
                FcChar8* file = nullptr;
754
0
                (void)FcPatternGetString(pFSet->fonts[i], FC_FILE, 0, &file);
755
0
                SAL_INFO("vcl.fonts.detail", "Ditching " << file << " as duplicate/obsolete");
756
0
                continue;
757
0
            }
758
759
1.27k
            auto oFont = fontFromFcPattern(pFSet->fonts[i]);
760
1.27k
            if (!oFont)
761
0
                continue;
762
763
            // sort into known fonts
764
1.27k
            fontID nFontID = m_nNextFontID++;
765
1.27k
            const OString aFontFile = oFont->m_aFontFile;
766
1.27k
            m_aFonts.emplace(nFontID, std::move(*oFont));
767
1.27k
            m_aFontFileToFontID[aFontFile].insert(nFontID);
768
1.27k
            nFonts++;
769
770
1.27k
            FcPattern* pPattern = pFSet->fonts[i];
771
1.27k
            FcPatternReference(pPattern);
772
1.27k
            FcFontSetAdd(pFilteredSet, pPattern);
773
774
1.27k
            SAL_INFO("vcl.fonts.detail", "inserted font as fontID " << nFontID);
775
1.27k
        }
776
777
        // tdf#157939 if we drop fonts, drop them from the FcConfig set too so they are not
778
        // candidates for suggestions by fontconfig
779
106
        if (pFSet->nfont != pFilteredSet->nfont)
780
106
            rWrapper.replaceFontSet(pFilteredSet);
781
0
        else
782
0
            FcFontSetDestroy(pFilteredSet);
783
784
106
    }
785
786
    // how does one get rid of the config ?
787
106
    SAL_INFO("vcl.fonts", "inserted " << nFonts << " fonts from fontconfig");
788
106
}
789
790
void PrintFontManager::deinitFontconfig()
791
0
{
792
0
    FontCfgWrapper::release();
793
0
}
794
795
void PrintFontManager::addFontconfigDir( const OString& rDirName )
796
212
{
797
212
    const char* pDirName = rDirName.getStr();
798
212
    bool bDirOk = (FcConfigAppFontAddDir(FcConfigGetCurrent(), reinterpret_cast<FcChar8 const *>(pDirName) ) == FcTrue);
799
800
212
    SAL_INFO("vcl.fonts", "FcConfigAppFontAddDir( \"" << pDirName << "\") => " << bDirOk);
801
802
212
    if( !bDirOk )
803
0
        return;
804
805
    // load dir-specific fc-config file too if available
806
212
    const OString aConfFileName = rDirName + "/fc_local.conf";
807
212
    FILE* pCfgFile = fopen( aConfFileName.getStr(), "rb" );
808
212
    if( pCfgFile )
809
0
    {
810
0
        fclose( pCfgFile);
811
0
        bool bCfgOk = FcConfigParseAndLoad(FcConfigGetCurrent(),
812
0
                        reinterpret_cast<FcChar8 const *>(aConfFileName.getStr()), FcTrue);
813
814
0
        SAL_INFO_IF(!bCfgOk,
815
0
                "vcl.fonts", "FcConfigParseAndLoad( \""
816
0
                << aConfFileName << "\") => " << bCfgOk);
817
212
    } else {
818
212
        SAL_INFO("vcl.fonts", "cannot open " << aConfFileName);
819
212
    }
820
212
}
821
822
void PrintFontManager::addFontconfigFile( const OString& rFileName )
823
9
{
824
9
    const char* pFileName = rFileName.getStr();
825
9
    bool bFileOk = (FcConfigAppFontAddFile(FcConfigGetCurrent(), reinterpret_cast<FcChar8 const *>(pFileName) ) == FcTrue);
826
827
9
    SAL_INFO("vcl.fonts", "FcConfigAppFontAddFile(\"" << pFileName << "\") => " << std::boolalpha << bFileOk);
828
829
9
    if( !bFileOk )
830
7
        return;
831
832
    // FIXME: we want to add only the newly added font not re-add the whole
833
    // application font set.
834
2
    FontCfgWrapper& rWrapper = FontCfgWrapper::get();
835
2
    rWrapper.addFontSet( FcSetApplication );
836
2
}
837
838
void PrintFontManager::removeFontconfigFile(std::string_view aFileName)
839
0
{
840
0
    FcFontSet* pOrig = FcConfigGetFonts(FcConfigGetCurrent(), FcSetApplication);
841
0
    if (!pOrig)
842
0
        return;
843
844
0
    std::set<OString> restoreList;
845
846
    // filter the font sets to remove the file
847
0
    for (int i = 0; i < pOrig->nfont; ++i)
848
0
    {
849
0
        FcChar8* file = nullptr;
850
0
        FcResult eFileRes = FcPatternGetString(pOrig->fonts[i], FC_FILE, 0, &file);
851
0
        if (eFileRes == FcResultMatch)
852
0
        {
853
0
            if (std::string_view curFile(reinterpret_cast<char*>(file)); aFileName != curFile)
854
0
                restoreList.emplace(curFile);
855
0
        }
856
0
    }
857
858
0
    FcConfigAppFontClear(FcConfigGetCurrent());
859
860
    // Re-add the rest of files
861
0
    for (const OString& thisFilename : restoreList)
862
0
    {
863
0
        FcConfigAppFontAddFile(FcConfigGetCurrent(),
864
0
                               reinterpret_cast<FcChar8 const*>(thisFilename.getStr()));
865
0
    }
866
867
0
    FontCfgWrapper& rWrapper = FontCfgWrapper::get();
868
0
    rWrapper.addFontSet( FcSetApplication );
869
0
}
870
871
static void addtopattern(FcPattern *pPattern,
872
    FontItalic eItalic, FontWeight eWeight, FontWidth eWidth, FontPitch ePitch)
873
66
{
874
66
    if( eItalic != ITALIC_DONTKNOW )
875
66
    {
876
66
        int nSlant = FC_SLANT_ROMAN;
877
66
        switch( eItalic )
878
66
        {
879
34
            case ITALIC_NORMAL:
880
34
                nSlant = FC_SLANT_ITALIC;
881
34
                break;
882
0
            case ITALIC_OBLIQUE:
883
0
                nSlant = FC_SLANT_OBLIQUE;
884
0
                break;
885
32
            default:
886
32
                break;
887
66
        }
888
66
        FcPatternAddInteger(pPattern, FC_SLANT, nSlant);
889
66
    }
890
66
    if( eWeight != WEIGHT_DONTKNOW )
891
66
    {
892
66
        int nWeight = FC_WEIGHT_NORMAL;
893
66
        switch( eWeight )
894
66
        {
895
0
            case WEIGHT_THIN:           nWeight = FC_WEIGHT_THIN;break;
896
0
            case WEIGHT_ULTRALIGHT:     nWeight = FC_WEIGHT_ULTRALIGHT;break;
897
0
            case WEIGHT_LIGHT:          nWeight = FC_WEIGHT_LIGHT;break;
898
0
            case WEIGHT_SEMILIGHT:      nWeight = FC_WEIGHT_BOOK;break;
899
35
            case WEIGHT_NORMAL:         nWeight = FC_WEIGHT_NORMAL;break;
900
0
            case WEIGHT_MEDIUM:         nWeight = FC_WEIGHT_MEDIUM;break;
901
0
            case WEIGHT_SEMIBOLD:       nWeight = FC_WEIGHT_SEMIBOLD;break;
902
31
            case WEIGHT_BOLD:           nWeight = FC_WEIGHT_BOLD;break;
903
0
            case WEIGHT_ULTRABOLD:      nWeight = FC_WEIGHT_ULTRABOLD;break;
904
0
            case WEIGHT_BLACK:          nWeight = FC_WEIGHT_BLACK;break;
905
0
            default:
906
0
                break;
907
66
        }
908
66
        FcPatternAddInteger(pPattern, FC_WEIGHT, nWeight);
909
66
    }
910
66
    if( eWidth != WIDTH_DONTKNOW )
911
66
    {
912
66
        int nWidth = FC_WIDTH_NORMAL;
913
66
        switch( eWidth )
914
66
        {
915
0
            case WIDTH_ULTRA_CONDENSED: nWidth = FC_WIDTH_ULTRACONDENSED;break;
916
0
            case WIDTH_EXTRA_CONDENSED: nWidth = FC_WIDTH_EXTRACONDENSED;break;
917
0
            case WIDTH_CONDENSED:       nWidth = FC_WIDTH_CONDENSED;break;
918
0
            case WIDTH_SEMI_CONDENSED:  nWidth = FC_WIDTH_SEMICONDENSED;break;
919
66
            case WIDTH_NORMAL:          nWidth = FC_WIDTH_NORMAL;break;
920
0
            case WIDTH_SEMI_EXPANDED:   nWidth = FC_WIDTH_SEMIEXPANDED;break;
921
0
            case WIDTH_EXPANDED:        nWidth = FC_WIDTH_EXPANDED;break;
922
0
            case WIDTH_EXTRA_EXPANDED:  nWidth = FC_WIDTH_EXTRAEXPANDED;break;
923
0
            case WIDTH_ULTRA_EXPANDED:  nWidth = FC_WIDTH_ULTRAEXPANDED;break;
924
0
            default:
925
0
                break;
926
66
        }
927
66
        FcPatternAddInteger(pPattern, FC_WIDTH, nWidth);
928
66
    }
929
66
    if( ePitch == PITCH_DONTKNOW )
930
0
        return;
931
932
66
    int nSpacing = FC_PROPORTIONAL;
933
66
    switch( ePitch )
934
66
    {
935
0
        case PITCH_FIXED:           nSpacing = FC_MONO;break;
936
66
        case PITCH_VARIABLE:        nSpacing = FC_PROPORTIONAL;break;
937
0
        default:
938
0
            break;
939
66
    }
940
66
    FcPatternAddInteger(pPattern, FC_SPACING, nSpacing);
941
66
    if (nSpacing == FC_MONO)
942
0
        FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>("monospace"));
943
66
}
944
945
namespace
946
{
947
    //Someday fontconfig will hopefully use bcp47, see:
948
    //https://gitlab.freedesktop.org/fontconfig/fontconfig/-/issues/50
949
    //In the meantime try something that will fit to workaround, see:
950
    //https://gitlab.freedesktop.org/fontconfig/fontconfig/-/issues/30
951
    OString mapToFontConfigLangTag(const LanguageTag &rLangTag)
952
0
    {
953
0
        std::shared_ptr<FcStrSet> xLangSet(FcGetLangs(), FcStrSetDestroy);
954
0
        OString sLangAttrib;
955
956
0
        sLangAttrib = OUStringToOString(rLangTag.getBcp47(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
957
0
        if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
958
0
        {
959
0
            return sLangAttrib;
960
0
        }
961
962
0
        sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
963
0
        if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
964
0
        {
965
0
            return sLangAttrib;
966
0
        }
967
968
0
        OString sLang = OUStringToOString(rLangTag.getLanguage(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
969
0
        OString sRegion = OUStringToOString(rLangTag.getCountry(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase();
970
971
0
        if (!sRegion.isEmpty())
972
0
        {
973
0
            sLangAttrib = sLang + "-" + sRegion;
974
0
            if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr())))
975
0
            {
976
0
                return sLangAttrib;
977
0
            }
978
0
        }
979
980
0
        if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLang.getStr())))
981
0
        {
982
0
            return sLang;
983
0
        }
984
985
0
        return OString();
986
0
    }
987
988
    bool isEmoji(sal_uInt32 nCurrentChar)
989
0
    {
990
0
        return u_hasBinaryProperty(nCurrentChar, UCHAR_EMOJI);
991
0
    }
992
993
    //returns true if the given code-point couldn't possibly be in rLangTag.
994
    bool isImpossibleCodePointForLang(const LanguageTag &rLangTag, sal_uInt32 currentChar)
995
0
    {
996
        //a non-default script is set, let's believe it
997
0
        if (rLangTag.hasScript())
998
0
            return false;
999
1000
0
        int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT);
1001
0
        UScriptCode eScript = static_cast<UScriptCode>(script);
1002
0
        bool bIsImpossible = false;
1003
0
        OUString sLang = rLangTag.getLanguage();
1004
0
        switch (eScript)
1005
0
        {
1006
            //http://en.wiktionary.org/wiki/Category:Oriya_script_languages
1007
0
            case USCRIPT_ORIYA:
1008
0
                bIsImpossible =
1009
0
                    sLang != "or" &&
1010
0
                    sLang != "kxv";
1011
0
                break;
1012
            //http://en.wiktionary.org/wiki/Category:Telugu_script_languages
1013
0
            case USCRIPT_TELUGU:
1014
0
                bIsImpossible =
1015
0
                    sLang != "te" &&
1016
0
                    sLang != "gon" &&
1017
0
                    sLang != "kfc";
1018
0
                break;
1019
            //http://en.wiktionary.org/wiki/Category:Bengali_script_languages
1020
0
            case USCRIPT_BENGALI:
1021
0
                bIsImpossible =
1022
0
                    sLang != "bn" &&
1023
0
                    sLang != "as" &&
1024
0
                    sLang != "bpy" &&
1025
0
                    sLang != "ctg" &&
1026
0
                    sLang != "sa";
1027
0
                break;
1028
0
            default:
1029
0
                break;
1030
0
        }
1031
0
        SAL_WARN_IF(bIsImpossible, "vcl.fonts", "In glyph fallback throwing away the language property of "
1032
0
            << sLang << " because the detected script for '0x"
1033
0
            << OUString::number(currentChar, 16)
1034
0
            << "' is " << uscript_getName(eScript)
1035
0
            << " and that language doesn't make sense. Autodetecting instead.");
1036
0
        return bIsImpossible;
1037
0
    }
1038
1039
    OUString getExemplarLangTagForCodePoint(sal_uInt32 currentChar)
1040
0
    {
1041
0
        if (isEmoji(currentChar))
1042
0
            return u"und-zsye"_ustr;
1043
0
        int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT);
1044
0
        UScriptCode eScript = static_cast<UScriptCode>(script);
1045
0
        OStringBuffer aBuf(unicode::getExemplarLanguageForUScriptCode(eScript));
1046
0
        if (const char* pScriptCode = uscript_getShortName(eScript))
1047
0
            aBuf.append(OStringChar('-') + pScriptCode);
1048
0
        return OStringToOUString(aBuf, RTL_TEXTENCODING_UTF8);
1049
0
    }
1050
}
1051
1052
IMPL_LINK_NOARG(PrintFontManager, autoInstallFontLangSupport, Timer *, void)
1053
0
{
1054
0
    try
1055
0
    {
1056
0
        using namespace org::freedesktop::PackageKit;
1057
0
        css::uno::Reference<XSyncDbusSessionHelper> xSyncDbusSessionHelper(SyncDbusSessionHelper::create(comphelper::getProcessComponentContext()));
1058
0
        xSyncDbusSessionHelper->InstallFontconfigResources(comphelper::containerToSequence(m_aCurrentRequests), u"hide-finished"_ustr);
1059
0
    }
1060
0
    catch (const css::uno::Exception&)
1061
0
    {
1062
0
        TOOLS_INFO_EXCEPTION("vcl.fonts", "InstallFontconfigResources problem");
1063
        // Disable this method from now on. It's simply not available on some systems
1064
        // and leads to an error dialog being shown each time this is called tdf#104883
1065
0
        std::shared_ptr<comphelper::ConfigurationChanges> batch( comphelper::ConfigurationChanges::create() );
1066
0
        officecfg::Office::Common::PackageKit::EnableFontInstallation::set(false, batch);
1067
0
        batch->commit();
1068
0
    }
1069
1070
0
    m_aCurrentRequests.clear();
1071
0
}
1072
1073
void PrintFontManager::Substitute(vcl::font::FontSelectPattern &rPattern, OUString& rMissingCodes)
1074
0
{
1075
0
    FontCfgWrapper& rWrapper = FontCfgWrapper::get();
1076
1077
    // build pattern argument for fontconfig query
1078
0
    FcPattern* pPattern = FcPatternCreate();
1079
1080
    // Prefer scalable fonts
1081
0
    FcPatternAddBool(pPattern, FC_SCALABLE, FcTrue);
1082
1083
0
    const OString aTargetName = OUStringToOString( rPattern.maTargetName, RTL_TEXTENCODING_UTF8 );
1084
0
    const FcChar8* pTargetNameUtf8 = reinterpret_cast<FcChar8 const *>(aTargetName.getStr());
1085
0
    FcPatternAddString(pPattern, FC_FAMILY, pTargetNameUtf8);
1086
1087
    // Try to map tools FontFamily to fontconfig FC_FAMILY. Note that FcPatternAddString() appends
1088
    // to a list, so it won't overwrite the previous FcPatternAddString(FC_FAMILY), this way we can
1089
    // express that we wanted a certain font, and otherwise a given family style.
1090
0
    FontFamily eFamilyType = rPattern.GetFamilyType();
1091
0
    switch (eFamilyType)
1092
0
    {
1093
0
        case FAMILY_ROMAN:
1094
0
            FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<const FcChar8*>("serif"));
1095
0
            break;
1096
0
        case FAMILY_SWISS:
1097
0
            FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<const FcChar8*>("sans"));
1098
0
            break;
1099
0
        default:
1100
0
            break;
1101
0
    }
1102
1103
0
    LanguageTag aLangTag(rPattern.meLanguage);
1104
0
    OString aLangAttrib = mapToFontConfigLangTag(aLangTag);
1105
1106
0
    bool bMissingJustBullet = false;
1107
0
    bool bMissingIdeographicComma = false;
1108
1109
    // Add required Unicode characters, if any
1110
0
    if ( !rMissingCodes.isEmpty() )
1111
0
    {
1112
0
        FcCharSet *codePoints = FcCharSetCreate();
1113
0
        if (rMissingCodes.getLength() == 1)
1114
0
        {
1115
0
            bMissingJustBullet = rMissingCodes[0] == 0xb7;
1116
0
            bMissingIdeographicComma = rMissingCodes[0] == 0x3001;
1117
0
        }
1118
0
        for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); )
1119
0
        {
1120
            // also handle unicode surrogates
1121
0
            const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex );
1122
0
            FcCharSetAddChar( codePoints, nCode );
1123
            //if the codepoint is impossible for this lang tag, then clear it
1124
            //and autodetect something useful
1125
0
            if (!aLangAttrib.isEmpty() && (isImpossibleCodePointForLang(aLangTag, nCode) || isEmoji(nCode)))
1126
0
                aLangAttrib.clear();
1127
            //#i105784#/rhbz#527719  improve selection of fallback font
1128
0
            if (aLangAttrib.isEmpty())
1129
0
            {
1130
0
                aLangTag.reset(getExemplarLangTagForCodePoint(nCode));
1131
0
                aLangAttrib = mapToFontConfigLangTag(aLangTag);
1132
0
            }
1133
0
        }
1134
0
        FcPatternAddCharSet(pPattern, FC_CHARSET, codePoints);
1135
0
        FcCharSetDestroy(codePoints);
1136
0
    }
1137
1138
0
    if (!aLangAttrib.isEmpty())
1139
0
        FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr()));
1140
1141
    // bodge: testTdf153440 wants a fallback to an emoji font it adds as a temp
1142
    // testing font which has the required glyphs, but that emoji font is not
1143
    // seen as a "color" font, while it is possible that OpenDyslexic can be
1144
    // bundled, which *is* a "color" font. The default rules (See in Fedora 38
1145
    // at least) then prefer a color font *without* the glyphs over a non-color
1146
    // font *with* the glyphs, which seems like a bug to me.
1147
    // Maybe this is an attempt to prefer color emoji fonts over non-color emoji
1148
    // containing fonts like Symbola which has gone awry?
1149
    // For testing purposes (isRestrictingFontSetForTesting is true) force a
1150
    // preference for non-color fonts.
1151
0
    if (rWrapper.isRestrictingFontSetForTesting())
1152
0
        FcPatternAddBool(pPattern, FC_COLOR, FcFalse);
1153
1154
0
    addtopattern(pPattern, rPattern.GetItalic(), rPattern.GetWeight(),
1155
0
        rPattern.GetWidthType(), rPattern.GetPitch());
1156
1157
    // query fontconfig for a substitute
1158
0
    FcConfigSubstitute(FcConfigGetCurrent(), pPattern, FcMatchPattern);
1159
0
    FcDefaultSubstitute(pPattern);
1160
1161
    // process the result of the fontconfig query
1162
0
    FcResult eResult = FcResultNoMatch;
1163
0
    FcFontSet* pFontSet = rWrapper.getFontSet();
1164
0
    FcPattern* pResult = FcFontSetMatch(FcConfigGetCurrent(), &pFontSet, 1, pPattern, &eResult);
1165
0
    FcPatternDestroy( pPattern );
1166
1167
0
    FcFontSet*  pSet = nullptr;
1168
0
    if( pResult )
1169
0
    {
1170
0
        pSet = FcFontSetCreate();
1171
        // info: destroying the pSet destroys pResult implicitly
1172
        // since pResult was "added" to pSet
1173
0
        FcFontSetAdd( pSet, pResult );
1174
0
    }
1175
1176
0
    if( pSet )
1177
0
    {
1178
0
        if( pSet->nfont > 0 )
1179
0
        {
1180
0
            bool bRet = false;
1181
1182
            //extract the closest match
1183
0
            FcChar8* file = nullptr;
1184
0
            FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file);
1185
0
            int nEntryId = 0;
1186
0
            FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId);
1187
0
            if (eIndexRes != FcResultMatch)
1188
0
                nEntryId = 0;
1189
0
            if( eFileRes == FcResultMatch )
1190
0
            {
1191
0
                OString aOrgPath( reinterpret_cast<char*>(file) );
1192
0
                fontID nFontID = findFontFileID(aOrgPath, GetCollectionIndex(nEntryId), GetVariationIndex(nEntryId));
1193
0
                auto const* pFont = getFont(nFontID);
1194
0
                if (pFont)
1195
0
                {
1196
0
                    rPattern.maSearchName = pFont->m_aFontAttributes.GetFamilyName();
1197
0
                    bRet = true;
1198
0
                }
1199
0
            }
1200
1201
0
            SAL_WARN_IF(!bRet, "vcl.fonts", "no FC_FILE found, falling back to name search");
1202
1203
0
            if (!bRet)
1204
0
            {
1205
0
                FcChar8* family = nullptr;
1206
0
                FcResult eFamilyRes = FcPatternGetString( pSet->fonts[0], FC_FAMILY, 0, &family );
1207
1208
                // get the family name
1209
0
                if( eFamilyRes == FcResultMatch )
1210
0
                {
1211
0
                    OString sFamily(reinterpret_cast<char*>(family));
1212
0
                    std::unordered_map< OString, OString >::const_iterator aI =
1213
0
                        rWrapper.m_aFontNameToLocalized.find(sFamily);
1214
0
                    if (aI != rWrapper.m_aFontNameToLocalized.end())
1215
0
                        sFamily = aI->second;
1216
0
                    rPattern.maSearchName = OStringToOUString( sFamily, RTL_TEXTENCODING_UTF8 );
1217
0
                    bRet = true;
1218
0
                }
1219
0
            }
1220
1221
0
            if (bRet)
1222
0
            {
1223
0
                int val = 0;
1224
0
                if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WEIGHT, 0, &val))
1225
0
                    rPattern.SetWeight( convertWeight(val) );
1226
0
                if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SLANT, 0, &val))
1227
0
                    rPattern.SetItalic( convertSlant(val) );
1228
0
                if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SPACING, 0, &val))
1229
0
                    rPattern.SetPitch ( convertSpacing(val) );
1230
0
                if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WIDTH, 0, &val))
1231
0
                    rPattern.SetWidthType ( convertWidth(val) );
1232
0
                FcBool bEmbolden;
1233
0
                if (FcResultMatch == FcPatternGetBool(pSet->fonts[0], FC_EMBOLDEN, 0, &bEmbolden))
1234
0
                    rPattern.mbEmbolden = bEmbolden;
1235
0
                FcMatrix *pMatrix = nullptr;
1236
0
                if (FcResultMatch == FcPatternGetMatrix(pSet->fonts[0], FC_MATRIX, 0, &pMatrix))
1237
0
                {
1238
0
                    rPattern.maItalicMatrix.xx = pMatrix->xx;
1239
0
                    rPattern.maItalicMatrix.xy = pMatrix->xy;
1240
0
                    rPattern.maItalicMatrix.yx = pMatrix->yx;
1241
0
                    rPattern.maItalicMatrix.yy = pMatrix->yy;
1242
0
                }
1243
0
            }
1244
1245
            // update rMissingCodes by removing resolved code points
1246
0
            if( !rMissingCodes.isEmpty() )
1247
0
            {
1248
0
                std::unique_ptr<sal_uInt32[]> const pRemainingCodes(new sal_uInt32[rMissingCodes.getLength()]);
1249
0
                int nRemainingLen = 0;
1250
0
                FcCharSet* codePoints;
1251
0
                if (!FcPatternGetCharSet(pSet->fonts[0], FC_CHARSET, 0, &codePoints))
1252
0
                {
1253
0
                    for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); )
1254
0
                    {
1255
                        // also handle surrogates
1256
0
                        const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex );
1257
0
                        if (FcCharSetHasChar(codePoints, nCode) != FcTrue)
1258
0
                            pRemainingCodes[ nRemainingLen++ ] = nCode;
1259
0
                    }
1260
0
                }
1261
0
                OUString sStillMissing(pRemainingCodes.get(), nRemainingLen);
1262
0
                if (!Application::IsHeadlessModeEnabled() && officecfg::Office::Common::PackageKit::EnableFontInstallation::get())
1263
0
                {
1264
0
                    if (sStillMissing == rMissingCodes) //replaced nothing
1265
0
                    {
1266
                        //It'd be better if we could ask packagekit using the
1267
                        //missing codepoints or some such rather than using
1268
                        //"language" as a proxy to how fontconfig considers
1269
                        //scripts to default to a given language.
1270
0
                        for (sal_Int32 i = 0; i < nRemainingLen; ++i)
1271
0
                        {
1272
0
                            LanguageTag aOurTag(getExemplarLangTagForCodePoint(pRemainingCodes[i]));
1273
0
                            OString sTag = OUStringToOString(aOurTag.getBcp47(), RTL_TEXTENCODING_UTF8);
1274
0
                            if (!m_aPreviousLangSupportRequests.insert(sTag).second)
1275
0
                                continue;
1276
0
                            sTag = mapToFontConfigLangTag(aOurTag);
1277
0
                            if (!sTag.isEmpty() && m_aPreviousLangSupportRequests.find(sTag) == m_aPreviousLangSupportRequests.end())
1278
0
                            {
1279
0
                                OString sReq = OString::Concat(":lang=") + sTag;
1280
0
                                m_aCurrentRequests.push_back(OUString::fromUtf8(sReq));
1281
0
                                m_aPreviousLangSupportRequests.insert(sTag);
1282
0
                            }
1283
0
                        }
1284
0
                    }
1285
0
                    if (!m_aCurrentRequests.empty())
1286
0
                        m_aFontInstallerTimer.Start();
1287
0
                }
1288
0
                rMissingCodes = sStillMissing;
1289
0
            }
1290
0
        }
1291
1292
0
        FcFontSetDestroy( pSet );
1293
0
    }
1294
1295
0
    SAL_INFO("vcl.fonts", "PrintFontManager::Substitute: replacing missing font: '"
1296
0
                              << rPattern.maTargetName << "' with '" << rPattern.maSearchName
1297
0
                              << "'");
1298
1299
0
    static const bool bAbortOnFontSubstitute = [] {
1300
0
        const char* pEnv = getenv("SAL_NON_APPLICATION_FONT_USE");
1301
0
        return pEnv && strcmp(pEnv, "abort") == 0;
1302
0
    }();
1303
0
    if (bAbortOnFontSubstitute && rPattern.maTargetName != rPattern.maSearchName)
1304
0
    {
1305
0
        if (bMissingJustBullet || bMissingIdeographicComma)
1306
0
        {
1307
            // Some fonts exist in "more_fonts", but:
1308
            // a) have no U+00B7 MIDDLE DOT so will always glyph fallback on
1309
            // measuring mnBulletOffset in FontMetricData::ImplInitTextLineSize
1310
            // b) have no U+3001 IDEOGRAPHIC COMMA so will always glyph fallback on
1311
            // measuring its width in FontMetricData::ImplInitFlags for CJK text
1312
0
            return;
1313
0
        }
1314
0
        if (rPattern.maTargetName == "Linux Libertine G" && rPattern.maSearchName == "Linux Libertine O")
1315
0
            return;
1316
0
        SAL_WARN("vcl.fonts", "PrintFontManager::Substitute: missing font: '" << rPattern.maTargetName <<
1317
0
                              "' try: " << rPattern.maSearchName << " instead");
1318
0
        std::cerr << "terminating test due to missing font: " << rPattern.maTargetName << std::endl;
1319
0
        std::abort();
1320
0
    }
1321
0
}
1322
1323
FontConfigFontOptions::~FontConfigFontOptions()
1324
98
{
1325
98
    FcPatternDestroy(mpPattern);
1326
98
}
1327
1328
FcPattern *FontConfigFontOptions::GetPattern() const
1329
206
{
1330
206
    return mpPattern;
1331
206
}
1332
1333
#ifndef FC_FONT_VARIATIONS
1334
#define FC_FONT_VARIATIONS "fontvariations"
1335
#endif
1336
1337
void FontConfigFontOptions::SyncPattern(const OString& rFileName, sal_uInt32 nIndex, sal_uInt32 nVariation, bool bEmbolden, const std::vector<vcl::font::Variation>& rVariations)
1338
117
{
1339
117
    FcPatternDel(mpPattern, FC_FILE);
1340
117
    FcPatternAddString(mpPattern, FC_FILE, reinterpret_cast<FcChar8 const *>(rFileName.getStr()));
1341
117
    FcPatternDel(mpPattern, FC_INDEX);
1342
117
    sal_uInt32 nFcIndex = (nVariation << 16) | nIndex;
1343
117
    FcPatternAddInteger(mpPattern, FC_INDEX, nFcIndex);
1344
117
    FcPatternDel(mpPattern, FC_EMBOLDEN);
1345
117
    FcPatternAddBool(mpPattern, FC_EMBOLDEN, bEmbolden ? FcTrue : FcFalse);
1346
1347
117
    FcPatternDel(mpPattern, FC_FONT_VARIATIONS);
1348
117
    if (!rVariations.empty())
1349
2
    {
1350
2
        OStringBuffer aVariationsString;
1351
2
        char buf[128];
1352
2
        for (const auto& rVariation : rVariations)
1353
2
        {
1354
2
            if (!aVariationsString.isEmpty())
1355
0
                aVariationsString.append(',');
1356
2
            hb_variation_t aHbVar = { rVariation.nTag, rVariation.fValue };
1357
2
            hb_variation_to_string(&aHbVar, buf, sizeof(buf));
1358
2
            aVariationsString.append(buf);
1359
2
        }
1360
2
        FcPatternAddString(mpPattern, FC_FONT_VARIATIONS, reinterpret_cast<const FcChar8*>(aVariationsString.getStr()));
1361
2
    }
1362
117
}
1363
1364
std::unique_ptr<FontConfigFontOptions> PrintFontManager::getFontOptions(const FontAttributes& rInfo, int nSize)
1365
117
{
1366
117
    FontOptionsKey aKey{ rInfo.GetFamilyName(), nSize, rInfo.GetItalic(),
1367
117
                         rInfo.GetWeight(), rInfo.GetWidthType(), rInfo.GetPitch() };
1368
1369
117
    FontCfgWrapper& rWrapper = FontCfgWrapper::get();
1370
1371
117
    std::unique_ptr<FontConfigFontOptions> pOptions = rWrapper.m_aCachedFontOptions.lookup(aKey);
1372
117
    if (pOptions)
1373
51
        return pOptions;
1374
1375
66
    FcConfig* pConfig = FcConfigGetCurrent();
1376
66
    FcPattern* pPattern = FcPatternCreate();
1377
1378
66
    OString sFamily = OUStringToOString(aKey.m_sFamilyName, RTL_TEXTENCODING_UTF8);
1379
1380
66
    std::unordered_map< OString, OString >::const_iterator aI = rWrapper.m_aLocalizedToCanonical.find(sFamily);
1381
66
    if (aI != rWrapper.m_aLocalizedToCanonical.end())
1382
0
        sFamily = aI->second;
1383
66
    if( !sFamily.isEmpty() )
1384
66
        FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(sFamily.getStr()));
1385
1386
66
    addtopattern(pPattern, aKey.m_eItalic, aKey.m_eWeight, aKey.m_eWidth, aKey.m_ePitch);
1387
66
    FcPatternAddDouble(pPattern, FC_PIXEL_SIZE, nSize);
1388
1389
66
    FcConfigSubstitute(pConfig, pPattern, FcMatchPattern);
1390
66
    FontConfigFontOptions::cairo_font_options_substitute(pPattern);
1391
66
    FcDefaultSubstitute(pPattern);
1392
1393
66
    FcResult eResult = FcResultNoMatch;
1394
66
    FcFontSet* pFontSet = rWrapper.getFontSet();
1395
66
    if (FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult))
1396
66
    {
1397
66
        rWrapper.m_aCachedFontOptions.cache(aKey, pResult);
1398
66
        pOptions.reset(new FontConfigFontOptions(pResult));
1399
66
    }
1400
1401
    // cleanup
1402
66
    FcPatternDestroy( pPattern );
1403
1404
66
    return pOptions;
1405
117
}
1406
1407
1408
bool PrintFontManager::matchFont(FontAttributes& rDFA, const css::lang::Locale& rLocale)
1409
0
{
1410
0
    bool bFound = false;
1411
0
    FontCfgWrapper& rWrapper = FontCfgWrapper::get();
1412
1413
0
    FcConfig* pConfig = FcConfigGetCurrent();
1414
0
    FcPattern* pPattern = FcPatternCreate();
1415
1416
    // populate pattern with font characteristics
1417
0
    const LanguageTag aLangTag(rLocale);
1418
0
    const OString aLangAttrib = mapToFontConfigLangTag(aLangTag);
1419
0
    if (!aLangAttrib.isEmpty())
1420
0
        FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr()));
1421
1422
0
    OString aFamily = OUStringToOString(rDFA.GetFamilyName(), RTL_TEXTENCODING_UTF8);
1423
0
    if( !aFamily.isEmpty() )
1424
0
        FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(aFamily.getStr()));
1425
1426
0
    addtopattern(pPattern, rDFA.GetItalic(), rDFA.GetWeight(), rDFA.GetWidthType(), rDFA.GetPitch());
1427
1428
0
    FcConfigSubstitute(pConfig, pPattern, FcMatchPattern);
1429
0
    FcDefaultSubstitute(pPattern);
1430
0
    FcResult eResult = FcResultNoMatch;
1431
0
    FcFontSet *pFontSet = rWrapper.getFontSet();
1432
0
    FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult);
1433
0
    if( pResult )
1434
0
    {
1435
0
        FcFontSet* pSet = FcFontSetCreate();
1436
0
        FcFontSetAdd( pSet, pResult );
1437
0
        if( pSet->nfont > 0 )
1438
0
        {
1439
            //extract the closest match
1440
0
            FcChar8* file = nullptr;
1441
0
            FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file);
1442
0
            int nEntryId = 0;
1443
0
            FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId);
1444
0
            if (eIndexRes != FcResultMatch)
1445
0
                nEntryId = 0;
1446
0
            if( eFileRes == FcResultMatch )
1447
0
            {
1448
0
                OString aOrgPath( reinterpret_cast<char*>(file) );
1449
0
                fontID nFontID = findFontFileID(aOrgPath,
1450
0
                                              GetCollectionIndex(nEntryId),
1451
0
                                              GetVariationIndex(nEntryId));
1452
0
                auto const* pFont = getFont(nFontID);
1453
0
                if (pFont)
1454
0
                {
1455
0
                    rDFA = pFont->m_aFontAttributes;
1456
0
                    bFound = true;
1457
0
                }
1458
0
            }
1459
0
        }
1460
        // info: destroying the pSet destroys pResult implicitly
1461
        // since pResult was "added" to pSet
1462
0
        FcFontSetDestroy( pSet );
1463
0
    }
1464
1465
    // cleanup
1466
0
    FcPatternDestroy( pPattern );
1467
1468
0
    return bFound;
1469
0
}
1470
1471
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */