Coverage Report

Created: 2026-03-31 11:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/unx/generic/fontmanager/fontmanager.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 <memory>
21
#include <osl/thread.h>
22
23
#include <unx/fontmanager.hxx>
24
#include <impfontcharmap.hxx>
25
#include <unotools/syslocaleoptions.hxx>
26
#include <unx/gendata.hxx>
27
#include <unx/helper.hxx>
28
#include <vcl/fontcharmap.hxx>
29
30
#include <tools/urlobj.hxx>
31
#include <tools/UnixWrappers.h>
32
33
#include <o3tl/string_view.hxx>
34
#include <osl/file.hxx>
35
36
#include <rtl/ustrbuf.hxx>
37
#include <rtl/strbuf.hxx>
38
39
#include <sal/macros.h>
40
#include <sal/log.hxx>
41
42
#include <i18nlangtag/applelangid.hxx>
43
44
#include <sft.hxx>
45
46
#if OSL_DEBUG_LEVEL > 1
47
#include <sys/times.h>
48
#include <stdio.h>
49
#endif
50
51
#include <algorithm>
52
#include <set>
53
54
#ifdef CALLGRIND_COMPILE
55
#include <valgrind/callgrind.h>
56
#endif
57
58
#include <com/sun/star/beans/XMaterialHolder.hpp>
59
60
using namespace vcl;
61
using namespace psp;
62
using namespace com::sun::star::uno;
63
using namespace com::sun::star::lang;
64
65
/*
66
 *  PrintFont implementations
67
 */
68
PrintFontManager::PrintFont::PrintFont()
69
1.27k
:   m_nDirectory(0)
70
1.27k
,   m_nCollectionEntry(0)
71
1.27k
,   m_nVariationEntry(0)
72
1.27k
{
73
1.27k
}
74
75
/*
76
 *  one instance only
77
 */
78
PrintFontManager& PrintFontManager::get()
79
145
{
80
145
    GenericUnixSalData* const pSalData(GetGenericUnixSalData());
81
145
    assert(pSalData);
82
145
    return *pSalData->GetPrintFontManager();
83
145
}
84
85
/*
86
 *  the PrintFontManager
87
 */
88
89
PrintFontManager::PrintFontManager()
90
106
    : m_nNextFontID( 1 )
91
106
    , m_nNextDirAtom( 1 )
92
106
    , m_aFontInstallerTimer("PrintFontManager m_aFontInstallerTimer")
