/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: */ |