[FreeType] Add initial implementation of variation fonts
[WebKit-https.git] / Source / WebCore / platform / graphics / freetype / FontCacheFreeType.cpp
1 /*
2  * Copyright (C) 2008 Alp Toker <alp@atoker.com>
3  * Copyright (C) 2010 Igalia S.L.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this library; see the file COPYING.LIB.  If not, write to
17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  */
21
22 #include "config.h"
23 #include "FontCache.h"
24
25 #include "CairoUniquePtr.h"
26 #include "CairoUtilities.h"
27 #include "FcUniquePtr.h"
28 #include "FloatConversion.h"
29 #include "Font.h"
30 #include "FontDescription.h"
31 #include "FontCacheFreeType.h"
32 #include "RefPtrCairo.h"
33 #include "RefPtrFontconfig.h"
34 #include "UTF16UChar32Iterator.h"
35 #include <cairo-ft.h>
36 #include <cairo.h>
37 #include <fontconfig/fcfreetype.h>
38 #include <wtf/Assertions.h>
39 #include <wtf/text/CString.h>
40
41 #if PLATFORM(GTK)
42 #include "GtkUtilities.h"
43 #endif
44
45 #if ENABLE(VARIATION_FONTS)
46 #include FT_MULTIPLE_MASTERS_H
47 #endif
48
49 namespace WebCore {
50
51 void FontCache::platformInit()
52 {
53     // It's fine to call FcInit multiple times per the documentation.
54     if (!FcInit())
55         ASSERT_NOT_REACHED();
56 }
57
58 static int fontWeightToFontconfigWeight(FontSelectionValue weight)
59 {
60     if (weight < FontSelectionValue(150))
61         return FC_WEIGHT_THIN;
62     if (weight < FontSelectionValue(250))
63         return FC_WEIGHT_ULTRALIGHT;
64     if (weight < FontSelectionValue(350))
65         return FC_WEIGHT_LIGHT;
66     if (weight < FontSelectionValue(450))
67         return FC_WEIGHT_REGULAR;
68     if (weight < FontSelectionValue(550))
69         return FC_WEIGHT_MEDIUM;
70     if (weight < FontSelectionValue(650))
71         return FC_WEIGHT_SEMIBOLD;
72     if (weight < FontSelectionValue(750))
73         return FC_WEIGHT_BOLD;
74     if (weight < FontSelectionValue(850))
75         return FC_WEIGHT_EXTRABOLD;
76     return FC_WEIGHT_ULTRABLACK;
77 }
78
79 static bool configurePatternForFontDescription(FcPattern* pattern, const FontDescription& fontDescription)
80 {
81     if (!FcPatternAddInteger(pattern, FC_SLANT, fontDescription.italic() ? FC_SLANT_ITALIC : FC_SLANT_ROMAN))
82         return false;
83     if (!FcPatternAddInteger(pattern, FC_WEIGHT, fontWeightToFontconfigWeight(fontDescription.weight())))
84         return false;
85     if (!FcPatternAddDouble(pattern, FC_PIXEL_SIZE, fontDescription.computedPixelSize()))
86         return false;
87     return true;
88 }
89
90 static void getFontPropertiesFromPattern(FcPattern* pattern, const FontDescription& fontDescription, bool& fixedWidth, bool& syntheticBold, bool& syntheticOblique)
91 {
92     fixedWidth = false;
93     int spacing;
94     if (FcPatternGetInteger(pattern, FC_SPACING, 0, &spacing) == FcResultMatch && spacing == FC_MONO)
95         fixedWidth = true;
96
97     syntheticBold = false;
98     bool descriptionAllowsSyntheticBold = fontDescription.fontSynthesis() & FontSynthesisWeight;
99     if (descriptionAllowsSyntheticBold && isFontWeightBold(fontDescription.weight())) {
100         // The FC_EMBOLDEN property instructs us to fake the boldness of the font.
101         FcBool fontConfigEmbolden = FcFalse;
102         if (FcPatternGetBool(pattern, FC_EMBOLDEN, 0, &fontConfigEmbolden) == FcResultMatch)
103             syntheticBold = fontConfigEmbolden;
104
105         // Fallback fonts may not have FC_EMBOLDEN activated even though it's necessary.
106         int weight = 0;
107         if (!syntheticBold && FcPatternGetInteger(pattern, FC_WEIGHT, 0, &weight) == FcResultMatch)
108             syntheticBold = syntheticBold || weight < FC_WEIGHT_DEMIBOLD;
109     }
110
111     // We requested an italic font, but Fontconfig gave us one that was neither oblique nor italic.
112     syntheticOblique = false;
113     int actualFontSlant;
114     bool descriptionAllowsSyntheticOblique = fontDescription.fontSynthesis() & FontSynthesisStyle;
115     if (descriptionAllowsSyntheticOblique && fontDescription.italic()
116         && FcPatternGetInteger(pattern, FC_SLANT, 0, &actualFontSlant) == FcResultMatch) {
117         syntheticOblique = actualFontSlant == FC_SLANT_ROMAN;
118     }
119 }
120
121 RefPtr<Font> FontCache::systemFallbackForCharacters(const FontDescription& description, const Font*, bool, const UChar* characters, unsigned length)
122 {
123     FcUniquePtr<FcCharSet> fontConfigCharSet(FcCharSetCreate());
124     UTF16UChar32Iterator iterator(characters, length);
125     UChar32 character = iterator.next();
126     while (character != iterator.end()) {
127         FcCharSetAddChar(fontConfigCharSet.get(), character);
128         character = iterator.next();
129     }
130
131     RefPtr<FcPattern> pattern = adoptRef(FcPatternCreate());
132     FcPatternAddCharSet(pattern.get(), FC_CHARSET, fontConfigCharSet.get());
133
134     FcPatternAddBool(pattern.get(), FC_SCALABLE, FcTrue);
135
136     if (!configurePatternForFontDescription(pattern.get(), description))
137         return nullptr;
138
139     FcConfigSubstitute(nullptr, pattern.get(), FcMatchPattern);
140     cairo_ft_font_options_substitute(getDefaultCairoFontOptions(), pattern.get());
141     FcDefaultSubstitute(pattern.get());
142
143     FcResult fontConfigResult;
144     RefPtr<FcPattern> resultPattern = adoptRef(FcFontMatch(nullptr, pattern.get(), &fontConfigResult));
145     if (!resultPattern)
146         return nullptr;
147
148     bool fixedWidth, syntheticBold, syntheticOblique;
149     getFontPropertiesFromPattern(resultPattern.get(), description, fixedWidth, syntheticBold, syntheticOblique);
150
151     RefPtr<cairo_font_face_t> fontFace = adoptRef(cairo_ft_font_face_create_for_pattern(resultPattern.get()));
152     FontPlatformData alternateFontData(fontFace.get(), resultPattern.get(), description.computedPixelSize(), fixedWidth, syntheticBold, syntheticOblique, description.orientation());
153     return fontForPlatformData(alternateFontData);
154 }
155
156 static Vector<String> patternToFamilies(FcPattern& pattern)
157 {
158     char* patternChars = reinterpret_cast<char*>(FcPatternFormat(&pattern, reinterpret_cast<const FcChar8*>("%{family}")));
159     String patternString = String::fromUTF8(patternChars);
160     free(patternChars);
161
162     return patternString.split(',');
163 }
164
165 Vector<String> FontCache::systemFontFamilies()
166 {
167     RefPtr<FcPattern> scalablesOnlyPattern = adoptRef(FcPatternCreate());
168     FcPatternAddBool(scalablesOnlyPattern.get(), FC_SCALABLE, FcTrue);
169
170     FcUniquePtr<FcObjectSet> familiesOnly(FcObjectSetBuild(FC_FAMILY, nullptr));
171     FcUniquePtr<FcFontSet> fontSet(FcFontList(nullptr, scalablesOnlyPattern.get(), familiesOnly.get()));
172
173     Vector<String> fontFamilies;
174     for (int i = 0; i < fontSet->nfont; i++) {
175         FcPattern* pattern = fontSet->fonts[i];
176         FcChar8* family = nullptr;
177         FcPatternGetString(pattern, FC_FAMILY, 0, &family);
178         if (family)
179             fontFamilies.appendVector(patternToFamilies(*pattern));
180     }
181
182     return fontFamilies;
183 }
184
185 Ref<Font> FontCache::lastResortFallbackFont(const FontDescription& fontDescription)
186 {
187     // We want to return a fallback font here, otherwise the logic preventing FontConfig
188     // matches for non-fallback fonts might return 0. See isFallbackFontAllowed.
189     static AtomicString timesStr("serif");
190     if (RefPtr<Font> font = fontForFamily(fontDescription, timesStr))
191         return *font;
192
193     // This could be reached due to improperly-installed or misconfigured fontconfig.
194     RELEASE_ASSERT_NOT_REACHED();
195 }
196
197 Vector<FontSelectionCapabilities> FontCache::getFontSelectionCapabilitiesInFamily(const AtomicString&, AllowUserInstalledFonts)
198 {
199     return { };
200 }
201
202 static String getFamilyNameStringFromFamily(const AtomicString& family)
203 {
204     // If we're creating a fallback font (e.g. "-webkit-monospace"), convert the name into
205     // the fallback name (like "monospace") that fontconfig understands.
206     if (family.length() && !family.startsWith("-webkit-"))
207         return family.string();
208
209     if (family == standardFamily || family == serifFamily)
210         return "serif";
211     if (family == sansSerifFamily)
212         return "sans-serif";
213     if (family == monospaceFamily)
214         return "monospace";
215     if (family == cursiveFamily)
216         return "cursive";
217     if (family == fantasyFamily)
218         return "fantasy";
219
220 #if PLATFORM(GTK)
221     if (family == systemUiFamily || family == "-webkit-system-font")
222         return defaultGtkSystemFont();
223 #endif
224
225     return "";
226 }
227
228 // This is based on Chromium BSD code from Skia (src/ports/SkFontMgr_fontconfig.cpp). It is a
229 // hack for lack of API in Fontconfig: https://bugs.freedesktop.org/show_bug.cgi?id=19375
230 // FIXME: This is horrible. It should be deleted once Fontconfig can do this itself.
231 enum class AliasStrength {
232     Weak,
233     Strong,
234     Done
235 };
236
237 static AliasStrength strengthOfFirstAlias(const FcPattern& original)
238 {
239     // Ideally there would exist a call like
240     // FcResult FcPatternIsWeak(pattern, object, id, FcBool* isWeak);
241     //
242     // However, there is no such call and as of Fc 2.11.0 even FcPatternEquals ignores the weak bit.
243     // Currently, the only reliable way of finding the weak bit is by its effect on matching.
244     // The weak bit only affects the matching of FC_FAMILY and FC_POSTSCRIPT_NAME object values.
245     // A element with the weak bit is scored after FC_LANG, without the weak bit is scored before.
246     // Note that the weak bit is stored on the element, not on the value it holds.
247     FcValue value;
248     FcResult result = FcPatternGet(&original, FC_FAMILY, 0, &value);
249     if (result != FcResultMatch)
250         return AliasStrength::Done;
251
252     RefPtr<FcPattern> pattern = adoptRef(FcPatternDuplicate(&original));
253     FcBool hasMultipleFamilies = true;
254     while (hasMultipleFamilies)
255         hasMultipleFamilies = FcPatternRemove(pattern.get(), FC_FAMILY, 1);
256
257     // Create a font set with two patterns.
258     // 1. the same FC_FAMILY as pattern and a lang object with only 'nomatchlang'.
259     // 2. a different FC_FAMILY from pattern and a lang object with only 'matchlang'.
260     FcUniquePtr<FcFontSet> fontSet(FcFontSetCreate());
261
262     FcUniquePtr<FcLangSet> strongLangSet(FcLangSetCreate());
263     FcLangSetAdd(strongLangSet.get(), reinterpret_cast<const FcChar8*>("nomatchlang"));
264     // Ownership of this FcPattern will be transferred with FcFontSetAdd.
265     FcPattern* strong = FcPatternDuplicate(pattern.get());
266     FcPatternAddLangSet(strong, FC_LANG, strongLangSet.get());
267
268     FcUniquePtr<FcLangSet> weakLangSet(FcLangSetCreate());
269     FcLangSetAdd(weakLangSet.get(), reinterpret_cast<const FcChar8*>("matchlang"));
270     // Ownership of this FcPattern will be transferred via FcFontSetAdd.
271     FcPattern* weak = FcPatternCreate();
272     FcPatternAddString(weak, FC_FAMILY, reinterpret_cast<const FcChar8*>("nomatchstring"));
273     FcPatternAddLangSet(weak, FC_LANG, weakLangSet.get());
274
275     FcFontSetAdd(fontSet.get(), strong);
276     FcFontSetAdd(fontSet.get(), weak);
277
278     // Add 'matchlang' to the copy of the pattern.
279     FcPatternAddLangSet(pattern.get(), FC_LANG, weakLangSet.get());
280
281     // Run a match against the copy of the pattern.
282     // If the first element was weak, then we should match the pattern with 'matchlang'.
283     // If the first element was strong, then we should match the pattern with 'nomatchlang'.
284
285     // Note that this config is only used for FcFontRenderPrepare, which we don't even want.
286     // However, there appears to be no way to match/sort without it.
287     RefPtr<FcConfig> config = adoptRef(FcConfigCreate());
288     FcFontSet* fontSets[1] = { fontSet.get() };
289     RefPtr<FcPattern> match = adoptRef(FcFontSetMatch(config.get(), fontSets, 1, pattern.get(), &result));
290
291     FcLangSet* matchLangSet;
292     FcPatternGetLangSet(match.get(), FC_LANG, 0, &matchLangSet);
293     return FcLangEqual == FcLangSetHasLang(matchLangSet, reinterpret_cast<const FcChar8*>("matchlang"))
294         ? AliasStrength::Weak : AliasStrength::Strong;
295 }
296
297 static Vector<String> strongAliasesForFamily(const String& family)
298 {
299     RefPtr<FcPattern> pattern = adoptRef(FcPatternCreate());
300     if (!FcPatternAddString(pattern.get(), FC_FAMILY, reinterpret_cast<const FcChar8*>(family.utf8().data())))
301         return Vector<String>();
302
303     FcConfigSubstitute(nullptr, pattern.get(), FcMatchPattern);
304     cairo_ft_font_options_substitute(getDefaultCairoFontOptions(), pattern.get());
305     FcDefaultSubstitute(pattern.get());
306
307     FcUniquePtr<FcObjectSet> familiesOnly(FcObjectSetBuild(FC_FAMILY, nullptr));
308     RefPtr<FcPattern> minimal = adoptRef(FcPatternFilter(pattern.get(), familiesOnly.get()));
309
310     // We really want to match strong (preferred) and same (acceptable) only here.
311     // If a family name was specified, assume that any weak matches after the last strong match
312     // are weak (default) and ignore them.
313     // The reason for is that after substitution the pattern for 'sans-serif' looks like
314     // "wwwwwwwwwwwwwwswww" where there are many weak but preferred names, followed by defaults.
315     // So it is possible to have weakly matching but preferred names.
316     // In aliases, bindings are weak by default, so this is easy and common.
317     // If no family name was specified, we'll probably only get weak matches, but that's ok.
318     int lastStrongId = -1;
319     int numIds = 0;
320     for (int id = 0; ; ++id) {
321         AliasStrength result = strengthOfFirstAlias(*minimal);
322         if (result == AliasStrength::Done) {
323             numIds = id;
324             break;
325         }
326         if (result == AliasStrength::Strong)
327             lastStrongId = id;
328         if (!FcPatternRemove(minimal.get(), FC_FAMILY, 0))
329             return Vector<String>();
330     }
331
332     // If they were all weak, then leave the pattern alone.
333     if (lastStrongId < 0)
334         return Vector<String>();
335
336     // Remove everything after the last strong.
337     for (int id = lastStrongId + 1; id < numIds; ++id) {
338         if (!FcPatternRemove(pattern.get(), FC_FAMILY, lastStrongId + 1)) {
339             ASSERT_NOT_REACHED();
340             return Vector<String>();
341         }
342     }
343
344     return patternToFamilies(*pattern);
345 }
346
347 static bool areStronglyAliased(const String& familyA, const String& familyB)
348 {
349     for (auto& family : strongAliasesForFamily(familyA)) {
350         if (family == familyB)
351             return true;
352     }
353     return false;
354 }
355
356 static inline bool isCommonlyUsedGenericFamily(const String& familyNameString)
357 {
358     return equalLettersIgnoringASCIICase(familyNameString, "sans")
359         || equalLettersIgnoringASCIICase(familyNameString, "sans-serif")
360         || equalLettersIgnoringASCIICase(familyNameString, "serif")
361         || equalLettersIgnoringASCIICase(familyNameString, "monospace")
362         || equalLettersIgnoringASCIICase(familyNameString, "fantasy")
363 #if PLATFORM(GTK)
364         || equalLettersIgnoringASCIICase(familyNameString, "-webkit-system-font")
365         || equalLettersIgnoringASCIICase(familyNameString, "-webkit-system-ui")
366 #endif
367         || equalLettersIgnoringASCIICase(familyNameString, "cursive");
368 }
369
370 std::unique_ptr<FontPlatformData> FontCache::createFontPlatformData(const FontDescription& fontDescription, const AtomicString& family, const FontFeatureSettings*, const FontVariantSettings*, FontSelectionSpecifiedCapabilities)
371 {
372     // The CSS font matching algorithm (http://www.w3.org/TR/css3-fonts/#font-matching-algorithm)
373     // says that we must find an exact match for font family, slant (italic or oblique can be used)
374     // and font weight (we only match bold/non-bold here).
375     RefPtr<FcPattern> pattern = adoptRef(FcPatternCreate());
376     // Never choose unscalable fonts, as they pixelate when displayed at different sizes.
377     FcPatternAddBool(pattern.get(), FC_SCALABLE, FcTrue);
378 #if ENABLE(VARIATION_FONTS)
379     FcPatternAddBool(pattern.get(), FC_VARIABLE, FcDontCare);
380 #endif
381     String familyNameString(getFamilyNameStringFromFamily(family));
382     if (!FcPatternAddString(pattern.get(), FC_FAMILY, reinterpret_cast<const FcChar8*>(familyNameString.utf8().data())))
383         return nullptr;
384
385     if (!configurePatternForFontDescription(pattern.get(), fontDescription))
386         return nullptr;
387
388     // The strategy is originally from Skia (src/ports/SkFontHost_fontconfig.cpp):
389     //
390     // We do not normally allow fontconfig to substitute one font family for another, since this
391     // would break CSS font family fallback: the website should be in control of fallback. During
392     // normal font matching, the only font family substitution permitted is for generic families
393     // (sans, serif, monospace) or for strongly-aliased fonts (which are to be treated as
394     // effectively identical). This is because the font matching step is designed to always find a
395     // match for the font, which we don't want.
396     //
397     // Fontconfig is used in two stages: (1) configuration and (2) matching. During the
398     // configuration step, before any matching occurs, we allow arbitrary family substitutions,
399     // since this is an exact matter of respecting the user's font configuration.
400     FcConfigSubstitute(nullptr, pattern.get(), FcMatchPattern);
401     cairo_ft_font_options_substitute(getDefaultCairoFontOptions(), pattern.get());
402     FcDefaultSubstitute(pattern.get());
403
404     FcChar8* fontConfigFamilyNameAfterConfiguration;
405     FcPatternGetString(pattern.get(), FC_FAMILY, 0, &fontConfigFamilyNameAfterConfiguration);
406     String familyNameAfterConfiguration = String::fromUTF8(reinterpret_cast<char*>(fontConfigFamilyNameAfterConfiguration));
407
408     FcResult fontConfigResult;
409     RefPtr<FcPattern> resultPattern = adoptRef(FcFontMatch(nullptr, pattern.get(), &fontConfigResult));
410     if (!resultPattern) // No match.
411         return nullptr;
412
413     // Loop through each font family of the result to see if it fits the one we requested.
414     bool matchedFontFamily = false;
415     FcChar8* fontConfigFamilyNameAfterMatching;
416     for (int i = 0; FcPatternGetString(resultPattern.get(), FC_FAMILY, i, &fontConfigFamilyNameAfterMatching) == FcResultMatch; ++i) {
417         // If Fontconfig gave us a different font family than the one we requested, we should ignore it
418         // and allow WebCore to give us the next font on the CSS fallback list. The exceptions are if
419         // this family name is a commonly-used generic family, or if the families are strongly-aliased.
420         // Checking for a strong alias comes last, since it is slow.
421         String familyNameAfterMatching = String::fromUTF8(reinterpret_cast<char*>(fontConfigFamilyNameAfterMatching));
422         if (equalIgnoringASCIICase(familyNameAfterConfiguration, familyNameAfterMatching) || isCommonlyUsedGenericFamily(familyNameString) || areStronglyAliased(familyNameAfterConfiguration, familyNameAfterMatching)) {
423             matchedFontFamily = true;
424             break;
425         }
426     }
427
428     if (!matchedFontFamily)
429         return nullptr;
430
431     bool fixedWidth, syntheticBold, syntheticOblique;
432     getFontPropertiesFromPattern(pattern.get(), fontDescription, fixedWidth, syntheticBold, syntheticOblique);
433
434     RefPtr<cairo_font_face_t> fontFace = adoptRef(cairo_ft_font_face_create_for_pattern(resultPattern.get()));
435 #if ENABLE(VARIATION_FONTS)
436     // Cairo doesn't have API to get the FT_Face of an unscaled font, so we need to
437     // create a temporary scaled font to get the FT_Face.
438     CairoUniquePtr<cairo_font_options_t> options(cairo_font_options_copy(getDefaultCairoFontOptions()));
439     cairo_matrix_t matrix;
440     cairo_matrix_init_identity(&matrix);
441     RefPtr<cairo_scaled_font_t> scaledFont = adoptRef(cairo_scaled_font_create(fontFace.get(), &matrix, &matrix, options.get()));
442     CairoFtFaceLocker cairoFtFaceLocker(scaledFont.get());
443     if (FT_Face freeTypeFace = cairoFtFaceLocker.ftFace()) {
444         auto variants = buildVariationSettings(freeTypeFace, fontDescription);
445         if (!variants.isEmpty())
446             FcPatternAddString(resultPattern.get(), FC_FONT_VARIATIONS, reinterpret_cast<const FcChar8*>(variants.utf8().data()));
447     }
448 #endif
449     auto platformData = std::make_unique<FontPlatformData>(fontFace.get(), resultPattern.get(), fontDescription.computedPixelSize(), fixedWidth, syntheticBold, syntheticOblique, fontDescription.orientation());
450     // Verify that this font has an encoding compatible with Fontconfig. Fontconfig currently
451     // supports three encodings in FcFreeTypeCharIndex: Unicode, Symbol and AppleRoman.
452     // If this font doesn't have one of these three encodings, don't select it.
453     if (!platformData->hasCompatibleCharmap())
454         return nullptr;
455
456     return platformData;
457 }
458
459 const AtomicString& FontCache::platformAlternateFamilyName(const AtomicString&)
460 {
461     return nullAtom();
462 }
463
464 #if ENABLE(VARIATION_FONTS)
465 struct VariationDefaults {
466     float defaultValue;
467     float minimumValue;
468     float maximumValue;
469 };
470
471 typedef HashMap<FontTag, VariationDefaults, FourCharacterTagHash, FourCharacterTagHashTraits> VariationDefaultsMap;
472 typedef HashMap<FontTag, float, FourCharacterTagHash, FourCharacterTagHashTraits> VariationsMap;
473
474 static VariationDefaultsMap defaultVariationValues(FT_Face face)
475 {
476     VariationDefaultsMap result;
477     FT_MM_Var* ftMMVar;
478     if (FT_Get_MM_Var(face, &ftMMVar))
479         return result;
480
481     for (unsigned i = 0; i < ftMMVar->num_axis; ++i) {
482         auto tag = ftMMVar->axis[i].tag;
483         auto b1 = 0xFF & (tag >> 24);
484         auto b2 = 0xFF & (tag >> 16);
485         auto b3 = 0xFF & (tag >> 8);
486         auto b4 = 0xFF & (tag >> 0);
487         FontTag resultKey = {{ static_cast<char>(b1), static_cast<char>(b2), static_cast<char>(b3), static_cast<char>(b4) }};
488         VariationDefaults resultValues = { narrowPrecisionToFloat(ftMMVar->axis[i].def / 65536.), narrowPrecisionToFloat(ftMMVar->axis[i].minimum / 65536.), narrowPrecisionToFloat(ftMMVar->axis[i].maximum / 65536.) };
489         result.set(resultKey, resultValues);
490     }
491     FT_Done_MM_Var(face->glyph->library, ftMMVar);
492     return result;
493 }
494
495 String buildVariationSettings(FT_Face face, const FontDescription& fontDescription)
496 {
497     auto defaultValues = defaultVariationValues(face);
498     const auto& variations = fontDescription.variationSettings();
499
500     VariationsMap variationsToBeApplied;
501     auto applyVariation = [&](const FontTag& tag, float value) {
502         auto iterator = defaultValues.find(tag);
503         if (iterator == defaultValues.end())
504             return;
505         float valueToApply = clampTo(value, iterator->value.minimumValue, iterator->value.maximumValue);
506         variationsToBeApplied.set(tag, valueToApply);
507     };
508
509     for (auto& variation : variations)
510         applyVariation(variation.tag(), variation.value());
511
512     StringBuilder builder;
513     for (auto& variation : variationsToBeApplied) {
514         if (!builder.isEmpty())
515             builder.append(',');
516         builder.append(variation.key[0]);
517         builder.append(variation.key[1]);
518         builder.append(variation.key[2]);
519         builder.append(variation.key[3]);
520         builder.append('=');
521         builder.appendNumber(variation.value);
522     }
523     return builder.toString();
524 }
525 #endif // ENABLE(VARIATION_FONTS)
526
527 }