93
106
{
94
106
    m_aFontInstallerTimer.SetInvokeHandler(LINK(this, PrintFontManager, autoInstallFontLangSupport));
95
106
    m_aFontInstallerTimer.SetTimeout(5000);
96
97
#ifdef CALLGRIND_COMPILE
98
    CALLGRIND_TOGGLE_COLLECT();
99
    CALLGRIND_ZERO_STATS();
100
#endif
101
102
#if OSL_DEBUG_LEVEL > 1
103
    clock_t aStart;
104
    clock_t aStep1;
105
    clock_t aStep2;
106
107
    struct tms tms;
108
109
    aStart = times(&tms);
110
#endif
111
112
    // first try fontconfig
113
106
    initFontconfig();
114
115
    // part one - look for downloadable fonts
116
106
    rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
117
106
    const OUString& rSalPrivatePath = psp::getFontPath();
118
119
    // search for the fonts in SAL_PRIVATE_FONTPATH first; those are
120
    // the fonts installed with the office
121
106
    if (!rSalPrivatePath.isEmpty())
122
106
    {
123
106
        OString aPath = OUStringToOString(rSalPrivatePath, aEncoding);
124
106
        sal_Int32 nIndex = 0;
125
106
        do
126
318
        {
127
318
            OString aToken = aPath.getToken(0, ';', nIndex);
128
318
            normPath(aToken);
129
318
            if (!aToken.isEmpty())
130
212
                addFontconfigDir(aToken);
131
318
        } while (nIndex >= 0);
132
106
    }
133
134
106
    countFontconfigFonts();
135
136
#if OSL_DEBUG_LEVEL > 1
137
    aStep1 = times(&tms);
138
139
    aStep2 = times(&tms);
140
    SAL_INFO("vcl.fonts",
141
             "PrintFontManager::PrintFontManager: collected " << m_aFonts.size() << " fonts.");
142
    double fTick = (double)sysconf(_SC_CLK_TCK);
143
    SAL_INFO("vcl.fonts", "Step 1 took " << ((double)(aStep1 - aStart) / fTick) << " seconds.");
144
    SAL_INFO("vcl.fonts", "Step 2 took " << ((double)(aStep2 - aStep1) / fTick) << " seconds.");
145
#endif
146
147
#ifdef CALLGRIND_COMPILE
148
    CALLGRIND_DUMP_STATS();
149
    CALLGRIND_TOGGLE_COLLECT();
150
#endif
151
106
}
152
153
PrintFontManager::~PrintFontManager()
154
0
{
155
0
    m_aFontInstallerTimer.Stop();
156
0
    deinitFontconfig();
157
0
}
158
159
OString PrintFontManager::getDirectory( int nAtom ) const
160
0
{
161
0
    std::unordered_map< int, OString >::const_iterator it( m_aAtomToDir.find( nAtom ) );
162
0
    return it != m_aAtomToDir.end() ? it->second : OString();
163
0
}
164
165
int PrintFontManager::getDirectoryAtom( const OString& rDirectory )
166
1.27k
{
167
1.27k
    int nAtom = 0;
168
1.27k
    std::unordered_map< OString, int >::const_iterator it
169
1.27k
          ( m_aDirToAtom.find( rDirectory ) );
170
1.27k
    if( it != m_aDirToAtom.end() )
171
1.16k
        nAtom = it->second;
172
106
    else
173
106
    {
174
106
        nAtom = m_nNextDirAtom++;
175
106
        m_aDirToAtom[ rDirectory ] = nAtom;
176
106
        m_aAtomToDir[ nAtom ] = rDirectory;
177
106
    }
178
1.27k
    return nAtom;
179
1.27k
}
180
181
std::vector<fontID> PrintFontManager::findFontFileIDs( std::u16string_view rFileUrl ) const
182
0
{
183
0
    rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
184
0
    INetURLObject aPath( rFileUrl );
185
0
    OString aName(OUStringToOString(aPath.GetLastName(INetURLObject::DecodeMechanism::WithCharset, aEncoding), aEncoding));
186
0
    OString aDir( OUStringToOString(
187
0
        INetURLObject::decode( aPath.GetPath(), INetURLObject::DecodeMechanism::WithCharset, aEncoding ), aEncoding ) );
188
189
0
    auto dirIt = m_aDirToAtom.find(aDir);
190
0
    if (dirIt == m_aDirToAtom.end())
191
0
        return {};
192
193
0
    return findFontFileIDs(dirIt->second, aName);
194
0
}
195
196
std::vector<fontID> PrintFontManager::addFontFile( std::u16string_view rFileUrl )
197
0
{
198
0
    rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
199
0
    INetURLObject aPath( rFileUrl );
200
0
    OString aName(OUStringToOString(aPath.GetLastName(INetURLObject::DecodeMechanism::WithCharset, aEncoding), aEncoding));
201
0
    OString aDir( OUStringToOString(
202
0
        INetURLObject::decode( aPath.GetPath(), INetURLObject::DecodeMechanism::WithCharset, aEncoding ), aEncoding ) );
203
204
0
    int nDirID = getDirectoryAtom( aDir );
205
0
    std::vector<fontID> aFontIds = findFontFileIDs( nDirID, aName );
206
0
    if( aFontIds.empty() )
207
0
    {
208
0
        addFontconfigFile(OUStringToOString(aPath.GetFull(), osl_getThreadTextEncoding()));
209
210
0
        std::vector<PrintFont> aNewFonts = analyzeFontFile(nDirID, aName);
211
0
        for (auto & font : aNewFonts)
212
0
        {
213
0
            fontID nFontId = m_nNextFontID++;
214
0
            m_aFonts[nFontId] = std::move(font);
215
0
            m_aFontFileToFontID[ aName ].insert( nFontId );
216
0
            aFontIds.push_back(nFontId);
217
0
        }
218
0
    }
219
0
    return aFontIds;
220
0
}
221
222
void PrintFontManager::removeFontFile(std::u16string_view rFileUrl)
223
0
{
224
0
    INetURLObject aPath(rFileUrl);
225
0
    rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
226
0
    if (auto ids = findFontFileIDs(rFileUrl); !ids.empty())
227
0
    {
228
0
        OString aName(OUStringToOString(
229
0
            aPath.GetLastName(INetURLObject::DecodeMechanism::WithCharset, aEncoding), aEncoding));
230
231
0
        for (auto nFontID : ids)
232
0
        {
233
0
            m_aFonts.erase(nFontID);
234
0
            m_aFontFileToFontID[aName].erase(nFontID);
235
0
        }
236
0
    }
237
238
0
    removeFontconfigFile(OUStringToOString(aPath.GetFull(), aEncoding));
239
0
}
240
241
std::vector<PrintFontManager::PrintFont> PrintFontManager::analyzeFontFile( int nDirID, const OString& rFontFile) const
242
0
{
243
0
    std::vector<PrintFontManager::PrintFont> aNewFonts;
244
245
0
    OString aDir( getDirectory( nDirID ) );
246
247
0
    OString aFullPath = aDir + "/" + rFontFile;
248
249
0
    bool bSupported;
250
0
    int nFD;
251
0
    int n;
252
0
    if (sscanf(aFullPath.getStr(), "/:FD:/%d%n", &nFD, &n) == 1 && aFullPath.getStr()[n] == '\0')
253
0
    {
254
        // Hack, pathname that actually means we will use a pre-opened file descriptor
255
0
        bSupported = true;
256
0
    }
257
0
    else
258
0
    {
259
        // #i1872# reject unreadable files
260
0
        if( wrap_access( aFullPath.getStr(), R_OK ) )
261
0
            return aNewFonts;
262
263
0
        bSupported = false;
264
0
        OString aExt( rFontFile.copy( rFontFile.lastIndexOf( '.' )+1 ) );
265
0
        if( aExt.equalsIgnoreAsciiCase("ttf")
266
0
             ||  aExt.equalsIgnoreAsciiCase("ttc")
267
0
             ||  aExt.equalsIgnoreAsciiCase("tte")   // #i33947# for Gaiji support
268
0
             ||  aExt.equalsIgnoreAsciiCase("otf") ) // check for TTF- and PS-OpenType too
269
0
            bSupported = true;
270
0
    }
271
272
0
    if (bSupported)
273
0
    {
274
        // get number of ttc entries
275
0
        int nLength = CountTTCFonts( aFullPath.getStr() );
276
0
        if (nLength > 0)
277
0
        {
278
0
            SAL_INFO("vcl.fonts", "ttc: " << aFullPath << " contains " << nLength << " fonts");
279
280
0
            for( int i = 0; i < nLength; i++ )
281
0
            {
282
0
                PrintFont aFont;
283
0
                aFont.m_nDirectory         = nDirID;
284
0
                aFont.m_aFontFile          = rFontFile;
285
0
                aFont.m_nCollectionEntry   = i;
286
0
                if (analyzeSfntFile(aFont))
287
0
                    aNewFonts.push_back(aFont);
288
0
            }
289
0
        }
290
0
        else
291
0
        {
292
0
            PrintFont aFont;
293
0
            aFont.m_nDirectory         = nDirID;
294
0
            aFont.m_aFontFile          = rFontFile;
295
0
            aFont.m_nCollectionEntry   = 0;
296
297
            // need to read the font anyway to get aliases inside the font file
298
0
            if (analyzeSfntFile(aFont))
299
0
                aNewFonts.push_back(aFont);
300
0
        }
301
0
    }
302
0
    return aNewFonts;
303
0
}
304
305
fontID PrintFontManager::findFontFileID(int nDirID, const OString& rFontFile, int nFaceIndex, int nVariationIndex) const
306
0
{
307
0
    fontID nID = 0;
308
309
0
    auto set_it = m_aFontFileToFontID.find( rFontFile );
310
0
    if( set_it == m_aFontFileToFontID.end() )
311
0
        return nID;
312
313
0
    for (fontID elem : set_it->second)
314
0
    {
315
0
        auto it = m_aFonts.find(elem);
316
0
        if( it == m_aFonts.end() )
317
0
            continue;
318
0
        const PrintFont& rFont = (*it).second;
319
0
        if (rFont.m_nDirectory == nDirID &&
320
0
            rFont.m_aFontFile == rFontFile &&
321
0
            rFont.m_nCollectionEntry == nFaceIndex &&
322
0
            rFont.m_nVariationEntry == nVariationIndex)
323
0
        {
324
0
            nID = it->first;
325
0
            if (nID)
326
0
                break;
327
0
        }
328
0
    }
329
330
0
    return nID;
331
0
}
332
333
std::vector<fontID> PrintFontManager::findFontFileIDs( int nDirID, const OString& rFontFile ) const
334
0
{
335
0
    std::vector<fontID> aIds;
336
337
0
    auto set_it = m_aFontFileToFontID.find( rFontFile );
338
0
    if( set_it == m_aFontFileToFontID.end() )
339
0
        return aIds;
340
341
0
    for (auto const& elem : set_it->second)
342
0
    {
343
0
        auto it = m_aFonts.find(elem);
344
0
        if( it == m_aFonts.end() )
345
0
            continue;
346
0
        const PrintFont& rFont = (*it).second;
347
0
        if (rFont.m_nDirectory == nDirID &&
348
0
            rFont.m_aFontFile == rFontFile)
349
0
            aIds.push_back(it->first);
350
0
    }
351
352
0
    return aIds;
353
0
}
354
355
namespace {
356
357
OUString analyzeSfntFamilyName(void const * pTTFont)
358
0
{
359
0
    return analyzeSfntName(static_cast<TrueTypeFont const *>(pTTFont), 1, SvtSysLocaleOptions().GetRealUILanguageTag());
360
0
}
361
362
}
363
364
bool PrintFontManager::analyzeSfntFile( PrintFont& rFont ) const
365
0
{
366
0
    bool bSuccess = false;
367
0
    rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
368
0
    OString aFile = getFontFile( rFont );
369
0
    TrueTypeFont* pTTFont = nullptr;
370
371
0
    auto& rDFA = rFont.m_aFontAttributes;
372
0
    rDFA.SetQuality(512);
373
374
0
    auto const e = OpenTTFontFile( aFile.getStr(), rFont.m_nCollectionEntry, &pTTFont );
375
0
    if( e == SFErrCodes::Ok )
376
0
    {
377
0
        TTGlobalFontInfo aInfo;
378
0
        GetTTGlobalFontInfo( pTTFont, & aInfo );
379
380
0
        if (rDFA.GetFamilyName().isEmpty())
381
0
        {
382
0
            OUString aFamily = analyzeSfntFamilyName(pTTFont);
383
0
            if (aFamily.isEmpty())
384
0
            {
385
                 // poor font does not have a family name
386
                 // name it to file name minus the extension
387
0
                 sal_Int32 dotIndex = rFont.m_aFontFile.lastIndexOf('.');
388
0
                 if ( dotIndex == -1 )
389
0
                     dotIndex = rFont.m_aFontFile.getLength();
390
0
                 aFamily = OStringToOUString(rFont.m_aFontFile.subView(0, dotIndex), aEncoding);
391
0
            }
392
393
0
            rDFA.SetFamilyName(aFamily);
394
0
        }
395
396
0
        if( !aInfo.usubfamily.isEmpty() )
397
0
            rDFA.SetStyleName(aInfo.usubfamily);
398
399
0
        rDFA.SetFamilyType(matchFamilyName(rDFA.GetFamilyName()));
400
401
0
        rDFA.SetWeight(AnalyzeTTFWeight(pTTFont));
402
403
0
        switch( aInfo.width )
404
0
        {
405
0
            case FWIDTH_ULTRA_CONDENSED:    rDFA.SetWidthType(WIDTH_ULTRA_CONDENSED); break;
406
0
            case FWIDTH_EXTRA_CONDENSED:    rDFA.SetWidthType(WIDTH_EXTRA_CONDENSED); break;
407
0
            case FWIDTH_CONDENSED:          rDFA.SetWidthType(WIDTH_CONDENSED); break;
408
0
            case FWIDTH_SEMI_CONDENSED:     rDFA.SetWidthType(WIDTH_SEMI_CONDENSED); break;
409
0
            case FWIDTH_SEMI_EXPANDED:      rDFA.SetWidthType(WIDTH_SEMI_EXPANDED); break;
410
0
            case FWIDTH_EXPANDED:           rDFA.SetWidthType(WIDTH_EXPANDED); break;
411
0
            case FWIDTH_EXTRA_EXPANDED:     rDFA.SetWidthType(WIDTH_EXTRA_EXPANDED); break;
412
0
            case FWIDTH_ULTRA_EXPANDED:     rDFA.SetWidthType(WIDTH_ULTRA_EXPANDED); break;
413
414
0
            case FWIDTH_NORMAL:
415
0
            default:                        rDFA.SetWidthType(WIDTH_NORMAL); break;
416
0
        }
417
418
0
        rDFA.SetPitch(aInfo.pitch ? PITCH_FIXED : PITCH_VARIABLE);
419
0
        rDFA.SetItalic(aInfo.italicAngle == 0 ? ITALIC_NONE : (aInfo.italicAngle < 0 ? ITALIC_NORMAL : ITALIC_OBLIQUE));
420
        // #104264# there are fonts that set italic angle 0 although they are
421
        // italic; use macstyle bit here
422
0
        if( aInfo.italicAngle == 0 && (aInfo.macStyle & 2) )
423
0
            rDFA.SetItalic(ITALIC_NORMAL);
424
425
0
        rDFA.SetMicrosoftSymbolEncoded(aInfo.microsoftSymbolEncoded);
426
427
0
        CloseTTFont( pTTFont );
428
0
        bSuccess = true;
429
0
    }
430
0
    else
431
0
        SAL_WARN("vcl.fonts", "Could not OpenTTFont \"" << aFile << "\": " << int(e));
432
433
0
    return bSuccess;
434
0
}
435
436
std::vector<fontID> PrintFontManager::getFontList()
437
39
{
438
39
    std::vector<fontID> aFontIDs;
439
39
    for (auto const& font : m_aFonts)
440
468
        aFontIDs.push_back(font.first);
441
442
39
    return aFontIDs;
443
39
}
444
445
int PrintFontManager::getFontFaceNumber( fontID nFontID ) const
446
468
{
447
468
    int nRet = 0;
448
468
    const PrintFont* pFont = getFont( nFontID );
449
468
    if (pFont)
450
468
    {
451
468
        nRet = pFont->m_nCollectionEntry;
452
468
        if (nRet < 0)
453
0
            nRet = 0;
454
468
    }
455
468
    return nRet;
456
468
}
457
458
int PrintFontManager::getFontFaceVariation( fontID nFontID ) const
459
468
{
460
468
    int nRet = 0;
461
468
    const PrintFont* pFont = getFont( nFontID );
462
468
    if (pFont)
463
468
    {
464
468
        nRet = pFont->m_nVariationEntry;
465
468
        if (nRet < 0)
466
0
            nRet = 0;
467
468
    }
468
468
    return nRet;
469
468
}
470
471
FontFamily PrintFontManager::matchFamilyName( std::u16string_view rFamily )
472
0
{
473
0
    struct family_t {
474
0
        const char*  mpName;
475
0
        sal_uInt16   mnLength;
476
0
        FontFamily   meType;
477
0
    };
478
479
0
#define InitializeClass( p, a ) p, sizeof(p) - 1, a
480
0
    static const family_t pFamilyMatch[] =  {
481
0
        { InitializeClass( "arial",                  FAMILY_SWISS )  },
482
0
        { InitializeClass( "arioso",                 FAMILY_SCRIPT ) },
483
0
        { InitializeClass( "avant garde",            FAMILY_SWISS )  },
484
0
        { InitializeClass( "avantgarde",             FAMILY_SWISS )  },
485
0
        { InitializeClass( "bembo",                  FAMILY_ROMAN )  },
486
0
        { InitializeClass( "bookman",                FAMILY_ROMAN )  },
487
0
        { InitializeClass( "conga",                  FAMILY_ROMAN )  },
488
0
        { InitializeClass( "courier",                FAMILY_MODERN ) },
489
0
        { InitializeClass( "curl",                   FAMILY_SCRIPT ) },
490
0
        { InitializeClass( "fixed",                  FAMILY_MODERN ) },
491
0
        { InitializeClass( "gill",                   FAMILY_SWISS )  },
492
0
        { InitializeClass( "helmet",                 FAMILY_MODERN ) },
493
0
        { InitializeClass( "helvetica",              FAMILY_SWISS )  },
494
0
        { InitializeClass( "international",          FAMILY_MODERN ) },
495
0
        { InitializeClass( "lucida",                 FAMILY_SWISS )  },
496
0
        { InitializeClass( "new century schoolbook", FAMILY_ROMAN )  },
497
0
        { InitializeClass( "palatino",               FAMILY_ROMAN )  },
498
0
        { InitializeClass( "roman",                  FAMILY_ROMAN )  },
499
0
        { InitializeClass( "sans serif",             FAMILY_SWISS )  },
500
0
        { InitializeClass( "sansserif",              FAMILY_SWISS )  },
501
0
        { InitializeClass( "serf",                   FAMILY_ROMAN )  },
502
0
        { InitializeClass( "serif",                  FAMILY_ROMAN )  },
503
0
        { InitializeClass( "times",                  FAMILY_ROMAN )  },
504
0
        { InitializeClass( "utopia",                 FAMILY_ROMAN )  },
505
0
        { InitializeClass( "zapf chancery",          FAMILY_SCRIPT ) },
506
0
        { InitializeClass( "zapfchancery",           FAMILY_SCRIPT ) }
507
0
    };
508
509
0
    OString aFamily = OUStringToOString( rFamily, RTL_TEXTENCODING_ASCII_US );
510
0
    sal_uInt32 nLower = 0;
511
0
    sal_uInt32 nUpper = SAL_N_ELEMENTS(pFamilyMatch);
512
513
0
    while( nLower < nUpper )
514
0
    {
515
0
        sal_uInt32 nCurrent = (nLower + nUpper) / 2;
516
0
        const family_t* pHaystack = pFamilyMatch + nCurrent;
517
0
        sal_Int32  nComparison =
518
0
            rtl_str_compareIgnoreAsciiCase_WithLength
519
0
            (
520
0
             aFamily.getStr(), aFamily.getLength(),
521
0
             pHaystack->mpName, pHaystack->mnLength
522
0
             );
523
524
0
        if( nComparison < 0 )
525
0
            nUpper = nCurrent;
526
0
        else
527
0
            if( nComparison > 0 )
528
0
                nLower = nCurrent + 1;
529
0
            else
530
0
                return pHaystack->meType;
531
0
    }
532
533
0
    return FAMILY_DONTKNOW;
534
0
}
535
536
OString PrintFontManager::getFontFile(const PrintFont& rFont) const
537
468
{
538
468
    std::unordered_map< int, OString >::const_iterator it = m_aAtomToDir.find(rFont.m_nDirectory);
539
    assert(it != m_aAtomToDir.end());
540
468
    OString aPath = it->second + "/" + rFont.m_aFontFile;
541
468
    return aPath;
542
468
}
543
544
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */