Coverage Report

Created: 2026-10-14 09:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/gdi/embeddedfontsmanager.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
10
#include <sal/config.h>
11
12
#include <memory>
13
#include <set>
14
#include <unordered_map>
15
#include <frozen/bits/defines.h>
16
#include <frozen/bits/elsa_std.h>
17
#include <frozen/unordered_set.h>
18
#include <config_folders.h>
19
#include <config_eot.h>
20
21
#include <o3tl/string_view.hxx>
22
#include <o3tl/temporary.hxx>
23
#include <osl/file.hxx>
24
#include <rtl/bootstrap.hxx>
25
#include <rtl/uri.hxx>
26
#include <sal/log.hxx>
27
#include <tools/debug.hxx>
28
#include <vcl/svapp.hxx>
29
#include <vcl/embeddedfontsmanager.hxx>
30
#include <com/sun/star/io/XInputStream.hpp>
31
#include <comphelper/diagnose_ex.hxx>
32
#include <comphelper/interaction.hxx>
33
#include <comphelper/lok.hxx>
34
#include <comphelper/propertyvalue.hxx>
35
#include <comphelper/storagehelper.hxx>
36
#include <unotools/tempfile.hxx>
37
38
#include <font/PhysicalFontFaceCollection.hxx>
39
#include <font/PhysicalFontCollection.hxx>
40
#include <salgdi.hxx>
41
#include <font/TrueTypeFont.hxx>
42
#include <tools/stream.hxx>
43
44
#include <com/sun/star/beans/StringPair.hpp>
45
#include <com/sun/star/document/FontsDisallowEditingRequest.hpp>
46
#include <com/sun/star/task/XInteractionHandler.hpp>
47
#include <com/sun/star/frame/XModel2.hpp>
48
49
#if ENABLE_EOT
50
extern "C"
51
{
52
namespace libeot
53
{
54
#include <libeot/libeot.h>
55
} // namespace libeot
56
} // extern "C"
57
#endif
58
59
using namespace com::sun::star;
60
using namespace vcl;
61
62
namespace
63
{
64
OUString GetStandardEmbeddedFontsRoot()
65
101
{
66
101
    OUString p = u"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}"_ustr;
67
101
    rtl::Bootstrap::expandMacros(p);
68
101
    return p + "/user/temp/embeddedfonts";
69
101
}
70
71
OUString GetEmbeddedFontsRootURL(bool bLOK)
72
101
{
73
101
    OUString p = bLOK ? utl::CreateTempURL(nullptr, true) : GetStandardEmbeddedFontsRoot();
74
    // Drop things like "//" and "/../"; ensure trailing slash
75
101
    (void)osl::FileBase::getAbsoluteFileURL({}, p + "/", p);
76
101
    return p;
77
101
}
78
79
const OUString& GetEmbeddedFontsRoot()
80
249
{
81
249
    static const OUString path = GetEmbeddedFontsRootURL(comphelper::LibreOfficeKit::isActive());
82
249
    return path;
83
249
}
84
85
struct EmbeddedFontData
86
{
87
    OUString familyName;
88
    int refcount = 0;
89
    bool isRestricted = true; // Has restricted permissions, *and* isn't installed locally
90
    bool isActivated = false;
91
};
92
93
// file URL -> EmbeddedFontData
94
std::unordered_map<OUString, EmbeddedFontData> s_EmbeddedFonts;
95
96
void clearDir( const OUString& path )
97
202
{
98
202
    osl::Directory dir( path );
99
202
    if( dir.reset() == osl::Directory::E_None )
100
156
    {
101
452
        for (osl::DirectoryItem item; dir.getNextItem(item) == osl::Directory::E_None;)
102
296
        {
103
296
            osl::FileStatus status( osl_FileStatus_Mask_FileURL );
104
296
            if( item.getFileStatus( status ) == osl::File::E_None )
105
296
                osl::File::remove( status.getFileURL());
106
296
        }
107
156
    }
108
202
}
109
}
110
111
// Returns a URL for a file where to store contents of a given temporary font.
112
// The file may or not may not exist yet, and will be cleaned up automatically as appropriate.
113
// Use activateFonts() to actually enable usage of the font.
114
// static
115
OUString EmbeddedFontsManager::getFileUrlForTemporaryFont(std::u16string_view name, std::u16string_view suffix)
116
4
{
117
4
    OUString filename = OUString::Concat(name) + suffix;
118
119
4
    if (!comphelper::OStorageHelper::IsValidZipEntryFileName(filename, false))
120
0
    {
121
0
        SAL_WARN("vcl.fonts", "Cannot use filename: " << filename << " for temporary font");
122
0
        static int uniqueCounter = 0;
123
0
        filename = "font" + OUString::number(uniqueCounter++) + ".ttf";
124
0
    }
125
126
4
    OUString path = GetEmbeddedFontsRoot() + "fromdocs/";
127
4
    osl::Directory::createPath(path);
128
4
    return path
129
4
           + rtl::Uri::encode(filename, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes,
130
4
                              RTL_TEXTENCODING_UTF8);
131
4
}
132
133
namespace
134
{
135
bool writeFontBytesToFile(osl::File& file, const void* data, sal_uInt64 size)
136
0
{
137
0
    auto bytes = static_cast<const char*>(data);
138
0
    for (sal_uInt64 writtenTotal = 0; writtenTotal < size;)
139
0
    {
140
0
        sal_uInt64 written = 0;
141
0
        switch (file.write(bytes + writtenTotal, size - writtenTotal, written))
142
0
        {
143
0
            case osl::File::E_None:
144
0
                writtenTotal += written;
145
0
                break;
146
0
            case osl::File::E_AGAIN:
147
0
            case osl::File::E_INTR:
148
0
                break;
149
0
            default:
150
0
                SAL_WARN("vcl.fonts", "Writing temporary font file failed");
151
0
                file.close();
152
0
                osl::File::remove(file.getURL());
153
0
                return false;
154
0
        }
155
0
    }
156
0
    if (file.close() != osl::File::E_None)
157
0
    {
158
        // Something failed in delayed writing?
159
0
        SAL_WARN("vcl.fonts", "Writing temporary font file failed");
160
0
        osl::File::remove(file.getURL());
161
0
        return false;
162
0
    }
163
164
0
    return true;
165
0
}
166
167
// Returns actual URL (maybe of an already existing file), or empty string on failure.
168
//
169
// @param name name of the font file
170
OUString writeFontBytesToFile(const std::vector<char>& bytes, std::u16string_view name)
171
0
{
172
0
    OUString url = EmbeddedFontsManager::getFileUrlForTemporaryFont(name, u".ttf");
173
0
    std::optional<osl::File> file(url);
174
0
    auto rc = file->open(osl_File_OpenFlag_Create | osl_File_OpenFlag_Write);
175
176
    // Check if existing file is the same
177
0
    for (int counter = 0; rc == osl::File::E_EXIST;)
178
0
    {
179
0
        if (file->open(osl_File_OpenFlag_Read | osl_File_OpenFlag_NoLock) == osl::File::E_None)
180
0
        {
181
0
            if (sal_uInt64 size; file->getSize(size) == osl::File::E_None && size == bytes.size())
182
0
            {
183
0
                std::vector<char> bytes2(bytes.size());
184
0
                sal_uInt64 readTotal = 0;
185
0
                while (readTotal < bytes.size())
186
0
                {
187
0
                    sal_uInt64 read = 0;
188
0
                    rc = file->read(bytes2.data() + readTotal, bytes.size() - readTotal, read);
189
0
                    if (read == 0)
190
0
                        break;
191
0
                    readTotal += read;
192
0
                }
193
0
                if (rc == osl::File::E_None && bytes2 == bytes)
194
0
                {
195
0
                    return url; // OK, it's the same bytes
196
0
                }
197
0
            }
198
0
        }
199
0
        url = EmbeddedFontsManager::getFileUrlForTemporaryFont(Concat2View(name + OUString::number(counter++)), u".ttf");
200
0
        file.emplace(url);
201
0
        rc = file->open(osl_File_OpenFlag_Create | osl_File_OpenFlag_Write);
202
0
    }
203
204
0
    if (rc != osl::File::E_None)
205
0
        return {};
206
207
0
    if (!writeFontBytesToFile(*file, bytes.data(), bytes.size()))
208
0
        return {};
209
210
0
    return url;
211
0
}
212
213
OUString getFilenameForExport(std::u16string_view familyName, FontFamily family, FontItalic italic,
214
                              FontWeight weight, FontPitch pitch,
215
                              std::u16string_view subFamilyName)
216
144
{
217
144
    OUString filename = OUString::Concat(familyName) + "_" + subFamilyName + "_"
218
144
                        + OUString::number(family) + "_" + OUString::number(italic) + "_"
219
144
                        + OUString::number(weight) + "_" + OUString::number(pitch) + ".ttf";
220
144
    return rtl::Uri::encode(filename, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes,
221
144
                            RTL_TEXTENCODING_UTF8);
222
144
}
223
224
// Check if it's (legally) allowed to embed the font file into a document
225
// (ttf has a flag allowing this). PhysicalFontFace::IsEmbeddable() appears
226
// to have a different meaning (guessing from code, IsSubsettable() might
227
// possibly mean it's ttf, while IsEmbeddable() might mean it's type1).
228
// So just try to open the data as ttf and see.
229
bool sufficientTTFRights(const TrueTypeFont& font,
230
                         EmbeddedFontsManager::FontRights rights)
231
0
{
232
    // https://www.microsoft.com/typography/otspec/os2.htm#fst
233
0
    int copyright = font.getTypeFlags();
234
0
    switch (rights)
235
0
    {
236
0
        case EmbeddedFontsManager::FontRights::ViewingAllowed:
237
            // Embedding not restricted completely.
238
0
            return (copyright & 0x02) != 0x02;
239
0
        case EmbeddedFontsManager::FontRights::EditingAllowed:
240
            // Font is installable or editable.
241
0
            return copyright == 0 || (copyright & 0x08);
242
0
    }
243
0
    return true; // no known restriction
244
0
}
245
246
}
247
248
EmbeddedFontsManager::EmbeddedFontsManager(const uno::Reference<frame::XModel>& xModel)
249
4
    : m_xDocumentModel(xModel)
