blob: d384f707b1812d629e0481aaf67816899d67b04c [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
reed@google.comf55061f2013-04-22 18:48:45 +000010#include "SkBuffer.h"
bungemanf20488b2015-07-29 11:49:40 -070011#include "SkDataTable.h"
bungeman11a77c62016-04-12 13:45:06 -070012#include "SkFixed.h"
benjaminwagner2211a7b2015-12-01 11:12:05 -080013#include "SkFontConfigInterface_direct.h"
bungemanf20488b2015-07-29 11:49:40 -070014#include "SkFontStyle.h"
mtklein1b249332015-07-07 12:21:21 -070015#include "SkMutex.h"
reed@google.comb1c65b62013-02-26 15:50:51 +000016#include "SkStream.h"
mtkleinba59a672014-08-04 10:18:27 -070017#include "SkString.h"
bungemanf20488b2015-07-29 11:49:40 -070018#include "SkTArray.h"
19#include "SkTDArray.h"
20#include "SkTemplates.h"
21#include "SkTypeface.h"
22#include "SkTypes.h"
23
24#include <fontconfig/fontconfig.h>
25#include <unistd.h>
reed@google.comb1c65b62013-02-26 15:50:51 +000026
reed@google.comf55061f2013-04-22 18:48:45 +000027size_t SkFontConfigInterface::FontIdentity::writeToMemory(void* addr) const {
28 size_t size = sizeof(fID) + sizeof(fTTCIndex);
29 size += sizeof(int32_t) + sizeof(int32_t) + sizeof(uint8_t); // weight, width, italic
30 size += sizeof(int32_t) + fString.size(); // store length+data
31 if (addr) {
32 SkWBuffer buffer(addr, size);
33
34 buffer.write32(fID);
35 buffer.write32(fTTCIndex);
36 buffer.write32(fString.size());
37 buffer.write32(fStyle.weight());
38 buffer.write32(fStyle.width());
39 buffer.write8(fStyle.slant());
40 buffer.write(fString.c_str(), fString.size());
41 buffer.padToAlign4();
42
43 SkASSERT(buffer.pos() == size);
44 }
45 return size;
46}
47
48size_t SkFontConfigInterface::FontIdentity::readFromMemory(const void* addr,
49 size_t size) {
50 SkRBuffer buffer(addr, size);
51
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +000052 (void)buffer.readU32(&fID);
53 (void)buffer.readS32(&fTTCIndex);
54 uint32_t strLen, weight, width;
55 (void)buffer.readU32(&strLen);
56 (void)buffer.readU32(&weight);
57 (void)buffer.readU32(&width);
58 uint8_t u8;
59 (void)buffer.readU8(&u8);
60 SkFontStyle::Slant slant = (SkFontStyle::Slant)u8;
reed@google.comf55061f2013-04-22 18:48:45 +000061 fStyle = SkFontStyle(weight, width, slant);
62 fString.resize(strLen);
commit-bot@chromium.org8f457e32013-11-08 19:22:57 +000063 (void)buffer.read(fString.writable_str(), strLen);
reed@google.comf55061f2013-04-22 18:48:45 +000064 buffer.skipToAlign4();
65
66 return buffer.pos(); // the actual number of bytes read
67}
68
69#ifdef SK_DEBUG
70static void make_iden(SkFontConfigInterface::FontIdentity* iden) {
71 iden->fID = 10;
72 iden->fTTCIndex = 2;
73 iden->fString.set("Hello world");
74 iden->fStyle = SkFontStyle(300, 6, SkFontStyle::kItalic_Slant);
75}
76
77static void test_writeToMemory(const SkFontConfigInterface::FontIdentity& iden0,
78 int initValue) {
79 SkFontConfigInterface::FontIdentity iden1;
80
halcanary96fcdcc2015-08-27 07:41:13 -070081 size_t size0 = iden0.writeToMemory(nullptr);
reed@google.comf55061f2013-04-22 18:48:45 +000082
83 SkAutoMalloc storage(size0);
84 memset(storage.get(), initValue, size0);
85
86 size_t size1 = iden0.writeToMemory(storage.get());
87 SkASSERT(size0 == size1);
88
89 SkASSERT(iden0 != iden1);
90 size_t size2 = iden1.readFromMemory(storage.get(), size1);
91 SkASSERT(size2 == size1);
92 SkASSERT(iden0 == iden1);
93}
94
95static void fontconfiginterface_unittest() {
96 SkFontConfigInterface::FontIdentity iden0, iden1;
97
98 SkASSERT(iden0 == iden1);
99
100 make_iden(&iden0);
101 SkASSERT(iden0 != iden1);
102
103 make_iden(&iden1);
104 SkASSERT(iden0 == iden1);
105
106 test_writeToMemory(iden0, 0);
107 test_writeToMemory(iden0, 0);
108}
109#endif
110
reed@google.com54c69142013-04-09 15:54:52 +0000111///////////////////////////////////////////////////////////////////////////////
112
halcanary96fcdcc2015-08-27 07:41:13 -0700113// Returns the string from the pattern, or nullptr
reed@google.come49d67e2013-04-22 18:00:06 +0000114static const char* get_name(FcPattern* pattern, const char field[],
115 int index = 0) {
116 const char* name;
117 if (FcPatternGetString(pattern, field, index,
118 (FcChar8**)&name) != FcResultMatch) {
halcanary96fcdcc2015-08-27 07:41:13 -0700119 name = nullptr;
reed@google.come49d67e2013-04-22 18:00:06 +0000120 }
121 return name;
122}
123
124///////////////////////////////////////////////////////////////////////////////
125
reed@google.comb1c65b62013-02-26 15:50:51 +0000126namespace {
127
128// Equivalence classes, used to match the Liberation and other fonts
129// with their metric-compatible replacements. See the discussion in
130// GetFontEquivClass().
131enum FontEquivClass
132{
133 OTHER,
134 SANS,
135 SERIF,
136 MONO,
137 SYMBOL,
138 PGOTHIC,
139 GOTHIC,
140 PMINCHO,
141 MINCHO,
142 SIMSUN,
143 NSIMSUN,
144 SIMHEI,
145 PMINGLIU,
146 MINGLIU,
147 PMINGLIUHK,
148 MINGLIUHK,
commit-bot@chromium.orgc4de7762013-03-20 13:33:33 +0000149 CAMBRIA,
bungeman@google.comc526c712013-09-20 17:41:52 +0000150 CALIBRI,
reed@google.comb1c65b62013-02-26 15:50:51 +0000151};
152
153// Match the font name against a whilelist of fonts, returning the equivalence
154// class.
155FontEquivClass GetFontEquivClass(const char* fontname)
156{
157 // It would be nice for fontconfig to tell us whether a given suggested
158 // replacement is a "strong" match (that is, an equivalent font) or
159 // a "weak" match (that is, fontconfig's next-best attempt at finding a
160 // substitute). However, I played around with the fontconfig API for
161 // a good few hours and could not make it reveal this information.
162 //
163 // So instead, we hardcode. Initially this function emulated
164 // /etc/fonts/conf.d/30-metric-aliases.conf
165 // from my Ubuntu system, but we're better off being very conservative.
166
167 // Arimo, Tinos and Cousine are a set of fonts metric-compatible with
168 // Arial, Times New Roman and Courier New with a character repertoire
169 // much larger than Liberation. Note that Cousine is metrically
170 // compatible with Courier New, but the former is sans-serif while
171 // the latter is serif.
172
173
174 struct FontEquivMap {
175 FontEquivClass clazz;
176 const char name[40];
177 };
178
179 static const FontEquivMap kFontEquivMap[] = {
180 { SANS, "Arial" },
181 { SANS, "Arimo" },
182 { SANS, "Liberation Sans" },
183
184 { SERIF, "Times New Roman" },
185 { SERIF, "Tinos" },
186 { SERIF, "Liberation Serif" },
187
188 { MONO, "Courier New" },
189 { MONO, "Cousine" },
190 { MONO, "Liberation Mono" },
191
192 { SYMBOL, "Symbol" },
193 { SYMBOL, "Symbol Neu" },
194
195 // MS Pゴシック
196 { PGOTHIC, "MS PGothic" },
197 { PGOTHIC, "\xef\xbc\xad\xef\xbc\xb3 \xef\xbc\xb0"
198 "\xe3\x82\xb4\xe3\x82\xb7\xe3\x83\x83\xe3\x82\xaf" },
jshin7476cf52014-09-09 12:30:57 -0700199 { PGOTHIC, "Noto Sans CJK JP" },
reed@google.comb1c65b62013-02-26 15:50:51 +0000200 { PGOTHIC, "IPAPGothic" },
201 { PGOTHIC, "MotoyaG04Gothic" },
202
203 // MS ゴシック
204 { GOTHIC, "MS Gothic" },
205 { GOTHIC, "\xef\xbc\xad\xef\xbc\xb3 "
206 "\xe3\x82\xb4\xe3\x82\xb7\xe3\x83\x83\xe3\x82\xaf" },
jshindd4e5682015-05-12 12:08:36 -0700207 { GOTHIC, "Noto Sans Mono CJK JP" },
reed@google.comb1c65b62013-02-26 15:50:51 +0000208 { GOTHIC, "IPAGothic" },
209 { GOTHIC, "MotoyaG04GothicMono" },
210
211 // MS P明朝
212 { PMINCHO, "MS PMincho" },
213 { PMINCHO, "\xef\xbc\xad\xef\xbc\xb3 \xef\xbc\xb0"
214 "\xe6\x98\x8e\xe6\x9c\x9d"},
215 { PMINCHO, "IPAPMincho" },
216 { PMINCHO, "MotoyaG04Mincho" },
217
218 // MS 明朝
219 { MINCHO, "MS Mincho" },
220 { MINCHO, "\xef\xbc\xad\xef\xbc\xb3 \xe6\x98\x8e\xe6\x9c\x9d" },
221 { MINCHO, "IPAMincho" },
222 { MINCHO, "MotoyaG04MinchoMono" },
223
224 // 宋体
225 { SIMSUN, "Simsun" },
226 { SIMSUN, "\xe5\xae\x8b\xe4\xbd\x93" },
227 { SIMSUN, "MSung GB18030" },
228 { SIMSUN, "Song ASC" },
229
230 // 新宋体
231 { NSIMSUN, "NSimsun" },
232 { NSIMSUN, "\xe6\x96\xb0\xe5\xae\x8b\xe4\xbd\x93" },
233 { NSIMSUN, "MSung GB18030" },
234 { NSIMSUN, "N Song ASC" },
235
236 // 黑体
237 { SIMHEI, "Simhei" },
238 { SIMHEI, "\xe9\xbb\x91\xe4\xbd\x93" },
jshin7476cf52014-09-09 12:30:57 -0700239 { SIMHEI, "Noto Sans CJK SC" },
reed@google.comb1c65b62013-02-26 15:50:51 +0000240 { SIMHEI, "MYingHeiGB18030" },
241 { SIMHEI, "MYingHeiB5HK" },
242
commit-bot@chromium.orgc4de7762013-03-20 13:33:33 +0000243 // 新細明體
244 { PMINGLIU, "PMingLiU"},
245 { PMINGLIU, "\xe6\x96\xb0\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94" },
246 { PMINGLIU, "MSung B5HK"},
reed@google.comb1c65b62013-02-26 15:50:51 +0000247
commit-bot@chromium.orgc4de7762013-03-20 13:33:33 +0000248 // 細明體
249 { MINGLIU, "MingLiU"},
250 { MINGLIU, "\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94" },
251 { MINGLIU, "MSung B5HK"},
reed@google.comb1c65b62013-02-26 15:50:51 +0000252
commit-bot@chromium.orgc4de7762013-03-20 13:33:33 +0000253 // 新細明體
254 { PMINGLIUHK, "PMingLiU_HKSCS"},
255 { PMINGLIUHK, "\xe6\x96\xb0\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94_HKSCS" },
256 { PMINGLIUHK, "MSung B5HK"},
reed@google.comb1c65b62013-02-26 15:50:51 +0000257
commit-bot@chromium.orgc4de7762013-03-20 13:33:33 +0000258 // 細明體
259 { MINGLIUHK, "MingLiU_HKSCS"},
260 { MINGLIUHK, "\xe7\xb4\xb0\xe6\x98\x8e\xe9\xab\x94_HKSCS" },
261 { MINGLIUHK, "MSung B5HK"},
262
263 // Cambria
264 { CAMBRIA, "Cambria" },
265 { CAMBRIA, "Caladea" },
bungeman@google.comc526c712013-09-20 17:41:52 +0000266
267 // Calibri
268 { CALIBRI, "Calibri" },
269 { CALIBRI, "Carlito" },
reed@google.comb1c65b62013-02-26 15:50:51 +0000270 };
271
272 static const size_t kFontCount =
273 sizeof(kFontEquivMap)/sizeof(kFontEquivMap[0]);
274
275 // TODO(jungshik): If this loop turns out to be hot, turn
276 // the array to a static (hash)map to speed it up.
277 for (size_t i = 0; i < kFontCount; ++i) {
278 if (strcasecmp(kFontEquivMap[i].name, fontname) == 0)
279 return kFontEquivMap[i].clazz;
280 }
281 return OTHER;
282}
283
284
285// Return true if |font_a| and |font_b| are visually and at the metrics
286// level interchangeable.
287bool IsMetricCompatibleReplacement(const char* font_a, const char* font_b)
288{
289 FontEquivClass class_a = GetFontEquivClass(font_a);
290 FontEquivClass class_b = GetFontEquivClass(font_b);
291
292 return class_a != OTHER && class_a == class_b;
293}
294
reed@google.comb1c65b62013-02-26 15:50:51 +0000295// Normally we only return exactly the font asked for. In last-resort
296// cases, the request either doesn't specify a font or is one of the
297// basic font names like "Sans", "Serif" or "Monospace". This function
298// tells you whether a given request is for such a fallback.
mtkleinba59a672014-08-04 10:18:27 -0700299bool IsFallbackFontAllowed(const SkString& family) {
reed@google.comb1c65b62013-02-26 15:50:51 +0000300 const char* family_cstr = family.c_str();
mtkleinba59a672014-08-04 10:18:27 -0700301 return family.isEmpty() ||
reed@google.comb1c65b62013-02-26 15:50:51 +0000302 strcasecmp(family_cstr, "sans") == 0 ||
303 strcasecmp(family_cstr, "serif") == 0 ||
304 strcasecmp(family_cstr, "monospace") == 0;
305}
306
reed@google.comb1c65b62013-02-26 15:50:51 +0000307// Retrieves |is_bold|, |is_italic| and |font_family| properties from |font|.
bungeman11a77c62016-04-12 13:45:06 -0700308static int get_int(FcPattern* pattern, const char object[], int missing) {
309 int value;
310 if (FcPatternGetInteger(pattern, object, 0, &value) != FcResultMatch) {
311 return missing;
312 }
313 return value;
314}
315
316static int map_range(SkFixed value,
317 SkFixed old_min, SkFixed old_max,
318 SkFixed new_min, SkFixed new_max)
319{
320 SkASSERT(old_min < old_max);
321 SkASSERT(new_min <= new_max);
322 return new_min + SkMulDiv(value - old_min, new_max - new_min, old_max - old_min);
323}
324
325struct MapRanges {
326 SkFixed old_val;
327 SkFixed new_val;
328};
329
330static SkFixed map_ranges_fixed(SkFixed val, MapRanges const ranges[], int rangesCount) {
331 // -Inf to [0]
332 if (val < ranges[0].old_val) {
333 return ranges[0].new_val;
334 }
335
336 // Linear from [i] to [i+1]
337 for (int i = 0; i < rangesCount - 1; ++i) {
338 if (val < ranges[i+1].old_val) {
339 return map_range(val, ranges[i].old_val, ranges[i+1].old_val,
340 ranges[i].new_val, ranges[i+1].new_val);
341 }
342 }
343
344 // From [n] to +Inf
345 // if (fcweight < Inf)
346 return ranges[rangesCount-1].new_val;
347}
348
349static int map_ranges(int val, MapRanges const ranges[], int rangesCount) {
350 return SkFixedRoundToInt(map_ranges_fixed(SkIntToFixed(val), ranges, rangesCount));
351}
352
353template<int n> struct SkTFixed {
354 static_assert(-32768 <= n && n <= 32767, "SkTFixed_n_not_in_range");
355 static const SkFixed value = static_cast<SkFixed>(n << 16);
356};
357
358static SkFontStyle skfontstyle_from_fcpattern(FcPattern* pattern) {
359 typedef SkFontStyle SkFS;
360
361 static const MapRanges weightRanges[] = {
362 { SkTFixed<FC_WEIGHT_THIN>::value, SkTFixed<SkFS::kThin_Weight>::value },
363 { SkTFixed<FC_WEIGHT_EXTRALIGHT>::value, SkTFixed<SkFS::kExtraLight_Weight>::value },
364 { SkTFixed<FC_WEIGHT_LIGHT>::value, SkTFixed<SkFS::kLight_Weight>::value },
365 { SkTFixed<FC_WEIGHT_REGULAR>::value, SkTFixed<SkFS::kNormal_Weight>::value },
366 { SkTFixed<FC_WEIGHT_MEDIUM>::value, SkTFixed<SkFS::kMedium_Weight>::value },
367 { SkTFixed<FC_WEIGHT_DEMIBOLD>::value, SkTFixed<SkFS::kSemiBold_Weight>::value },
368 { SkTFixed<FC_WEIGHT_BOLD>::value, SkTFixed<SkFS::kBold_Weight>::value },
369 { SkTFixed<FC_WEIGHT_EXTRABOLD>::value, SkTFixed<SkFS::kExtraBold_Weight>::value },
370 { SkTFixed<FC_WEIGHT_BLACK>::value, SkTFixed<SkFS::kBlack_Weight>::value },
371 { SkTFixed<FC_WEIGHT_EXTRABLACK>::value, SkTFixed<1000>::value },
372 };
373 int weight = map_ranges(get_int(pattern, FC_WEIGHT, FC_WEIGHT_REGULAR),
374 weightRanges, SK_ARRAY_COUNT(weightRanges));
375
376 static const MapRanges widthRanges[] = {
377 { SkTFixed<FC_WIDTH_ULTRACONDENSED>::value, SkTFixed<SkFS::kUltraCondensed_Width>::value },
378 { SkTFixed<FC_WIDTH_EXTRACONDENSED>::value, SkTFixed<SkFS::kExtraCondensed_Width>::value },
379 { SkTFixed<FC_WIDTH_CONDENSED>::value, SkTFixed<SkFS::kCondensed_Width>::value },
380 { SkTFixed<FC_WIDTH_SEMICONDENSED>::value, SkTFixed<SkFS::kSemiCondensed_Width>::value },
381 { SkTFixed<FC_WIDTH_NORMAL>::value, SkTFixed<SkFS::kNormal_Width>::value },
382 { SkTFixed<FC_WIDTH_SEMIEXPANDED>::value, SkTFixed<SkFS::kSemiExpanded_Width>::value },
383 { SkTFixed<FC_WIDTH_EXPANDED>::value, SkTFixed<SkFS::kExpanded_Width>::value },
384 { SkTFixed<FC_WIDTH_EXTRAEXPANDED>::value, SkTFixed<SkFS::kExtraExpanded_Width>::value },
385 { SkTFixed<FC_WIDTH_ULTRAEXPANDED>::value, SkTFixed<SkFS::kUltaExpanded_Width>::value },
386 };
387 int width = map_ranges(get_int(pattern, FC_WIDTH, FC_WIDTH_NORMAL),
388 widthRanges, SK_ARRAY_COUNT(widthRanges));
389
bungemanb4bb7d82016-04-27 10:21:04 -0700390 SkFS::Slant slant = SkFS::kUpright_Slant;
391 switch (get_int(pattern, FC_SLANT, FC_SLANT_ROMAN)) {
392 case FC_SLANT_ROMAN: slant = SkFS::kUpright_Slant; break;
393 case FC_SLANT_ITALIC : slant = SkFS::kItalic_Slant ; break;
394 case FC_SLANT_OBLIQUE: slant = SkFS::kOblique_Slant; break;
395 default: SkASSERT(false); break;
396 }
bungeman11a77c62016-04-12 13:45:06 -0700397
398 return SkFontStyle(weight, width, slant);
399}
400
401static void fcpattern_from_skfontstyle(SkFontStyle style, FcPattern* pattern) {
402 typedef SkFontStyle SkFS;
403
404 static const MapRanges weightRanges[] = {
405 { SkTFixed<SkFS::kThin_Weight>::value, SkTFixed<FC_WEIGHT_THIN>::value },
406 { SkTFixed<SkFS::kExtraLight_Weight>::value, SkTFixed<FC_WEIGHT_EXTRALIGHT>::value },
407 { SkTFixed<SkFS::kLight_Weight>::value, SkTFixed<FC_WEIGHT_LIGHT>::value },
408 { SkTFixed<SkFS::kNormal_Weight>::value, SkTFixed<FC_WEIGHT_REGULAR>::value },
409 { SkTFixed<SkFS::kMedium_Weight>::value, SkTFixed<FC_WEIGHT_MEDIUM>::value },
410 { SkTFixed<SkFS::kSemiBold_Weight>::value, SkTFixed<FC_WEIGHT_DEMIBOLD>::value },
411 { SkTFixed<SkFS::kBold_Weight>::value, SkTFixed<FC_WEIGHT_BOLD>::value },
412 { SkTFixed<SkFS::kExtraBold_Weight>::value, SkTFixed<FC_WEIGHT_EXTRABOLD>::value },
413 { SkTFixed<SkFS::kBlack_Weight>::value, SkTFixed<FC_WEIGHT_BLACK>::value },
414 { SkTFixed<1000>::value, SkTFixed<FC_WEIGHT_EXTRABLACK>::value },
415 };
416 int weight = map_ranges(style.weight(), weightRanges, SK_ARRAY_COUNT(weightRanges));
417
418 static const MapRanges widthRanges[] = {
419 { SkTFixed<SkFS::kUltraCondensed_Width>::value, SkTFixed<FC_WIDTH_ULTRACONDENSED>::value },
420 { SkTFixed<SkFS::kExtraCondensed_Width>::value, SkTFixed<FC_WIDTH_EXTRACONDENSED>::value },
421 { SkTFixed<SkFS::kCondensed_Width>::value, SkTFixed<FC_WIDTH_CONDENSED>::value },
422 { SkTFixed<SkFS::kSemiCondensed_Width>::value, SkTFixed<FC_WIDTH_SEMICONDENSED>::value },
423 { SkTFixed<SkFS::kNormal_Width>::value, SkTFixed<FC_WIDTH_NORMAL>::value },
424 { SkTFixed<SkFS::kSemiExpanded_Width>::value, SkTFixed<FC_WIDTH_SEMIEXPANDED>::value },
425 { SkTFixed<SkFS::kExpanded_Width>::value, SkTFixed<FC_WIDTH_EXPANDED>::value },
426 { SkTFixed<SkFS::kExtraExpanded_Width>::value, SkTFixed<FC_WIDTH_EXTRAEXPANDED>::value },
427 { SkTFixed<SkFS::kUltaExpanded_Width>::value, SkTFixed<FC_WIDTH_ULTRAEXPANDED>::value },
428 };
429 int width = map_ranges(style.width(), widthRanges, SK_ARRAY_COUNT(widthRanges));
430
bungemanb4bb7d82016-04-27 10:21:04 -0700431 int slant = FC_SLANT_ROMAN;
432 switch (style.slant()) {
433 case SkFS::kUpright_Slant: slant = FC_SLANT_ROMAN ; break;
434 case SkFS::kItalic_Slant : slant = FC_SLANT_ITALIC ; break;
435 case SkFS::kOblique_Slant: slant = FC_SLANT_OBLIQUE; break;
436 default: SkASSERT(false); break;
437 }
438
bungeman11a77c62016-04-12 13:45:06 -0700439 FcPatternAddInteger(pattern, FC_WEIGHT, weight);
bungemanb4bb7d82016-04-27 10:21:04 -0700440 FcPatternAddInteger(pattern, FC_WIDTH , width);
441 FcPatternAddInteger(pattern, FC_SLANT , slant);
bungeman11a77c62016-04-12 13:45:06 -0700442}
443
reed@google.comb1c65b62013-02-26 15:50:51 +0000444} // anonymous namespace
445
446///////////////////////////////////////////////////////////////////////////////
447
reed@google.comf71a2332013-02-27 19:06:30 +0000448#define kMaxFontFamilyLength 2048
reed@google.comb1c65b62013-02-26 15:50:51 +0000449
reed@google.comf71a2332013-02-27 19:06:30 +0000450SkFontConfigInterfaceDirect::SkFontConfigInterfaceDirect() {
reed@google.comf55061f2013-04-22 18:48:45 +0000451 SkAutoMutexAcquire ac(mutex_);
452
reed@google.comb1c65b62013-02-26 15:50:51 +0000453 FcInit();
reed@google.comf55061f2013-04-22 18:48:45 +0000454
455 SkDEBUGCODE(fontconfiginterface_unittest();)
reed@google.comb1c65b62013-02-26 15:50:51 +0000456}
457
458SkFontConfigInterfaceDirect::~SkFontConfigInterfaceDirect() {
459}
460
benjaminwagner2211a7b2015-12-01 11:12:05 -0800461bool SkFontConfigInterfaceDirect::isAccessible(const char* filename) {
462 if (access(filename, R_OK) != 0) {
463 return false;
464 }
465 return true;
466}
467
468bool SkFontConfigInterfaceDirect::isValidPattern(FcPattern* pattern) {
469#ifdef SK_FONT_CONFIG_ONLY_ALLOW_SCALABLE_FONTS
470 FcBool is_scalable;
471 if (FcPatternGetBool(pattern, FC_SCALABLE, 0, &is_scalable) != FcResultMatch
472 || !is_scalable) {
473 return false;
474 }
475#endif
476
477 // fontconfig can also return fonts which are unreadable
478 const char* c_filename = get_name(pattern, FC_FILE);
479 if (!c_filename) {
480 return false;
481 }
482 return this->isAccessible(c_filename);
483}
484
485// Find matching font from |font_set| for the given font family.
486FcPattern* SkFontConfigInterfaceDirect::MatchFont(FcFontSet* font_set,
487 const char* post_config_family,
488 const SkString& family) {
489 // Older versions of fontconfig have a bug where they cannot select
490 // only scalable fonts so we have to manually filter the results.
491 FcPattern* match = nullptr;
492 for (int i = 0; i < font_set->nfont; ++i) {
493 FcPattern* current = font_set->fonts[i];
494 if (this->isValidPattern(current)) {
495 match = current;
496 break;
497 }
498 }
499
500 if (match && !IsFallbackFontAllowed(family)) {
501 bool acceptable_substitute = false;
502 for (int id = 0; id < 255; ++id) {
503 const char* post_match_family = get_name(match, FC_FAMILY, id);
504 if (!post_match_family)
505 break;
506 acceptable_substitute =
507 (strcasecmp(post_config_family, post_match_family) == 0 ||
508 // Workaround for Issue 12530:
509 // requested family: "Bitstream Vera Sans"
510 // post_config_family: "Arial"
511 // post_match_family: "Bitstream Vera Sans"
512 // -> We should treat this case as a good match.
513 strcasecmp(family.c_str(), post_match_family) == 0) ||
514 IsMetricCompatibleReplacement(family.c_str(), post_match_family);
515 if (acceptable_substitute)
516 break;
517 }
518 if (!acceptable_substitute)
519 return nullptr;
520 }
521
522 return match;
523}
524
bungeman11a77c62016-04-12 13:45:06 -0700525bool SkFontConfigInterfaceDirect::matchFamilyName(const char familyName[],
526 SkFontStyle style,
527 FontIdentity* outIdentity,
528 SkString* outFamilyName,
529 SkFontStyle* outStyle) {
mtkleinba59a672014-08-04 10:18:27 -0700530 SkString familyStr(familyName ? familyName : "");
531 if (familyStr.size() > kMaxFontFamilyLength) {
reed@google.comb1c65b62013-02-26 15:50:51 +0000532 return false;
533 }
534
535 SkAutoMutexAcquire ac(mutex_);
536
reed@google.comb1c65b62013-02-26 15:50:51 +0000537 FcPattern* pattern = FcPatternCreate();
538
reed@google.comee619a02013-02-26 22:58:09 +0000539 if (familyName) {
540 FcPatternAddString(pattern, FC_FAMILY, (FcChar8*)familyName);
541 }
bungeman11a77c62016-04-12 13:45:06 -0700542 fcpattern_from_skfontstyle(style, pattern);
bungeman11a77c62016-04-12 13:45:06 -0700543
reed@google.comb1c65b62013-02-26 15:50:51 +0000544 FcPatternAddBool(pattern, FC_SCALABLE, FcTrue);
545
halcanary96fcdcc2015-08-27 07:41:13 -0700546 FcConfigSubstitute(nullptr, pattern, FcMatchPattern);
reed@google.comb1c65b62013-02-26 15:50:51 +0000547 FcDefaultSubstitute(pattern);
548
549 // Font matching:
550 // CSS often specifies a fallback list of families:
551 // font-family: a, b, c, serif;
552 // However, fontconfig will always do its best to find *a* font when asked
553 // for something so we need a way to tell if the match which it has found is
halcanary96fcdcc2015-08-27 07:41:13 -0700554 // "good enough" for us. Otherwise, we can return nullptr which gets piped up
reed@google.comb1c65b62013-02-26 15:50:51 +0000555 // and lets WebKit know to try the next CSS family name. However, fontconfig
556 // configs allow substitutions (mapping "Arial -> Helvetica" etc) and we
557 // wish to support that.
558 //
559 // Thus, if a specific family is requested we set @family_requested. Then we
560 // record two strings: the family name after config processing and the
561 // family name after resolving. If the two are equal, it's a good match.
562 //
563 // So consider the case where a user has mapped Arial to Helvetica in their
564 // config.
565 // requested family: "Arial"
566 // post_config_family: "Helvetica"
567 // post_match_family: "Helvetica"
568 // -> good match
569 //
570 // and for a missing font:
571 // requested family: "Monaco"
572 // post_config_family: "Monaco"
573 // post_match_family: "Times New Roman"
574 // -> BAD match
575 //
576 // However, we special-case fallback fonts; see IsFallbackFontAllowed().
reed@google.come49d67e2013-04-22 18:00:06 +0000577
578 const char* post_config_family = get_name(pattern, FC_FAMILY);
579 if (!post_config_family) {
reed@google.comab792822013-04-23 16:35:09 +0000580 // we can just continue with an empty name, e.g. default font
581 post_config_family = "";
reed@google.come49d67e2013-04-22 18:00:06 +0000582 }
reed@google.comb1c65b62013-02-26 15:50:51 +0000583
584 FcResult result;
585 FcFontSet* font_set = FcFontSort(0, pattern, 0, 0, &result);
586 if (!font_set) {
587 FcPatternDestroy(pattern);
588 return false;
589 }
590
benjaminwagner2211a7b2015-12-01 11:12:05 -0800591 FcPattern* match = this->MatchFont(font_set, post_config_family, familyStr);
reed@google.comb1c65b62013-02-26 15:50:51 +0000592 if (!match) {
593 FcPatternDestroy(pattern);
594 FcFontSetDestroy(font_set);
595 return false;
596 }
597
598 FcPatternDestroy(pattern);
599
reed@google.comf71a2332013-02-27 19:06:30 +0000600 // From here out we just extract our results from 'match'
601
reed@google.come49d67e2013-04-22 18:00:06 +0000602 post_config_family = get_name(match, FC_FAMILY);
603 if (!post_config_family) {
reed@google.comf71a2332013-02-27 19:06:30 +0000604 FcFontSetDestroy(font_set);
605 return false;
606 }
607
reed@google.come49d67e2013-04-22 18:00:06 +0000608 const char* c_filename = get_name(match, FC_FILE);
609 if (!c_filename) {
reed@google.comb1c65b62013-02-26 15:50:51 +0000610 FcFontSetDestroy(font_set);
611 return false;
612 }
reed@google.comf71a2332013-02-27 19:06:30 +0000613
reed@google.comb1c65b62013-02-26 15:50:51 +0000614 int face_index;
615 if (FcPatternGetInteger(match, FC_INDEX, 0, &face_index) != FcResultMatch) {
616 FcFontSetDestroy(font_set);
617 return false;
618 }
619
reed@google.comb1c65b62013-02-26 15:50:51 +0000620 FcFontSetDestroy(font_set);
621
reed@google.comf71a2332013-02-27 19:06:30 +0000622 if (outIdentity) {
reed@google.com8c9737e2013-03-06 13:06:03 +0000623 outIdentity->fTTCIndex = face_index;
reed@google.come49d67e2013-04-22 18:00:06 +0000624 outIdentity->fString.set(c_filename);
reed@google.comb1c65b62013-02-26 15:50:51 +0000625 }
reed@google.comf71a2332013-02-27 19:06:30 +0000626 if (outFamilyName) {
reed@google.come49d67e2013-04-22 18:00:06 +0000627 outFamilyName->set(post_config_family);
reed@google.comb1c65b62013-02-26 15:50:51 +0000628 }
reed@google.comf71a2332013-02-27 19:06:30 +0000629 if (outStyle) {
bungemaned2edab2016-04-13 05:23:35 -0700630 *outStyle = skfontstyle_from_fcpattern(match);
reed@google.comb1c65b62013-02-26 15:50:51 +0000631 }
reed@google.comee619a02013-02-26 22:58:09 +0000632 return true;
reed@google.comb1c65b62013-02-26 15:50:51 +0000633}
634
bungeman5f213d92015-01-27 05:39:10 -0800635SkStreamAsset* SkFontConfigInterfaceDirect::openStream(const FontIdentity& identity) {
reed@google.com8c3f84d2013-03-19 13:34:55 +0000636 return SkStream::NewFromFile(identity.fString.c_str());
reed@google.comb1c65b62013-02-26 15:50:51 +0000637}
reed@google.com027fd202013-04-19 20:45:30 +0000638
639///////////////////////////////////////////////////////////////////////////////
640
reed@google.com027fd202013-04-19 20:45:30 +0000641static bool find_name(const SkTDArray<const char*>& list, const char* str) {
642 int count = list.count();
643 for (int i = 0; i < count; ++i) {
644 if (!strcmp(list[i], str)) {
645 return true;
646 }
647 }
648 return false;
649}
650
651SkDataTable* SkFontConfigInterfaceDirect::getFamilyNames() {
reed@google.comf55061f2013-04-22 18:48:45 +0000652 SkAutoMutexAcquire ac(mutex_);
653
reed@google.com027fd202013-04-19 20:45:30 +0000654 FcPattern* pat = FcPatternCreate();
bungeman@google.com02dd6882013-07-22 14:10:08 +0000655 SkAutoTCallVProc<FcPattern, FcPatternDestroy> autoDestroyPat(pat);
halcanary96fcdcc2015-08-27 07:41:13 -0700656 if (nullptr == pat) {
657 return nullptr;
bungeman@google.com02dd6882013-07-22 14:10:08 +0000658 }
659
660 FcObjectSet* os = FcObjectSetBuild(FC_FAMILY, (char *)0);
661 SkAutoTCallVProc<FcObjectSet, FcObjectSetDestroy> autoDestroyOs(os);
halcanary96fcdcc2015-08-27 07:41:13 -0700662 if (nullptr == os) {
663 return nullptr;
reed@google.com027fd202013-04-19 20:45:30 +0000664 }
bungeman@google.com02dd6882013-07-22 14:10:08 +0000665
halcanary96fcdcc2015-08-27 07:41:13 -0700666 FcFontSet* fs = FcFontList(nullptr, pat, os);
bungeman@google.com02dd6882013-07-22 14:10:08 +0000667 SkAutoTCallVProc<FcFontSet, FcFontSetDestroy> autoDestroyFs(fs);
halcanary96fcdcc2015-08-27 07:41:13 -0700668 if (nullptr == fs) {
669 return nullptr;
reed@google.com027fd202013-04-19 20:45:30 +0000670 }
671
672 SkTDArray<const char*> names;
673 SkTDArray<size_t> sizes;
674 for (int i = 0; i < fs->nfont; ++i) {
675 FcPattern* match = fs->fonts[i];
676 const char* famName = get_name(match, FC_FAMILY);
reed@google.come49d67e2013-04-22 18:00:06 +0000677 if (famName && !find_name(names, famName)) {
reed@google.com027fd202013-04-19 20:45:30 +0000678 *names.append() = famName;
679 *sizes.append() = strlen(famName) + 1;
680 }
681 }
682
reed@google.com027fd202013-04-19 20:45:30 +0000683 return SkDataTable::NewCopyArrays((const void*const*)names.begin(),
684 sizes.begin(), names.count());
685}