blob: 555dd75fbd60a4231c267d32aef058cce7e68c45 [file] [log] [blame]
reed@google.comb1c65b62013-02-26 15:50:51 +00001/*
benjaminwagner2211a7b2015-12-01 11:12:05 -08002 * Copyright 2009-2015 Google Inc.
reed@google.comb1c65b62013-02-26 15:50:51 +00003 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8/* migrated from chrome/src/skia/ext/SkFontHost_fontconfig_direct.cpp */
9
Mike Kleinc0bd9f92019-04-23 12:05:21 -050010#include "include/core/SkFontStyle.h"
11#include "include/core/SkStream.h"
12#include "include/core/SkString.h"
13#include "include/core/SkTypeface.h"
14#include "include/private/SkFixed.h"
15#include "include/private/SkMutex.h"
16#include "include/private/SkTArray.h"
17#include "include/private/SkTDArray.h"
18#include "include/private/SkTemplates.h"
19#include "src/core/SkAutoMalloc.h"
20#include "src/core/SkBuffer.h"
21#include "src/ports/SkFontConfigInterface_direct.h"
bungemanf20488b2015-07-29 11:49:40 -070022
23#include <fontconfig/fontconfig.h>
24#include <unistd.h>
reed@google.comb1c65b62013-02-26 15:50:51 +000025
bungeman02657072016-05-02 11:54:13 -070026namespace {
27
Ben Wagnerc22288f2020-11-02 12:15:20 -050028// FontConfig was thread antagonistic until 2.10.91 with known thread safety issues until 2.13.93.
29// Before that, lock with a global mutex.
30// See https://bug.skia.org/1497 and cl/339089311 for background.
Herb Derby9c71e7b2019-06-17 14:40:42 -040031static SkMutex& f_c_mutex() {
32 static SkMutex& mutex = *(new SkMutex);
33 return mutex;
34}
bungeman02657072016-05-02 11:54:13 -070035
bungeman02657072016-05-02 11:54:13 -070036struct FCLocker {
Ben Wagnerc22288f2020-11-02 12:15:20 -050037 static constexpr int FontConfigThreadSafeVersion = 21393;
bungeman02657072016-05-02 11:54:13 -070038
Ben Wagnerc22288f2020-11-02 12:15:20 -050039 // Assume FcGetVersion() has always been thread safe.
bungeman02657072016-05-02 11:54:13 -070040 FCLocker() {
Ben Wagnerc22288f2020-11-02 12:15:20 -050041 if (FcGetVersion() < FontConfigThreadSafeVersion) {
Herb Derby9c71e7b2019-06-17 14:40:42 -040042 f_c_mutex().acquire();
bungeman02657072016-05-02 11:54:13 -070043 }
44 }
45
46 ~FCLocker() {
47 AssertHeld();
Ben Wagnerc22288f2020-11-02 12:15:20 -050048 if (FcGetVersion() < FontConfigThreadSafeVersion) {
Herb Derby9c71e7b2019-06-17 14:40:42 -040049 f_c_mutex().release();
bungeman02657072016-05-02 11:54:13 -070050 }
51 }
52
53 static void AssertHeld() { SkDEBUGCODE(
Ben Wagnerc22288f2020-11-02 12:15:20 -050054 if (FcGetVersion() < FontConfigThreadSafeVersion) {
Herb Derby9c71e7b2019-06-17 14:40:42 -040055 f_c_mutex().assertHeld();
bungeman02657072016-05-02 11:54:13 -070056 }
57 ) }
58};
59
Ben Wagnerf33dcc22019-10-03 17:47:02 -040060using UniqueFCConfig = std::unique_ptr<FcConfig, SkFunctionWrapper<decltype(FcConfigDestroy), FcConfigDestroy>>;
61
bungeman02657072016-05-02 11:54:13 -070062} // namespace
63
reed@google.comf55061f2013-04-22 18:48:45 +000064size_t SkFontConfigInterface::FontIdentity::writeToMemory(void* addr) const {
65 size_t size = sizeof(fID) + sizeof(fTTCIndex);
66 size += sizeof(int32_t) + sizeof(int32_t) + sizeof(uint8_t); // weight, width, italic
67 size += sizeof(int32_t) + fString.size(); // store length+data
68 if (addr) {
69 SkWBuffer buffer(addr, size);
70
71 buffer.write32(fID);
72 buffer.write32(fTTCIndex);
73 buffer.write32(fString.size());
74 buffer.write32(fStyle.weight());
75 buffer.write32(fStyle.width());
76 buffer.write8(fStyle.slant());
77 buffer.write(fString.c_str(), fString.size());
78 buffer.padToAlign4();
79
80 SkASSERT(buffer.pos() == size);
81 }
82 return size;
83}
84
85size_t SkFontConfigInterface::FontIdentity::readFromMemory(const void* addr,
86 size_t size) {
87 SkRBuffer buffer(addr, size);
88
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +000089 (void)buffer.readU32(&fID);
90 (void)buffer.readS32(&fTTCIndex);
91 uint32_t strLen, weight, width;
92 (void)buffer.readU32(&strLen);
93 (void)buffer.readU32(&weight);
94 (void)buffer.readU32(&width);
95 uint8_t u8;
96 (void)buffer.readU8(&u8);
97 SkFontStyle::Slant slant = (SkFontStyle::Slant)u8;
reed@google.comf55061f2013-04-22 18:48:45 +000098 fStyle = SkFontStyle(weight, width, slant);
99 fString.resize(strLen);
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +0000100 (void)buffer.read(fString.writable_str(), strLen);
reed@google.comf55061f2013-04-22 18:48:45 +0000101 buffer.skipToAlign4();
102
103 return buffer.pos(); // the actual number of bytes read
104}
105
106#ifdef SK_DEBUG
107static void make_iden(SkFontConfigInterface::FontIdentity* iden) {
108 iden->fID = 10;
109 iden->fTTCIndex = 2;
110 iden->fString.set("Hello world");
111 iden->fStyle = SkFontStyle(300, 6, SkFontStyle::kItalic_Slant);
112}
113
114static void test_writeToMemory(const SkFontConfigInterface::FontIdentity& iden0,
115 int initValue) {
116 SkFontConfigInterface::FontIdentity iden1;
117
halcanary96fcdcc2015-08-27 07:41:13 -0700118 size_t size0 = iden0.writeToMemory(nullptr);
reed@google.comf55061f2013-04-22 18:48:45 +0000119
120 SkAutoMalloc storage(size0);
121 memset(storage.get(), initValue, size0);
122
123 size_t size1 = iden0.writeToMemory(storage.get());
124 SkASSERT(size0 == size1);
125
126 SkASSERT(iden0 != iden1);
127 size_t size2 = iden1.readFromMemory(storage.get(), size1);
128 SkASSERT(size2 == size1);
129 SkASSERT(iden0 == iden1);
130}
131
132static void fontconfiginterface_unittest() {
133 SkFontConfigInterface::FontIdentity iden0, iden1;
134
135 SkASSERT(iden0 == iden1);
136
137 make_iden(&iden0);
138 SkASSERT(iden0 != iden1);
139
140 make_iden(&iden1);
141 SkASSERT(iden0 == iden1);
142
143 test_writeToMemory(iden0, 0);
144 test_writeToMemory(iden0, 0);
145}
146#endif
147
reed@google.com54c69142013-04-09 15:54:52 +0000148///////////////////////////////////////////////////////////////////////////////
149
halcanary96fcdcc2015-08-27 07:41:13 -0700150// Returns the string from the pattern, or nullptr
bungeman875b8f62016-09-16 13:19:49 -0700151static const char* get_string(FcPattern* pattern, const char field[], int index = 0) {
reed@google.come49d67e2013-04-22 18:00:06 +0000152 const char* name;
bungeman875b8f62016-09-16 13:19:49 -0700153 if (FcPatternGetString(pattern, field, index, (FcChar8**)&name) != FcResultMatch) {
halcanary96fcdcc2015-08-27 07:41:13 -0700154 name = nullptr;
reed@google.come49d67e2013-04-22 18:00:06 +0000155 }
156 return name;
157}
158
159///////////////////////////////////////////////////////////////////////////////
160
reed@google.comb1c65b62013-02-26 15:50:51 +0000161namespace {
162
163// Equivalence classes, used to match the Liberation and other fonts
164// with their metric-compatible replacements. See the discussion in
165// GetFontEquivClass().
166enum FontEquivClass
167{
168 OTHER,
169 SANS,
170 SERIF,
171 MONO,
172 SYMBOL,
173 PGOTHIC,
174 GOTHIC,
175 PMINCHO,
176 MINCHO,
177 SIMSUN,
178 NSIMSUN,
179 SIMHEI,
180 PMINGLIU,
181 MINGLIU,
182 PMINGLIUHK,
183 MINGLIUHK,
commit-bot@chromium.orgc4de7762013-03-20 13:33:33 +0000184 CAMBRIA,
bungeman@google.comc526c712013-09-20 17:41:52 +0000185 CALIBRI,
reed@google.comb1c65b62013-02-26 15:50:51 +0000186};
187
188// Match the font name against a whilelist of fonts, returning the equivalence
189// class.
190FontEquivClass GetFontEquivClass(const char* fontname)
191{
192 // It would be nice for fontconfig to tell us whether a given suggested
193 // replacement is a "strong" match (that is, an equivalent font) or
194 // a "weak" match (that is, fontconfig's next-best attempt at finding a
195 // substitute). However, I played around with the fontconfig API for
196 // a good few hours and could not make it reveal this information.
197 //
198 // So instead, we hardcode. Initially this function emulated
199 // /etc/fonts/conf.d/30-metric-aliases.conf
200 // from my Ubuntu system, but we're better off being very conservative.
201
202 // Arimo, Tinos and Cousine are a set of fonts metric-compatible with
203 // Arial, Times New Roman and Courier New with a character repertoire
204 // much larger than Liberation. Note that Cousine is metrically
205 // compatible with Courier New, but the former is sans-serif while
206 // the latter is serif.
207
208
209 struct FontEquivMap {
210 FontEquivClass clazz;
211 const char name[40];
212 };
213
214 static const FontEquivMap kFontEquivMap[] = {
215 { SANS, "Arial" },
216 { SANS, "Arimo" },
217 { SANS, "Liberation Sans" },
218
219 { SERIF, "Times New Roman" },
220 { SERIF, "Tinos" },
221 { SERIF, "Liberation Serif" },
222
223 { MONO, "Courier New" },
224 { MONO, "Cousine" },
225 { MONO, "Liberation Mono" },
226
227 { SYMBOL, "Symbol" },
228 { SYMBOL, "Symbol Neu" },
229
230 // MS Pゴシック
231 { PGOTHIC, "MS PGothic" },
232 { PGOTHIC, "\xef\xbc\xad\xef\xbc\xb3 \xef\xbc\xb0"
233 "\xe3\x82\xb4\xe3\x82\xb7\xe3\x83\x83\xe3\x82\xaf" },
jshin7476cf52014-09-09 12:30:57 -0700234 { PGOTHIC, "Noto Sans CJK JP" },
reed@google.comb1c65b62013-02-26 15:50:51 +0000235 { PGOTHIC, "IPAPGothic" },
236 { PGOTHIC, "MotoyaG04Gothic" },
237
238 // MS ゴシック
239 { GOTHIC, "MS Gothic" },
240 { GOTHIC, "\xef\xbc\xad\xef\xbc\xb3 "
241 "\xe3\x82\xb4\xe3\x82\xb7\xe3\x83\x83\xe3\x82\xaf" },
jshindd4e5682015-05-12 12:08:36 -0700242 { GOTHIC, "Noto Sans Mono CJK JP" },
reed@google.comb1c65b62013-02-26 15:50:51 +0000243 { GOTHIC, "IPAGothic" },
244 { GOTHIC, "MotoyaG04GothicMono" },
245
246 // MS P明朝
247 { PMINCHO, "MS PMincho" },
248 { PMINCHO, "\xef\xbc\xad\xef\xbc\xb3 \xef\xbc\xb0"
249 "\xe6\x98\x8e\xe6\x9c\x9d"},
Jungshik Shincae60da2017-07-18 16:42:32 -0700250 { PMINCHO, "Noto Serif CJK JP" },
reed@google.comb1c65b62013-02-26 15:50:51 +0000251 { PMINCHO, "IPAPMincho" },
252 { PMINCHO, "MotoyaG04Mincho" },
253
254 // MS 明朝
255 { MINCHO, "MS Mincho" },
256 { MINCHO, "\xef\xbc\xad\xef\xbc\xb3 \xe6\x98\x8e\xe6\x9c\x9d" },
Jungshik Shincae60da2017-07-18 16:42:32 -0700257 { MINCHO, "Noto Serif CJK JP" },
reed@google.comb1c65b62013-02-26 15:50:51 +0000258 { MINCHO, "IPAMincho" },
259 { MINCHO, "MotoyaG04MinchoMono" },
260
261 // 宋体
262 { SIMSUN, "Simsun" },
263 { SIMSUN, "\xe5\xae\x8b\xe4\xbd\x93" },
Jungshik Shincae60da2017-07-18 16:42:32 -0700264 { SIMSUN, "Noto Serif CJK SC" },
reed@google.comb1c65b62013-02-26 15:50:51 +0000265 { SIMSUN, "MSung GB18030" },
266 { SIMSUN, "Song ASC" },
267
268 // 新宋体
269 { NSIMSUN, "NSimsun" },
270 { NSIMSUN, "\xe6\x96\xb0\xe5\xae\x8b\xe4\xbd\x93" },
Jungshik Shincae60da2017-07-18 16:42:32 -0700271 { NSIMSUN, "Noto Serif CJK SC" },
reed@google.comb1c65b62013-02-26 15:50:51 +0000272 { NSIMSUN, "MSung GB18030" },
273 { NSIMSUN, "N Song ASC" },
274
275 // 黑体
276 { SIMHEI, "Simhei" },
277 { SIMHEI, "\xe9\xbb\x91\xe4\xbd\x93" },
jshin7476cf52014-09-09 12:30:57 -0700278 { SIMHEI, "Noto Sans CJK SC" },
reed@google.comb1c65b62013-02-26 15:50:51 +0000279 { SIMHEI, "MYingHeiGB18030" },
280 { SIMHEI, "MYingHeiB5HK" },
281
commit-bot@chromium.orgc4de7762013-03-20 13:33:33 +0000282 // 新細明體
283 { PMINGLIU, "PMingLiU"},
284 { PMINGLIU, "\xe6\x96\xb0\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94" },
Jungshik Shincae60da2017-07-18 16:42:32 -0700285 { PMINGLIU, "Noto Serif CJK TC"},
commit-bot@chromium.orgc4de7762013-03-20 13:33:33 +0000286 { PMINGLIU, "MSung B5HK"},
reed@google.comb1c65b62013-02-26 15:50:51 +0000287
commit-bot@chromium.orgc4de7762013-03-20 13:33:33 +0000288 // 細明體
289 { MINGLIU, "MingLiU"},
290 { MINGLIU, "\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94" },
Jungshik Shincae60da2017-07-18 16:42:32 -0700291 { MINGLIU, "Noto Serif CJK TC"},
commit-bot@chromium.orgc4de7762013-03-20 13:33:33 +0000292 { MINGLIU, "MSung B5HK"},
reed@google.comb1c65b62013-02-26 15:50:51 +0000293
commit-bot@chromium.orgc4de7762013-03-20 13:33:33 +0000294 // 新細明體
295 { PMINGLIUHK, "PMingLiU_HKSCS"},
296 { PMINGLIUHK, "\xe6\x96\xb0\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94_HKSCS" },
Jungshik Shincae60da2017-07-18 16:42:32 -0700297 { PMINGLIUHK, "Noto Serif CJK TC"},
commit-bot@chromium.orgc4de7762013-03-20 13:33:33 +0000298 { PMINGLIUHK, "MSung B5HK"},
reed@google.comb1c65b62013-02-26 15:50:51 +0000299
commit-bot@chromium.orgc4de7762013-03-20 13:33:33 +0000300 // 細明體
301 { MINGLIUHK, "MingLiU_HKSCS"},
302 { MINGLIUHK, "\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94_HKSCS" },
Jungshik Shincae60da2017-07-18 16:42:32 -0700303 { MINGLIUHK, "Noto Serif CJK TC"},
commit-bot@chromium.orgc4de7762013-03-20 13:33:33 +0000304 { MINGLIUHK, "MSung B5HK"},
305
306 // Cambria
307 { CAMBRIA, "Cambria" },
308 { CAMBRIA, "Caladea" },
bungeman@google.comc526c712013-09-20 17:41:52 +0000309
310 // Calibri
311 { CALIBRI, "Calibri" },
312 { CALIBRI, "Carlito" },
reed@google.comb1c65b62013-02-26 15:50:51 +0000313 };
314
315 static const size_t kFontCount =
316 sizeof(kFontEquivMap)/sizeof(kFontEquivMap[0]);
317
318 // TODO(jungshik): If this loop turns out to be hot, turn
319 // the array to a static (hash)map to speed it up.
320 for (size_t i = 0; i < kFontCount; ++i) {
321 if (strcasecmp(kFontEquivMap[i].name, fontname) == 0)
322 return kFontEquivMap[i].clazz;
323 }
324 return OTHER;
325}
326
327
328// Return true if |font_a| and |font_b| are visually and at the metrics
329// level interchangeable.
330bool IsMetricCompatibleReplacement(const char* font_a, const char* font_b)
331{
332 FontEquivClass class_a = GetFontEquivClass(font_a);
333 FontEquivClass class_b = GetFontEquivClass(font_b);
334
335 return class_a != OTHER && class_a == class_b;
336}
337
reed@google.comb1c65b62013-02-26 15:50:51 +0000338// Normally we only return exactly the font asked for. In last-resort
339// cases, the request either doesn't specify a font or is one of the
340// basic font names like "Sans", "Serif" or "Monospace". This function
341// tells you whether a given request is for such a fallback.
mtkleinba59a672014-08-04 10:18:27 -0700342bool IsFallbackFontAllowed(const SkString& family) {
reed@google.comb1c65b62013-02-26 15:50:51 +0000343 const char* family_cstr = family.c_str();
mtkleinba59a672014-08-04 10:18:27 -0700344 return family.isEmpty() ||
reed@google.comb1c65b62013-02-26 15:50:51 +0000345 strcasecmp(family_cstr, "sans") == 0 ||
346 strcasecmp(family_cstr, "serif") == 0 ||
347 strcasecmp(family_cstr, "monospace") == 0;
348}
349
reed@google.comb1c65b62013-02-26 15:50:51 +0000350// Retrieves |is_bold|, |is_italic| and |font_family| properties from |font|.
bungeman11a77c62016-04-12 13:45:06 -0700351static int get_int(FcPattern* pattern, const char object[], int missing) {
352 int value;
353 if (FcPatternGetInteger(pattern, object, 0, &value) != FcResultMatch) {
354 return missing;
355 }
356 return value;
357}
358
Ben Wagner6fbafc02018-06-12 17:52:29 -0400359static int map_range(SkScalar value,
360 SkScalar old_min, SkScalar old_max,
361 SkScalar new_min, SkScalar new_max)
bungeman11a77c62016-04-12 13:45:06 -0700362{
363 SkASSERT(old_min < old_max);
364 SkASSERT(new_min <= new_max);
Ben Wagner6fbafc02018-06-12 17:52:29 -0400365 return new_min + ((value - old_min) * (new_max - new_min) / (old_max - old_min));
bungeman11a77c62016-04-12 13:45:06 -0700366}
367
368struct MapRanges {
Ben Wagner6fbafc02018-06-12 17:52:29 -0400369 SkScalar old_val;
370 SkScalar new_val;
bungeman11a77c62016-04-12 13:45:06 -0700371};
372
Ben Wagner6fbafc02018-06-12 17:52:29 -0400373static SkScalar map_ranges(SkScalar val, MapRanges const ranges[], int rangesCount) {
bungeman11a77c62016-04-12 13:45:06 -0700374 // -Inf to [0]
375 if (val < ranges[0].old_val) {
376 return ranges[0].new_val;
377 }
378
379 // Linear from [i] to [i+1]
380 for (int i = 0; i < rangesCount - 1; ++i) {
381 if (val < ranges[i+1].old_val) {
382 return map_range(val, ranges[i].old_val, ranges[i+1].old_val,
383 ranges[i].new_val, ranges[i+1].new_val);
384 }
385 }
386
387 // From [n] to +Inf
388 // if (fcweight < Inf)
389 return ranges[rangesCount-1].new_val;
390}
391
Ben Wagner13197b82017-08-14 11:06:12 -0400392#ifndef FC_WEIGHT_DEMILIGHT
393#define FC_WEIGHT_DEMILIGHT 65
394#endif
395
bungeman11a77c62016-04-12 13:45:06 -0700396static SkFontStyle skfontstyle_from_fcpattern(FcPattern* pattern) {
397 typedef SkFontStyle SkFS;
398
Ben Wagner6fbafc02018-06-12 17:52:29 -0400399 static constexpr MapRanges weightRanges[] = {
400 { FC_WEIGHT_THIN, SkFS::kThin_Weight },
401 { FC_WEIGHT_EXTRALIGHT, SkFS::kExtraLight_Weight },
402 { FC_WEIGHT_LIGHT, SkFS::kLight_Weight },
403 { FC_WEIGHT_DEMILIGHT, 350 },
404 { FC_WEIGHT_BOOK, 380 },
405 { FC_WEIGHT_REGULAR, SkFS::kNormal_Weight },
406 { FC_WEIGHT_MEDIUM, SkFS::kMedium_Weight },
407 { FC_WEIGHT_DEMIBOLD, SkFS::kSemiBold_Weight },
408 { FC_WEIGHT_BOLD, SkFS::kBold_Weight },
409 { FC_WEIGHT_EXTRABOLD, SkFS::kExtraBold_Weight },
410 { FC_WEIGHT_BLACK, SkFS::kBlack_Weight },
411 { FC_WEIGHT_EXTRABLACK, SkFS::kExtraBlack_Weight },
bungeman11a77c62016-04-12 13:45:06 -0700412 };
Ben Wagner6fbafc02018-06-12 17:52:29 -0400413 SkScalar weight = map_ranges(get_int(pattern, FC_WEIGHT, FC_WEIGHT_REGULAR),
414 weightRanges, SK_ARRAY_COUNT(weightRanges));
bungeman11a77c62016-04-12 13:45:06 -0700415
Ben Wagner6fbafc02018-06-12 17:52:29 -0400416 static constexpr MapRanges widthRanges[] = {
417 { FC_WIDTH_ULTRACONDENSED, SkFS::kUltraCondensed_Width },
418 { FC_WIDTH_EXTRACONDENSED, SkFS::kExtraCondensed_Width },
419 { FC_WIDTH_CONDENSED, SkFS::kCondensed_Width },
420 { FC_WIDTH_SEMICONDENSED, SkFS::kSemiCondensed_Width },
421 { FC_WIDTH_NORMAL, SkFS::kNormal_Width },
422 { FC_WIDTH_SEMIEXPANDED, SkFS::kSemiExpanded_Width },
423 { FC_WIDTH_EXPANDED, SkFS::kExpanded_Width },
424 { FC_WIDTH_EXTRAEXPANDED, SkFS::kExtraExpanded_Width },
425 { FC_WIDTH_ULTRAEXPANDED, SkFS::kUltraExpanded_Width },
bungeman11a77c62016-04-12 13:45:06 -0700426 };
Ben Wagner6fbafc02018-06-12 17:52:29 -0400427 SkScalar width = map_ranges(get_int(pattern, FC_WIDTH, FC_WIDTH_NORMAL),
428 widthRanges, SK_ARRAY_COUNT(widthRanges));
bungeman11a77c62016-04-12 13:45:06 -0700429
bungemanb4bb7d82016-04-27 10:21:04 -0700430 SkFS::Slant slant = SkFS::kUpright_Slant;
431 switch (get_int(pattern, FC_SLANT, FC_SLANT_ROMAN)) {
432 case FC_SLANT_ROMAN: slant = SkFS::kUpright_Slant; break;
433 case FC_SLANT_ITALIC : slant = SkFS::kItalic_Slant ; break;
434 case FC_SLANT_OBLIQUE: slant = SkFS::kOblique_Slant; break;
435 default: SkASSERT(false); break;
436 }
bungeman11a77c62016-04-12 13:45:06 -0700437
Ben Wagner6fbafc02018-06-12 17:52:29 -0400438 return SkFontStyle(SkScalarRoundToInt(weight), SkScalarRoundToInt(width), slant);
bungeman11a77c62016-04-12 13:45:06 -0700439}
440
441static void fcpattern_from_skfontstyle(SkFontStyle style, FcPattern* pattern) {
442 typedef SkFontStyle SkFS;
443
Ben Wagner6fbafc02018-06-12 17:52:29 -0400444 static constexpr MapRanges weightRanges[] = {
445 { SkFS::kThin_Weight, FC_WEIGHT_THIN },
446 { SkFS::kExtraLight_Weight, FC_WEIGHT_EXTRALIGHT },
447 { SkFS::kLight_Weight, FC_WEIGHT_LIGHT },
448 { 350, FC_WEIGHT_DEMILIGHT },
449 { 380, FC_WEIGHT_BOOK },
450 { SkFS::kNormal_Weight, FC_WEIGHT_REGULAR },
451 { SkFS::kMedium_Weight, FC_WEIGHT_MEDIUM },
452 { SkFS::kSemiBold_Weight, FC_WEIGHT_DEMIBOLD },
453 { SkFS::kBold_Weight, FC_WEIGHT_BOLD },
454 { SkFS::kExtraBold_Weight, FC_WEIGHT_EXTRABOLD },
455 { SkFS::kBlack_Weight, FC_WEIGHT_BLACK },
456 { SkFS::kExtraBlack_Weight, FC_WEIGHT_EXTRABLACK },
bungeman11a77c62016-04-12 13:45:06 -0700457 };
458 int weight = map_ranges(style.weight(), weightRanges, SK_ARRAY_COUNT(weightRanges));
459
Ben Wagner6fbafc02018-06-12 17:52:29 -0400460 static constexpr MapRanges widthRanges[] = {
461 { SkFS::kUltraCondensed_Width, FC_WIDTH_ULTRACONDENSED },
462 { SkFS::kExtraCondensed_Width, FC_WIDTH_EXTRACONDENSED },
463 { SkFS::kCondensed_Width, FC_WIDTH_CONDENSED },
464 { SkFS::kSemiCondensed_Width, FC_WIDTH_SEMICONDENSED },
465 { SkFS::kNormal_Width, FC_WIDTH_NORMAL },
466 { SkFS::kSemiExpanded_Width, FC_WIDTH_SEMIEXPANDED },
467 { SkFS::kExpanded_Width, FC_WIDTH_EXPANDED },
468 { SkFS::kExtraExpanded_Width, FC_WIDTH_EXTRAEXPANDED },
469 { SkFS::kUltraExpanded_Width, FC_WIDTH_ULTRAEXPANDED },
bungeman11a77c62016-04-12 13:45:06 -0700470 };
471 int width = map_ranges(style.width(), widthRanges, SK_ARRAY_COUNT(widthRanges));
472
bungemanb4bb7d82016-04-27 10:21:04 -0700473 int slant = FC_SLANT_ROMAN;
474 switch (style.slant()) {
475 case SkFS::kUpright_Slant: slant = FC_SLANT_ROMAN ; break;
476 case SkFS::kItalic_Slant : slant = FC_SLANT_ITALIC ; break;
477 case SkFS::kOblique_Slant: slant = FC_SLANT_OBLIQUE; break;
478 default: SkASSERT(false); break;
479 }
480
bungeman11a77c62016-04-12 13:45:06 -0700481 FcPatternAddInteger(pattern, FC_WEIGHT, weight);
bungemanb4bb7d82016-04-27 10:21:04 -0700482 FcPatternAddInteger(pattern, FC_WIDTH , width);
483 FcPatternAddInteger(pattern, FC_SLANT , slant);
bungeman11a77c62016-04-12 13:45:06 -0700484}
485
reed@google.comb1c65b62013-02-26 15:50:51 +0000486} // anonymous namespace
487
488///////////////////////////////////////////////////////////////////////////////
489
reed@google.comf71a2332013-02-27 19:06:30 +0000490#define kMaxFontFamilyLength 2048
drott358f93d2016-08-26 10:08:45 -0700491#ifdef SK_FONT_CONFIG_INTERFACE_ONLY_ALLOW_SFNT_FONTS
492const char* kFontFormatTrueType = "TrueType";
493const char* kFontFormatCFF = "CFF";
494#endif
reed@google.comb1c65b62013-02-26 15:50:51 +0000495
reed@google.comf71a2332013-02-27 19:06:30 +0000496SkFontConfigInterfaceDirect::SkFontConfigInterfaceDirect() {
reed@google.comf55061f2013-04-22 18:48:45 +0000497 SkDEBUGCODE(fontconfiginterface_unittest();)
reed@google.comb1c65b62013-02-26 15:50:51 +0000498}
499
500SkFontConfigInterfaceDirect::~SkFontConfigInterfaceDirect() {
501}
502
benjaminwagner2211a7b2015-12-01 11:12:05 -0800503bool SkFontConfigInterfaceDirect::isAccessible(const char* filename) {
504 if (access(filename, R_OK) != 0) {
505 return false;
506 }
507 return true;
508}
509
510bool SkFontConfigInterfaceDirect::isValidPattern(FcPattern* pattern) {
drott358f93d2016-08-26 10:08:45 -0700511#ifdef SK_FONT_CONFIG_INTERFACE_ONLY_ALLOW_SFNT_FONTS
bungeman875b8f62016-09-16 13:19:49 -0700512 const char* font_format = get_string(pattern, FC_FONTFORMAT);
drott358f93d2016-08-26 10:08:45 -0700513 if (font_format
John Stilesc1c3c6d2020-08-15 23:22:53 -0400514 && 0 != strcmp(font_format, kFontFormatTrueType)
515 && 0 != strcmp(font_format, kFontFormatCFF))
drott358f93d2016-08-26 10:08:45 -0700516 {
517 return false;
518 }
519#endif
520
benjaminwagner2211a7b2015-12-01 11:12:05 -0800521 // fontconfig can also return fonts which are unreadable
bungeman875b8f62016-09-16 13:19:49 -0700522 const char* c_filename = get_string(pattern, FC_FILE);
benjaminwagner2211a7b2015-12-01 11:12:05 -0800523 if (!c_filename) {
524 return false;
525 }
Ben Wagnerf33dcc22019-10-03 17:47:02 -0400526 UniqueFCConfig fcConfig(FcConfigReference(nullptr));
527 const char* sysroot = (const char*)FcConfigGetSysRoot(fcConfig.get());
Tom Andersonb1beba52019-02-21 11:32:18 -0800528 SkString resolvedFilename;
529 if (sysroot) {
530 resolvedFilename = sysroot;
531 resolvedFilename += c_filename;
532 c_filename = resolvedFilename.c_str();
533 }
benjaminwagner2211a7b2015-12-01 11:12:05 -0800534 return this->isAccessible(c_filename);
535}
536
537// Find matching font from |font_set| for the given font family.
538FcPattern* SkFontConfigInterfaceDirect::MatchFont(FcFontSet* font_set,
539 const char* post_config_family,
540 const SkString& family) {
541 // Older versions of fontconfig have a bug where they cannot select
542 // only scalable fonts so we have to manually filter the results.
543 FcPattern* match = nullptr;
544 for (int i = 0; i < font_set->nfont; ++i) {
545 FcPattern* current = font_set->fonts[i];
546 if (this->isValidPattern(current)) {
547 match = current;
548 break;
549 }
550 }
551
552 if (match && !IsFallbackFontAllowed(family)) {
553 bool acceptable_substitute = false;
554 for (int id = 0; id < 255; ++id) {
bungeman875b8f62016-09-16 13:19:49 -0700555 const char* post_match_family = get_string(match, FC_FAMILY, id);
benjaminwagner2211a7b2015-12-01 11:12:05 -0800556 if (!post_match_family)
557 break;
558 acceptable_substitute =
559 (strcasecmp(post_config_family, post_match_family) == 0 ||
560 // Workaround for Issue 12530:
561 // requested family: "Bitstream Vera Sans"
562 // post_config_family: "Arial"
563 // post_match_family: "Bitstream Vera Sans"
564 // -> We should treat this case as a good match.
565 strcasecmp(family.c_str(), post_match_family) == 0) ||
566 IsMetricCompatibleReplacement(family.c_str(), post_match_family);
567 if (acceptable_substitute)
568 break;
569 }
570 if (!acceptable_substitute)
571 return nullptr;
572 }
573
574 return match;
575}
576
bungeman11a77c62016-04-12 13:45:06 -0700577bool SkFontConfigInterfaceDirect::matchFamilyName(const char familyName[],
578 SkFontStyle style,
579 FontIdentity* outIdentity,
580 SkString* outFamilyName,
581 SkFontStyle* outStyle) {
mtkleinba59a672014-08-04 10:18:27 -0700582 SkString familyStr(familyName ? familyName : "");
583 if (familyStr.size() > kMaxFontFamilyLength) {
reed@google.comb1c65b62013-02-26 15:50:51 +0000584 return false;
585 }
586
bungeman02657072016-05-02 11:54:13 -0700587 FCLocker lock;
Ben Wagnerf33dcc22019-10-03 17:47:02 -0400588 UniqueFCConfig fcConfig(FcConfigReference(nullptr));
reed@google.comb1c65b62013-02-26 15:50:51 +0000589 FcPattern* pattern = FcPatternCreate();
590
reed@google.comee619a02013-02-26 22:58:09 +0000591 if (familyName) {
592 FcPatternAddString(pattern, FC_FAMILY, (FcChar8*)familyName);
593 }
bungeman11a77c62016-04-12 13:45:06 -0700594 fcpattern_from_skfontstyle(style, pattern);
bungeman11a77c62016-04-12 13:45:06 -0700595
reed@google.comb1c65b62013-02-26 15:50:51 +0000596 FcPatternAddBool(pattern, FC_SCALABLE, FcTrue);
597
Ben Wagnerf33dcc22019-10-03 17:47:02 -0400598 FcConfigSubstitute(fcConfig.get(), pattern, FcMatchPattern);
reed@google.comb1c65b62013-02-26 15:50:51 +0000599 FcDefaultSubstitute(pattern);
600
601 // Font matching:
602 // CSS often specifies a fallback list of families:
603 // font-family: a, b, c, serif;
604 // However, fontconfig will always do its best to find *a* font when asked
605 // for something so we need a way to tell if the match which it has found is
halcanary96fcdcc2015-08-27 07:41:13 -0700606 // "good enough" for us. Otherwise, we can return nullptr which gets piped up
reed@google.comb1c65b62013-02-26 15:50:51 +0000607 // and lets WebKit know to try the next CSS family name. However, fontconfig
608 // configs allow substitutions (mapping "Arial -> Helvetica" etc) and we
609 // wish to support that.
610 //
611 // Thus, if a specific family is requested we set @family_requested. Then we
612 // record two strings: the family name after config processing and the
613 // family name after resolving. If the two are equal, it's a good match.
614 //
615 // So consider the case where a user has mapped Arial to Helvetica in their
616 // config.
617 // requested family: "Arial"
618 // post_config_family: "Helvetica"
619 // post_match_family: "Helvetica"
620 // -> good match
621 //
622 // and for a missing font:
623 // requested family: "Monaco"
624 // post_config_family: "Monaco"
625 // post_match_family: "Times New Roman"
626 // -> BAD match
627 //
628 // However, we special-case fallback fonts; see IsFallbackFontAllowed().
reed@google.come49d67e2013-04-22 18:00:06 +0000629
bungeman875b8f62016-09-16 13:19:49 -0700630 const char* post_config_family = get_string(pattern, FC_FAMILY);
reed@google.come49d67e2013-04-22 18:00:06 +0000631 if (!post_config_family) {
reed@google.comab792822013-04-23 16:35:09 +0000632 // we can just continue with an empty name, e.g. default font
633 post_config_family = "";
reed@google.come49d67e2013-04-22 18:00:06 +0000634 }
reed@google.comb1c65b62013-02-26 15:50:51 +0000635
636 FcResult result;
Ben Wagnerf33dcc22019-10-03 17:47:02 -0400637 FcFontSet* font_set = FcFontSort(fcConfig.get(), pattern, 0, nullptr, &result);
reed@google.comb1c65b62013-02-26 15:50:51 +0000638 if (!font_set) {
639 FcPatternDestroy(pattern);
640 return false;
641 }
642
benjaminwagner2211a7b2015-12-01 11:12:05 -0800643 FcPattern* match = this->MatchFont(font_set, post_config_family, familyStr);
reed@google.comb1c65b62013-02-26 15:50:51 +0000644 if (!match) {
645 FcPatternDestroy(pattern);
646 FcFontSetDestroy(font_set);
647 return false;
648 }
649
650 FcPatternDestroy(pattern);
651
reed@google.comf71a2332013-02-27 19:06:30 +0000652 // From here out we just extract our results from 'match'
653
bungeman875b8f62016-09-16 13:19:49 -0700654 post_config_family = get_string(match, FC_FAMILY);
reed@google.come49d67e2013-04-22 18:00:06 +0000655 if (!post_config_family) {
reed@google.comf71a2332013-02-27 19:06:30 +0000656 FcFontSetDestroy(font_set);
657 return false;
658 }
659
bungeman875b8f62016-09-16 13:19:49 -0700660 const char* c_filename = get_string(match, FC_FILE);
reed@google.come49d67e2013-04-22 18:00:06 +0000661 if (!c_filename) {
reed@google.comb1c65b62013-02-26 15:50:51 +0000662 FcFontSetDestroy(font_set);
663 return false;
664 }
Ben Wagnerf33dcc22019-10-03 17:47:02 -0400665 const char* sysroot = (const char*)FcConfigGetSysRoot(fcConfig.get());
Tom Andersonb1beba52019-02-21 11:32:18 -0800666 SkString resolvedFilename;
667 if (sysroot) {
668 resolvedFilename = sysroot;
669 resolvedFilename += c_filename;
670 c_filename = resolvedFilename.c_str();
671 }
reed@google.comf71a2332013-02-27 19:06:30 +0000672
bungeman875b8f62016-09-16 13:19:49 -0700673 int face_index = get_int(match, FC_INDEX, 0);
reed@google.comb1c65b62013-02-26 15:50:51 +0000674
reed@google.comb1c65b62013-02-26 15:50:51 +0000675 FcFontSetDestroy(font_set);
676
reed@google.comf71a2332013-02-27 19:06:30 +0000677 if (outIdentity) {
reed@google.com8c9737e2013-03-06 13:06:03 +0000678 outIdentity->fTTCIndex = face_index;
reed@google.come49d67e2013-04-22 18:00:06 +0000679 outIdentity->fString.set(c_filename);
reed@google.comb1c65b62013-02-26 15:50:51 +0000680 }
reed@google.comf71a2332013-02-27 19:06:30 +0000681 if (outFamilyName) {
reed@google.come49d67e2013-04-22 18:00:06 +0000682 outFamilyName->set(post_config_family);
reed@google.comb1c65b62013-02-26 15:50:51 +0000683 }
reed@google.comf71a2332013-02-27 19:06:30 +0000684 if (outStyle) {
bungemaned2edab2016-04-13 05:23:35 -0700685 *outStyle = skfontstyle_from_fcpattern(match);
reed@google.comb1c65b62013-02-26 15:50:51 +0000686 }
reed@google.comee619a02013-02-26 22:58:09 +0000687 return true;
reed@google.comb1c65b62013-02-26 15:50:51 +0000688}
689
bungeman5f213d92015-01-27 05:39:10 -0800690SkStreamAsset* SkFontConfigInterfaceDirect::openStream(const FontIdentity& identity) {
bungemanf93d7112016-09-16 06:24:20 -0700691 return SkStream::MakeFromFile(identity.fString.c_str()).release();
reed@google.comb1c65b62013-02-26 15:50:51 +0000692}