250
4
{
251
4
}
252
253
EmbeddedFontsManager::~EmbeddedFontsManager()
254
4
{
255
4
    if (m_aAccumulatedFonts.empty())
256
0
        return;
257
258
4
    if (auto xModel2 = m_xDocumentModel.query<frame::XModel2>())
259
0
    {
260
        // Send embedded fonts list into the document's media descriptor.
261
        // This helps to decide if the document is allowed to switch to edit mode.
262
0
        try
263
0
        {
264
0
            uno::Sequence<beans::StringPair> fonts(m_aAccumulatedFonts.size());
265
0
            std::transform(m_aAccumulatedFonts.begin(), m_aAccumulatedFonts.end(), fonts.getArray(),
266
0
                           [](const auto& el) { return beans::StringPair(el.first, el.second); });
267
0
            xModel2->setArgs({ comphelper::makePropertyValue(u"EmbeddedFonts"_ustr, fonts) });
268
            // The following will not get executed, if setArgs throws
269
0
            return;
270
0
        }
271
0
        catch (const uno::Exception&)
272
0
        {
273
0
            DBG_UNHANDLED_EXCEPTION("xmloff.core");
274
0
        }
275
0
    }
276
277
    // Failed to transfer the fonts to the document. Activate them here, discarding restricted.
278
    // They won't be released, so will stay until the application shutdown.
279
4
    activateFonts(m_aAccumulatedFonts, false, {}, o3tl::temporary(bool()));
280
4
}
281
282
void EmbeddedFontsManager::clearTemporaryFontFiles()
283
101
{
284
101
    const OUString& path = GetEmbeddedFontsRoot();
285
101
    clearDir( path + "fromdocs/" );
286
101
    clearDir( path + "fromsystem/" );
287
101
}
288
289
bool EmbeddedFontsManager::addEmbeddedFont( const uno::Reference< io::XInputStream >& stream, const OUString& fontName,
290
    std::u16string_view extra, std::vector< unsigned char > const & key, bool eot,
291
    bool bSubsetted )
292
0
{
293
0
    size_t keyPos = 0;
294
0
    std::vector< char > fontData;
295
0
    fontData.reserve( 1000000 );
296
0
    for(;;)
297
0
    {
298
0
        uno::Sequence< sal_Int8 > buffer;
299
0
        sal_uInt64 read = stream->readBytes( buffer, 1024 );
300
0
        auto bufferRange = asNonConstRange(buffer);
301
0
        for( sal_uInt64 pos = 0;
302
0
             pos < read && keyPos < key.size();
303
0
             ++pos )
304
0
            bufferRange[ pos ] ^= key[ keyPos++ ];
305
0
        fontData.insert( fontData.end(), buffer.getConstArray(), buffer.getConstArray() + read );
306
0
        if( read <= 0 )
307
0
            break;
308
0
    }
309
0
    bool sufficientFontRights(false);
310
0
#if ENABLE_EOT
311
0
    if( eot )
312
0
    {
313
0
        unsigned uncompressedFontSize = 0;
314
0
        unsigned char *nakedPointerToUncompressedFont = nullptr;
315
0
        libeot::EOTMetadata eotMetadata;
316
0
        libeot::EOTError uncompressError =
317
0
            libeot::EOT2ttf_buffer( reinterpret_cast<unsigned char *>(fontData.data()), fontData.size(), &eotMetadata, &nakedPointerToUncompressedFont, &uncompressedFontSize );
318
0
        std::shared_ptr<unsigned char> uncompressedFont( nakedPointerToUncompressedFont, libeot::EOTfreeBuffer );
319
0
        if( uncompressError != libeot::EOT_SUCCESS )
320
0
        {
321
0
            SAL_WARN( "vcl.fonts", "Failed to uncompress font" );
322
0
            return false;
323
0
        }
324
0
        fontData.clear();
325
0
        fontData.insert(fontData.end(), reinterpret_cast<char*>(uncompressedFont.get()),
326
0
                        reinterpret_cast<char*>(uncompressedFont.get() + uncompressedFontSize));
327
0
        sufficientFontRights = libeot::EOTcanLegallyEdit( &eotMetadata );
328
0
        libeot::EOTfreeMetadata( &eotMetadata );
329
0
    }
330
0
#endif
331
332
0
    TrueTypeFont font(fontData.data(), fontData.size(), 0);
333
0
    if (!font.isValid())
334
0
        return false;
335
336
0
    if( !eot )
337
0
    {
338
0
        sufficientFontRights = sufficientTTFRights(font, FontRights::EditingAllowed);
339
0
    }
340
341
0
    if (bSubsetted)
342
0
    {
343
0
        sal_uInt32 nGlyphs = font.countNonEmptyGlyphs();
344
        // Check if it has reasonable amount of glyphs, set the limit to the number of glyphs in the
345
        // English alphabet (not differentiating lowercase and uppercase).
346
0
        if (nGlyphs < 26)
347
0
        {
348
0
            SAL_INFO("vcl.fonts", "Ignoring embedded font that only provides " << nGlyphs << " non-empty glyphs");
349
0
            return false;
350
0
        }
351
0
    }
352
353
0
    OUString fileUrl = writeFontBytesToFile(fontData, Concat2View(fontName + extra));
354
0
    if (fileUrl.isEmpty())
355
0
        return false;
356
357
    // tdf#172647: register the font under the typographic family name that the document
358
    // model uses once legacy names are converted; the document-declared name can be a
359
    // legacy full name (e.g. "Manbow Solid" for the "Manbow" family's "Solid" style).
360
0
    OUString registerName = font.getTypographicFamilyName();
361
0
    if (registerName.isEmpty())
362
0
        registerName = fontName;
363
364
0
    return addEmbeddedFont(fileUrl, registerName, sufficientFontRights);
365
0
}
366
367
bool EmbeddedFontsManager::addEmbeddedFont( const OUString& fileUrl, const OUString& fontName, bool sufficientFontRights )
368
4
{
369
370
4
    bool bRestricted = false;
371
    // Register  it / increase its refcount in s_EmbeddedFonts
372
4
    {
373
4
        DBG_TESTSOLARMUTEX();
374
4
        auto& rData = s_EmbeddedFonts[fileUrl];
375
4
        if (rData.refcount == 0)
376
4
        {
377
4
            rData.familyName = fontName;
378
            // Check if the font is already installed on system. At this point, we know that this
379
            // restricted font hasn't yet been activated as embedded (rData.refcount == 0); if it
380
            // is already available, it's pre-installed, meaning that there are sufficient rights
381
            // to use the font. You can always edit with the restricted fonts taken from your own
382
            // system.
383
4
            rData.isRestricted = !sufficientFontRights
384
0
                                 && !Application::GetDefaultDevice()->IsFontAvailable(fontName);
385
4
        }
386
4
        assert(rData.familyName == fontName);
387
4
        ++rData.refcount;
388
4
        bRestricted = rData.isRestricted;
389
4
    }
390
391
4
    m_aAccumulatedFonts.emplace_back(fontName, fileUrl);
392
393
    // tdf#172647: activate an unrestricted font as soon as it is embedded, so it is
394
    // enumerable while the document body is still importing. Legacy font names are
395
    // converted to their typographic form at attribute-set time, which needs the
396
    // font present. Restricted fonts stay deferred until approved at end of load.
397
4
    if (!bRestricted)
398
4
    {
399
4
        std::vector<std::pair<OUString, OUString>> aOne{ { fontName, fileUrl } };
400
4
        activateFonts(aOne, false, {}, o3tl::temporary(bool()));
401
4
    }
402
403
4
    return true;
404
4
}
405
406
namespace
407
{
408
    struct UpdateFontsGuard
409
    {
410
        UpdateFontsGuard()
411
4
        {
412
4
            OutputDevice::ImplClearAllFontData(true);
413
4
        }
414
415
        ~UpdateFontsGuard()
416
4
        {
417
4
            OutputDevice::ImplRefreshAllFontData(true);
418
4
        }
419
    };
420
}
421
422
void EmbeddedFontsManager::activateFonts(std::vector<std::pair<OUString, OUString>>& fonts,
423
                                        bool silentlyAllowRestrictedFonts,
424
                                        const uno::Reference<task::XInteractionHandler>& xHandler,
425
                                        bool& activatedRestrictedFonts)
426
8
{
427
8
    activatedRestrictedFonts = false;
428
8
    if (fonts.empty())
429
0
        return;
430
8
    std::vector<std::pair<OUString, OUString>> temp;
431
432
8
    {
433
8
        DBG_TESTSOLARMUTEX();
434
        // Handle restricted fonts
435
16
        for (auto it1 = fonts.begin(); it1 != fonts.end();)
436
8
        {
437
8
            auto it2 = s_EmbeddedFonts.find(it1->second);
438
8
            if (it2 == s_EmbeddedFonts.end())
439
0
            {
440
0
                SAL_WARN("vcl.fonts", "Trying to activate a font not in s_EmbeddedFonts");
441
0
                it1 = fonts.erase(it1);
442
0
                continue;
443
0
            }
444
8
            assert(it2->second.familyName == it1->first);
445
446
8
            if (!silentlyAllowRestrictedFonts && it2->second.isRestricted)
447
0
            {
448
0
                temp.push_back(*it1);
449
0
                it1 = fonts.erase(it1);
450
0
                continue;
451
0
            }
452
453
8
            ++it1;
454
8
        }
455
8
    }
456
457
8
    if (!temp.empty())
458
0
    {
459
0
        bool allowRestrictedFonts = false;
460
0
        if (xHandler)
461
0
        {
462
0
            std::set<OUString> filteredFamilies; // families can repeat, e.g. for bold/italic
463
0
            for (const auto& pair : temp)
464
0
                filteredFamilies.insert(pair.first);
465
0
            OUStringBuffer fontlist;
466
0
            for (const auto& family : filteredFamilies)
467
0
                fontlist.append("\n" + family);
468
0
            rtl::Reference pRequest(new comphelper::OInteractionRequest(
469
0
                uno::Any(document::FontsDisallowEditingRequest({}, {}, fontlist.toString()))));
470
0
            rtl::Reference pApprove(new comphelper::OInteractionApprove);
471
0
            pRequest->addContinuation(pApprove);
472
0
            pRequest->addContinuation(new comphelper::OInteractionDisapprove);
473
0
            xHandler->handle(pRequest);
474
0
            allowRestrictedFonts = pApprove->wasSelected();
475
0
        }
476
0
        if (allowRestrictedFonts)
477
0
        {
478
0
            activatedRestrictedFonts = true;
479
0
            fonts.insert(fonts.end(), temp.begin(), temp.end());
480
0
        }
481
0
        else
482
0
        {
483
0
            releaseFonts(temp);
484
0
        }
485
0
        temp.clear();
486
0
    }
487
488
8
    {
489
8
        DBG_TESTSOLARMUTEX();
490
        // Only activate fonts that need activation. It goes after restricted fonts handling,
491
        // because we must ask user about a second document embedding the same restricted font.
492
        // We do not remove from fonts: the unlocking must happen only when the document is closed,
493
        // so that necessary fonts are not unregistered.
494
8
        for (const auto& pair : fonts)
495
8
        {
496
8
            auto it = s_EmbeddedFonts.find(pair.second);
497
            // At this point, we must find a match: neither releaseFonts above, nor other possible
498
            // intermediate changes of s_EmbeddedFonts must not remove our locked entries
499
8
            assert(it != s_EmbeddedFonts.end());
500
8
            if (!it->second.isActivated)
501
4
            {
502
4
                it->second.isActivated = true;
503
4
                temp.push_back(pair);
504
4
            }
505
8
        }
506
8
    }
507
508
8
    if (temp.empty())
509
4
        return;
510
511
4
    UpdateFontsGuard aUpdateFontsGuard;
512
4
    OutputDevice *pDevice = Application::GetDefaultDevice();
513
4
    for (const auto& [ fontName, fileUrl ] : temp)
514
4
        pDevice->AddTempDevFont(fileUrl, fontName);
515
4
}
516
517
void EmbeddedFontsManager::releaseFonts(const std::vector<std::pair<OUString, OUString>>& fonts)
518
157k
{
519
    // Background save process: avoid deletion of files created by the parent
520
157k
    if (comphelper::LibreOfficeKit::isForkedChild())
521
0
        return;
522
157k
    std::vector<std::pair<OUString, OUString>> unregister;
523
157k
    if (!fonts.empty())
524
0
    {
525
0
        DBG_TESTSOLARMUTEX();
526
0
        for (const auto& pair : fonts)
527
0
        {
528
0
            auto it = s_EmbeddedFonts.find(pair.second);
529
0
            if (it == s_EmbeddedFonts.end())
530
0
            {
531
0
                SAL_WARN("vcl.fonts", "Trying to release a font that wasn't locked?");
532
0
                continue;
533
0
            }
534
0
            assert(it->second.familyName == pair.first);
535
536
0
            --it->second.refcount;
537
0
            if (it->second.refcount == 0)
538
0
            {
539
0
                unregister.emplace_back(pair);
540
541
0
                s_EmbeddedFonts.erase(it);
542
0
            }
543
0
        }
544
0
    }
545
546
157k
    if (unregister.empty())
547
157k
        return;
548
549
0
    OutputDevice* pDevice = Application::GetDefaultDevice();
550
0
    for (const auto& [ family, url ] : unregister)
551
0
    {
552
0
        if (pDevice->RemoveTempDevFont(url, family))
553
0
            osl::File::remove(url);
554
0
    }
555
556
0
    OutputDevice::ImplUpdateAllFontData(true);
557
0
}
558
559
// static
560
bool EmbeddedFontsManager::isEmbeddedAndRestricted(std::u16string_view familyName)
561
1.94M
{
562
1.94M
    DBG_TESTSOLARMUTEX();
563
1.94M
    for (const auto& pair : s_EmbeddedFonts)
564
16.2k
    {
565
16.2k
        if (pair.second.familyName == familyName && pair.second.isRestricted)
566
0
            return true;
567
16.2k
    }
568
1.94M
    return false;
569
1.94M
}
570
571
bool EmbeddedFontsManager::analyzeTTF(const void* data, tools::Long size, FontWeight& weight)
572
4
{
573
4
    TrueTypeFont font(data, size, 0);
574
4
    if (!font.isValid())
575
0
        return false;
576
577
4
    weight = font.getFontWeight();
578
579
4
    return true;
580
4
}
581
582
OUString EmbeddedFontsManager::fontFileUrl( std::u16string_view familyName, FontFamily family, FontItalic italic,
583
    FontWeight weight, FontPitch pitch, FontRights rights, std::u16string_view subFamilyName )
584
144
{
585
    // Do not embed restricted fonts not installed locally.
586
144
    if (isEmbeddedAndRestricted(familyName))
587
0
        return {};
588
589
144
    OUString path = GetEmbeddedFontsRoot() + "fromsystem/";
590
144
    osl::Directory::createPath( path );
591
144
    OUString url = path + getFilenameForExport(familyName, family, italic, weight, pitch, subFamilyName);
592
144
    if( osl::File( url ).open( osl_File_OpenFlag_Read ) == osl::File::E_None ) // = exists()
593
0
    {
594
        // File with contents of the font file already exists, assume it's been created by a previous call.
595
0
        return url;
596
0
    }
597
144
    bool ok = false;
598
144
    SalGraphics* graphics = Application::GetDefaultDevice()->GetGraphics();
599
144
    vcl::font::PhysicalFontCollection fonts;
600
144
    graphics->GetDevFontList( &fonts );
601
144
    std::unique_ptr< vcl::font::PhysicalFontFaceCollection > fontInfo( fonts.GetFontFaceCollection());
602
144
    vcl::font::PhysicalFontFace* selected = nullptr;
603
604
    // Maybe we don't find the perfect match for the font. E.G. we have fonts with the same family name
605
    // but not same bold or italic etc...
606
    // In this case we add all the fonts having the family name of the used font:
607
    //  - we store all these fonts in familyNameFonts during loop
608
    //  - if we haven't found the perfect match we store all fonts in familyNameFonts
609
144
    typedef std::vector<vcl::font::PhysicalFontFace*> FontList;
610
144
    FontList familyNameFonts;
611
612
144
    for( int i = 0;
613
1.98k
         i < fontInfo->Count();
614
1.83k
         ++i )
615
1.83k
    {
616
1.83k
        vcl::font::PhysicalFontFace* f = fontInfo->Get( i );
617
        // match the typographic family name or fallback to legacy one.
618
1.83k
        if( f->MatchFamilyName( familyName )
619
1.83k
            || o3tl::equalsIgnoreAsciiCase( familyName, f->GetName( vcl::font::NAME_ID_FONT_FAMILY ) ) )
620
0
        {
621
            // tdf#172647: when a specific style is requested, restrict to faces of that subfamily
622
0
            if (!subFamilyName.empty() && !f->GetStyleName().startsWithIgnoreAsciiCase(subFamilyName))
623
0
                continue;
624
            // Ignore comparing text encodings, at least for now. They cannot be trivially compared
625
            // (e.g. UCS2 and UTF8 are technically the same characters, just have different encoding,
626
            // and just having a unicode font doesn't say what glyphs it actually contains).
627
            // It is possible that it still may be needed to do at least some checks here
628
            // for some encodings (can one font have more font files for more encodings?).
629
0
            if(( family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
630
0
                && ( italic == ITALIC_DONTKNOW || f->GetItalic() == italic )
631
0
                && ( weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
632
0
                && ( pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
633
0
            { // Exact match, return it immediately.
634
0
                selected = f;
635
0
                break;
636
0
            }
637
0
            if(( f->GetFamilyType() == FAMILY_DONTKNOW || family == FAMILY_DONTKNOW || f->GetFamilyType() == family )
638
0
                && ( f->GetItalic() == ITALIC_DONTKNOW || italic == ITALIC_DONTKNOW || f->GetItalic() == italic )
639
0
                && ( f->GetWeight() == WEIGHT_DONTKNOW || weight == WEIGHT_DONTKNOW || f->GetWeight() == weight )
640
0
                && ( f->GetPitch() == PITCH_DONTKNOW || pitch == PITCH_DONTKNOW || f->GetPitch() == pitch ))
641
0
            { // Some fonts specify 'DONTKNOW' for some things, still a good match, if we don't find a better one.
642
0
                selected = f;
643
0
            }
644
            // adding "not perfect match" to familyNameFonts vector
645
0
            familyNameFonts.push_back(f);
646
647
0
        }
648
1.83k
    }
649
650
    // if we have found a perfect match we will add only "selected", otherwise all familyNameFonts
651
144
    FontList fontsToAdd = (selected ? FontList(1, selected) : std::move(familyNameFonts));
652
653
144
    for (vcl::font::PhysicalFontFace* f : fontsToAdd)
654
0
    {
655
0
        if (!selected) { // recalculate file name for "not perfect match"
656
0
            url = path + getFilenameForExport(familyName, f->GetFamilyType(), f->GetItalic(),
657
0
                                              f->GetWeight(), f->GetPitch(), subFamilyName);
658
0
            if (osl::File(url).open(osl_File_OpenFlag_Read) == osl::File::E_None) // = exists()
659
0
            {
660
                // File with contents of the font file already exists, assume it's been created by a previous call.
661
0
                continue;
662
0
            }
663
0
        }
664
0
        auto aFontData(f->GetRawFontData(0));
665
0
        if (!aFontData.empty())
666
0
        {
667
0
            auto data = aFontData.data();
668
0
            auto size = aFontData.size();
669
0
            TrueTypeFont aFont(data, size, 0);
670
0
            if( sufficientTTFRights( aFont, rights ))
671
0
            {
672
0
                osl::File file( url );
673
0
                if( file.open( osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ) == osl::File::E_None )
674
0
                {
675
0
                    ok = writeFontBytesToFile(file, data, size);
676
0
                }
677
0
            }
678
0
        }
679
0
    }
680
144
    return ok ? url : u""_ustr;
681
144
}
682
683
bool EmbeddedFontsManager::isCommonFont(std::u16string_view aFontName)
684
147
{
685
147
    static constexpr auto aCommonFontsList = frozen::make_unordered_set<std::u16string_view>({
686
        // LO Common
687
147
        u"Liberation Sans",
688
147
        u"Liberation Serif",
689
147
        u"Liberation Sans Narrow",
690
147
        u"Liberation Mono",
691
147
        u"Caladea",
692
147
        u"Carlito",
693
147
        u"OpenSymbol",
694
        // MSO
695
147
        u"Times New Roman",
696
147
        u"Arial",
697
147
        u"Arial Narrow",
698
147
        u"Courier New",
699
147
        u"Cambria",
700
147
        u"Calibri",
701
147
        u"Mangal",
702
147
        u"Microsoft YaHei",
703
147
        u"NSimSun",
704
147
        u"Segoe UI",
705
147
        u"SimSun",
706
147
        u"Symbol",
707
147
        u"Tahoma",
708
147
    });
709
710
147
    return aCommonFontsList.contains(aFontName);
711
147
}
712
713
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */