2 * Copyright 2009-2015 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 /* migrated from chrome/src/skia/ext/SkFontHost_fontconfig_direct.cpp */
10 #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"
23 #include <fontconfig/fontconfig.h>
28 // 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.
31 static SkMutex& f_c_mutex() {
32 static SkMutex& mutex = *(new SkMutex);
37 inline static constexpr int FontConfigThreadSafeVersion = 21393;
39 // Assume FcGetVersion() has always been thread safe.
41 if (FcGetVersion() < FontConfigThreadSafeVersion) {
42 f_c_mutex().acquire();
48 if (FcGetVersion() < FontConfigThreadSafeVersion) {
49 f_c_mutex().release();
53 static void AssertHeld() { SkDEBUGCODE(
54 if (FcGetVersion() < FontConfigThreadSafeVersion) {
55 f_c_mutex().assertHeld();
60 using UniqueFCConfig = std::unique_ptr<FcConfig, SkFunctionWrapper<decltype(FcConfigDestroy), FcConfigDestroy>>;
64 size_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
69 SkWBuffer buffer(addr, size);
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());
80 SkASSERT(buffer.pos() == size);
85 size_t SkFontConfigInterface::FontIdentity::readFromMemory(const void* addr,
87 SkRBuffer buffer(addr, size);
89 (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);
96 (void)buffer.readU8(&u8);
97 SkFontStyle::Slant slant = (SkFontStyle::Slant)u8;
98 fStyle = SkFontStyle(weight, width, slant);
99 fString.resize(strLen);
100 (void)buffer.read(fString.writable_str(), strLen);
101 buffer.skipToAlign4();
103 return buffer.pos(); // the actual number of bytes read
107 static void make_iden(SkFontConfigInterface::FontIdentity* iden) {
110 iden->fString.set("Hello world");
111 iden->fStyle = SkFontStyle(300, 6, SkFontStyle::kItalic_Slant);
114 static void test_writeToMemory(const SkFontConfigInterface::FontIdentity& iden0,
116 SkFontConfigInterface::FontIdentity iden1;
118 size_t size0 = iden0.writeToMemory(nullptr);
120 SkAutoMalloc storage(size0);
121 memset(storage.get(), initValue, size0);
123 size_t size1 = iden0.writeToMemory(storage.get());
124 SkASSERT(size0 == size1);
126 SkASSERT(iden0 != iden1);
127 size_t size2 = iden1.readFromMemory(storage.get(), size1);
128 SkASSERT(size2 == size1);
129 SkASSERT(iden0 == iden1);
132 static void fontconfiginterface_unittest() {
133 SkFontConfigInterface::FontIdentity iden0, iden1;
135 SkASSERT(iden0 == iden1);
138 SkASSERT(iden0 != iden1);
141 SkASSERT(iden0 == iden1);
143 test_writeToMemory(iden0, 0);
144 test_writeToMemory(iden0, 0);
148 ///////////////////////////////////////////////////////////////////////////////
150 // Returns the string from the pattern, or nullptr
151 static const char* get_string(FcPattern* pattern, const char field[], int index = 0) {
153 if (FcPatternGetString(pattern, field, index, (FcChar8**)&name) != FcResultMatch) {
159 ///////////////////////////////////////////////////////////////////////////////
163 // Equivalence classes, used to match the Liberation and other fonts
164 // with their metric-compatible replacements. See the discussion in
165 // GetFontEquivClass().
188 // Match the font name against a whilelist of fonts, returning the equivalence
190 FontEquivClass GetFontEquivClass(const char* fontname)
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.
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.
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.
209 struct FontEquivMap {
210 FontEquivClass clazz;
214 static const FontEquivMap kFontEquivMap[] = {
217 { SANS, "Liberation Sans" },
219 { SERIF, "Times New Roman" },
221 { SERIF, "Liberation Serif" },
223 { MONO, "Courier New" },
225 { MONO, "Liberation Mono" },
227 { SYMBOL, "Symbol" },
228 { SYMBOL, "Symbol Neu" },
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" },
234 { PGOTHIC, "Noto Sans CJK JP" },
235 { PGOTHIC, "IPAPGothic" },
236 { PGOTHIC, "MotoyaG04Gothic" },
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" },
242 { GOTHIC, "Noto Sans Mono CJK JP" },
243 { GOTHIC, "IPAGothic" },
244 { GOTHIC, "MotoyaG04GothicMono" },
247 { PMINCHO, "MS PMincho" },
248 { PMINCHO, "\xef\xbc\xad\xef\xbc\xb3 \xef\xbc\xb0"
249 "\xe6\x98\x8e\xe6\x9c\x9d"},
250 { PMINCHO, "Noto Serif CJK JP" },
251 { PMINCHO, "IPAPMincho" },
252 { PMINCHO, "MotoyaG04Mincho" },
255 { MINCHO, "MS Mincho" },
256 { MINCHO, "\xef\xbc\xad\xef\xbc\xb3 \xe6\x98\x8e\xe6\x9c\x9d" },
257 { MINCHO, "Noto Serif CJK JP" },
258 { MINCHO, "IPAMincho" },
259 { MINCHO, "MotoyaG04MinchoMono" },
262 { SIMSUN, "Simsun" },
263 { SIMSUN, "\xe5\xae\x8b\xe4\xbd\x93" },
264 { SIMSUN, "Noto Serif CJK SC" },
265 { SIMSUN, "MSung GB18030" },
266 { SIMSUN, "Song ASC" },
269 { NSIMSUN, "NSimsun" },
270 { NSIMSUN, "\xe6\x96\xb0\xe5\xae\x8b\xe4\xbd\x93" },
271 { NSIMSUN, "Noto Serif CJK SC" },
272 { NSIMSUN, "MSung GB18030" },
273 { NSIMSUN, "N Song ASC" },
276 { SIMHEI, "Simhei" },
277 { SIMHEI, "\xe9\xbb\x91\xe4\xbd\x93" },
278 { SIMHEI, "Noto Sans CJK SC" },
279 { SIMHEI, "MYingHeiGB18030" },
280 { SIMHEI, "MYingHeiB5HK" },
283 { PMINGLIU, "PMingLiU"},
284 { PMINGLIU, "\xe6\x96\xb0\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94" },
285 { PMINGLIU, "Noto Serif CJK TC"},
286 { PMINGLIU, "MSung B5HK"},
289 { MINGLIU, "MingLiU"},
290 { MINGLIU, "\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94" },
291 { MINGLIU, "Noto Serif CJK TC"},
292 { MINGLIU, "MSung B5HK"},
295 { PMINGLIUHK, "PMingLiU_HKSCS"},
296 { PMINGLIUHK, "\xe6\x96\xb0\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94_HKSCS" },
297 { PMINGLIUHK, "Noto Serif CJK TC"},
298 { PMINGLIUHK, "MSung B5HK"},
301 { MINGLIUHK, "MingLiU_HKSCS"},
302 { MINGLIUHK, "\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94_HKSCS" },
303 { MINGLIUHK, "Noto Serif CJK TC"},
304 { MINGLIUHK, "MSung B5HK"},
307 { CAMBRIA, "Cambria" },
308 { CAMBRIA, "Caladea" },
311 { CALIBRI, "Calibri" },
312 { CALIBRI, "Carlito" },
315 static const size_t kFontCount =
316 sizeof(kFontEquivMap)/sizeof(kFontEquivMap[0]);
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;
328 // Return true if |font_a| and |font_b| are visually and at the metrics
329 // level interchangeable.
330 bool IsMetricCompatibleReplacement(const char* font_a, const char* font_b)
332 FontEquivClass class_a = GetFontEquivClass(font_a);
333 FontEquivClass class_b = GetFontEquivClass(font_b);
335 return class_a != OTHER && class_a == class_b;
338 // 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.
342 bool IsFallbackFontAllowed(const SkString& family) {
343 const char* family_cstr = family.c_str();
344 return family.isEmpty() ||
345 strcasecmp(family_cstr, "sans") == 0 ||
346 strcasecmp(family_cstr, "serif") == 0 ||
347 strcasecmp(family_cstr, "monospace") == 0;
350 // Retrieves |is_bold|, |is_italic| and |font_family| properties from |font|.
351 static int get_int(FcPattern* pattern, const char object[], int missing) {
353 if (FcPatternGetInteger(pattern, object, 0, &value) != FcResultMatch) {
359 static int map_range(SkScalar value,
360 SkScalar old_min, SkScalar old_max,
361 SkScalar new_min, SkScalar new_max)
363 SkASSERT(old_min < old_max);
364 SkASSERT(new_min <= new_max);
365 return new_min + ((value - old_min) * (new_max - new_min) / (old_max - old_min));
373 static SkScalar map_ranges(SkScalar val, MapRanges const ranges[], int rangesCount) {
375 if (val < ranges[0].old_val) {
376 return ranges[0].new_val;
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);
388 // if (fcweight < Inf)
389 return ranges[rangesCount-1].new_val;
392 #ifndef FC_WEIGHT_DEMILIGHT
393 #define FC_WEIGHT_DEMILIGHT 65
396 static SkFontStyle skfontstyle_from_fcpattern(FcPattern* pattern) {
397 typedef SkFontStyle SkFS;
399 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 },
413 SkScalar weight = map_ranges(get_int(pattern, FC_WEIGHT, FC_WEIGHT_REGULAR),
414 weightRanges, SK_ARRAY_COUNT(weightRanges));
416 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 },
427 SkScalar width = map_ranges(get_int(pattern, FC_WIDTH, FC_WIDTH_NORMAL),
428 widthRanges, SK_ARRAY_COUNT(widthRanges));
430 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;
438 return SkFontStyle(SkScalarRoundToInt(weight), SkScalarRoundToInt(width), slant);
441 static void fcpattern_from_skfontstyle(SkFontStyle style, FcPattern* pattern) {
442 typedef SkFontStyle SkFS;
444 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 },
458 int weight = map_ranges(style.weight(), weightRanges, SK_ARRAY_COUNT(weightRanges));
460 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 },
471 int width = map_ranges(style.width(), widthRanges, SK_ARRAY_COUNT(widthRanges));
473 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;
481 FcPatternAddInteger(pattern, FC_WEIGHT, weight);
482 FcPatternAddInteger(pattern, FC_WIDTH , width);
483 FcPatternAddInteger(pattern, FC_SLANT , slant);
486 } // anonymous namespace
488 ///////////////////////////////////////////////////////////////////////////////
490 #define kMaxFontFamilyLength 2048
491 #ifdef SK_FONT_CONFIG_INTERFACE_ONLY_ALLOW_SFNT_FONTS
492 const char* kFontFormatTrueType = "TrueType";
493 const char* kFontFormatCFF = "CFF";
496 SkFontConfigInterfaceDirect::SkFontConfigInterfaceDirect(FcConfig* fc) : fFC(fc)
498 SkDEBUGCODE(fontconfiginterface_unittest();)
501 SkFontConfigInterfaceDirect::~SkFontConfigInterfaceDirect() {
503 FcConfigDestroy(fFC);
507 bool SkFontConfigInterfaceDirect::isAccessible(const char* filename) {
508 if (access(filename, R_OK) != 0) {
514 bool SkFontConfigInterfaceDirect::isValidPattern(FcPattern* pattern) {
515 #ifdef SK_FONT_CONFIG_INTERFACE_ONLY_ALLOW_SFNT_FONTS
516 const char* font_format = get_string(pattern, FC_FONTFORMAT);
518 && 0 != strcmp(font_format, kFontFormatTrueType)
519 && 0 != strcmp(font_format, kFontFormatCFF))
525 // fontconfig can also return fonts which are unreadable
526 const char* c_filename = get_string(pattern, FC_FILE);
532 UniqueFCConfig fcStorage;
534 fcStorage.reset(FcConfigReference(nullptr));
535 fc = fcStorage.get();
538 const char* sysroot = (const char*)FcConfigGetSysRoot(fc);
539 SkString resolvedFilename;
541 resolvedFilename = sysroot;
542 resolvedFilename += c_filename;
543 c_filename = resolvedFilename.c_str();
545 return this->isAccessible(c_filename);
548 // Find matching font from |font_set| for the given font family.
549 FcPattern* SkFontConfigInterfaceDirect::MatchFont(FcFontSet* font_set,
550 const char* post_config_family,
551 const SkString& family) {
552 // Older versions of fontconfig have a bug where they cannot select
553 // only scalable fonts so we have to manually filter the results.
554 FcPattern* match = nullptr;
555 for (int i = 0; i < font_set->nfont; ++i) {
556 FcPattern* current = font_set->fonts[i];
557 if (this->isValidPattern(current)) {
563 if (match && !IsFallbackFontAllowed(family)) {
564 bool acceptable_substitute = false;
565 for (int id = 0; id < 255; ++id) {
566 const char* post_match_family = get_string(match, FC_FAMILY, id);
567 if (!post_match_family)
569 acceptable_substitute =
570 (strcasecmp(post_config_family, post_match_family) == 0 ||
571 // Workaround for Issue 12530:
572 // requested family: "Bitstream Vera Sans"
573 // post_config_family: "Arial"
574 // post_match_family: "Bitstream Vera Sans"
575 // -> We should treat this case as a good match.
576 strcasecmp(family.c_str(), post_match_family) == 0) ||
577 IsMetricCompatibleReplacement(family.c_str(), post_match_family);
578 if (acceptable_substitute)
581 if (!acceptable_substitute)
588 bool SkFontConfigInterfaceDirect::matchFamilyName(const char familyName[],
590 FontIdentity* outIdentity,
591 SkString* outFamilyName,
592 SkFontStyle* outStyle) {
593 SkString familyStr(familyName ? familyName : "");
594 if (familyStr.size() > kMaxFontFamilyLength) {
599 UniqueFCConfig fcStorage;
601 fcStorage.reset(FcConfigReference(nullptr));
602 fc = fcStorage.get();
606 FcPattern* pattern = FcPatternCreate();
609 FcPatternAddString(pattern, FC_FAMILY, (FcChar8*)familyName);
611 fcpattern_from_skfontstyle(style, pattern);
613 FcPatternAddBool(pattern, FC_SCALABLE, FcTrue);
615 FcConfigSubstitute(fc, pattern, FcMatchPattern);
616 FcDefaultSubstitute(pattern);
619 // CSS often specifies a fallback list of families:
620 // font-family: a, b, c, serif;
621 // However, fontconfig will always do its best to find *a* font when asked
622 // for something so we need a way to tell if the match which it has found is
623 // "good enough" for us. Otherwise, we can return nullptr which gets piped up
624 // and lets WebKit know to try the next CSS family name. However, fontconfig
625 // configs allow substitutions (mapping "Arial -> Helvetica" etc) and we
626 // wish to support that.
628 // Thus, if a specific family is requested we set @family_requested. Then we
629 // record two strings: the family name after config processing and the
630 // family name after resolving. If the two are equal, it's a good match.
632 // So consider the case where a user has mapped Arial to Helvetica in their
634 // requested family: "Arial"
635 // post_config_family: "Helvetica"
636 // post_match_family: "Helvetica"
639 // and for a missing font:
640 // requested family: "Monaco"
641 // post_config_family: "Monaco"
642 // post_match_family: "Times New Roman"
645 // However, we special-case fallback fonts; see IsFallbackFontAllowed().
647 const char* post_config_family = get_string(pattern, FC_FAMILY);
648 if (!post_config_family) {
649 // we can just continue with an empty name, e.g. default font
650 post_config_family = "";
654 FcFontSet* font_set = FcFontSort(fc, pattern, 0, nullptr, &result);
656 FcPatternDestroy(pattern);
660 FcPattern* match = this->MatchFont(font_set, post_config_family, familyStr);
662 FcPatternDestroy(pattern);
663 FcFontSetDestroy(font_set);
667 FcPatternDestroy(pattern);
669 // From here out we just extract our results from 'match'
671 post_config_family = get_string(match, FC_FAMILY);
672 if (!post_config_family) {
673 FcFontSetDestroy(font_set);
677 const char* c_filename = get_string(match, FC_FILE);
679 FcFontSetDestroy(font_set);
682 const char* sysroot = (const char*)FcConfigGetSysRoot(fc);
683 SkString resolvedFilename;
685 resolvedFilename = sysroot;
686 resolvedFilename += c_filename;
687 c_filename = resolvedFilename.c_str();
690 int face_index = get_int(match, FC_INDEX, 0);
692 FcFontSetDestroy(font_set);
695 outIdentity->fTTCIndex = face_index;
696 outIdentity->fString.set(c_filename);
699 outFamilyName->set(post_config_family);
702 *outStyle = skfontstyle_from_fcpattern(match);
707 SkStreamAsset* SkFontConfigInterfaceDirect::openStream(const FontIdentity& identity) {
708 return SkStream::MakeFromFile(identity.fString.c_str()).release();