1/*
2 * Copyright (C) 2006, 2013-2015 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "FontCascadeFonts.h"
31
32#include "FontCache.h"
33#include "FontCascade.h"
34#include "GlyphPage.h"
35
36namespace WebCore {
37
38class MixedFontGlyphPage {
39 WTF_MAKE_FAST_ALLOCATED;
40public:
41 MixedFontGlyphPage(const GlyphPage* initialPage)
42 {
43 if (initialPage) {
44 for (unsigned i = 0; i < GlyphPage::size; ++i)
45 setGlyphDataForIndex(i, initialPage->glyphDataForIndex(i));
46 }
47 }
48
49 GlyphData glyphDataForCharacter(UChar32 c) const
50 {
51 unsigned index = GlyphPage::indexForCodePoint(c);
52 ASSERT_WITH_SECURITY_IMPLICATION(index < GlyphPage::size);
53 return { m_glyphs[index], m_fonts[index] };
54 }
55
56 void setGlyphDataForCharacter(UChar32 c, GlyphData glyphData)
57 {
58 setGlyphDataForIndex(GlyphPage::indexForCodePoint(c), glyphData);
59 }
60
61private:
62 void setGlyphDataForIndex(unsigned index, const GlyphData& glyphData)
63 {
64 ASSERT_WITH_SECURITY_IMPLICATION(index < GlyphPage::size);
65 m_glyphs[index] = glyphData.glyph;
66 m_fonts[index] = glyphData.font;
67 }
68
69 Glyph m_glyphs[GlyphPage::size] { };
70 const Font* m_fonts[GlyphPage::size] { };
71};
72
73GlyphData FontCascadeFonts::GlyphPageCacheEntry::glyphDataForCharacter(UChar32 character)
74{
75 ASSERT(!(m_singleFont && m_mixedFont));
76 if (m_singleFont)
77 return m_singleFont->glyphDataForCharacter(character);
78 if (m_mixedFont)
79 return m_mixedFont->glyphDataForCharacter(character);
80 return 0;
81}
82
83void FontCascadeFonts::GlyphPageCacheEntry::setGlyphDataForCharacter(UChar32 character, GlyphData glyphData)
84{
85 ASSERT(!glyphDataForCharacter(character).glyph);
86 if (!m_mixedFont) {
87 m_mixedFont = std::make_unique<MixedFontGlyphPage>(m_singleFont.get());
88 m_singleFont = nullptr;
89 }
90 m_mixedFont->setGlyphDataForCharacter(character, glyphData);
91}
92
93void FontCascadeFonts::GlyphPageCacheEntry::setSingleFontPage(RefPtr<GlyphPage>&& page)
94{
95 ASSERT(isNull());
96 m_singleFont = page;
97}
98
99FontCascadeFonts::FontCascadeFonts(RefPtr<FontSelector>&& fontSelector)
100 : m_cachedPrimaryFont(nullptr)
101 , m_fontSelector(fontSelector)
102 , m_fontSelectorVersion(m_fontSelector ? m_fontSelector->version() : 0)
103 , m_generation(FontCache::singleton().generation())
104{
105}
106
107FontCascadeFonts::FontCascadeFonts(const FontPlatformData& platformData)
108 : m_cachedPrimaryFont(nullptr)
109 , m_fontSelectorVersion(0)
110 , m_generation(FontCache::singleton().generation())
111 , m_isForPlatformFont(true)
112{
113 m_realizedFallbackRanges.append(FontRanges(FontCache::singleton().fontForPlatformData(platformData)));
114}
115
116FontCascadeFonts::~FontCascadeFonts() = default;
117
118void FontCascadeFonts::determinePitch(const FontCascadeDescription& description)
119{
120 auto& primaryRanges = realizeFallbackRangesAt(description, 0);
121 unsigned numRanges = primaryRanges.size();
122 if (numRanges == 1)
123 m_pitch = primaryRanges.fontForFirstRange().pitch();
124 else
125 m_pitch = VariablePitch;
126}
127
128bool FontCascadeFonts::isLoadingCustomFonts() const
129{
130 for (auto& fontRanges : m_realizedFallbackRanges) {
131 if (fontRanges.isLoading())
132 return true;
133 }
134 return false;
135}
136
137static FontRanges realizeNextFallback(const FontCascadeDescription& description, unsigned& index, FontSelector* fontSelector)
138{
139 ASSERT(index < description.effectiveFamilyCount());
140
141 auto& fontCache = FontCache::singleton();
142 while (index < description.effectiveFamilyCount()) {
143 auto visitor = WTF::makeVisitor([&](const AtomicString& family) -> FontRanges {
144 if (family.isEmpty())
145 return FontRanges();
146 if (fontSelector) {
147 auto ranges = fontSelector->fontRangesForFamily(description, family);
148 if (!ranges.isNull())
149 return ranges;
150 }
151 if (auto font = fontCache.fontForFamily(description, family))
152 return FontRanges(WTFMove(font));
153 return FontRanges();
154 }, [&](const FontFamilyPlatformSpecification& fontFamilySpecification) -> FontRanges {
155 return fontFamilySpecification.fontRanges(description);
156 });
157 const auto& currentFamily = description.effectiveFamilyAt(index++);
158 auto ranges = WTF::visit(visitor, currentFamily);
159 if (!ranges.isNull())
160 return ranges;
161 }
162 // We didn't find a font. Try to find a similar font using our own specific knowledge about our platform.
163 // For example on OS X, we know to map any families containing the words Arabic, Pashto, or Urdu to the
164 // Geeza Pro font.
165 for (auto& family : description.families()) {
166 if (auto font = fontCache.similarFont(description, family))
167 return FontRanges(WTFMove(font));
168 }
169 return { };
170}
171
172const FontRanges& FontCascadeFonts::realizeFallbackRangesAt(const FontCascadeDescription& description, unsigned index)
173{
174 if (index < m_realizedFallbackRanges.size())
175 return m_realizedFallbackRanges[index];
176
177 ASSERT(index == m_realizedFallbackRanges.size());
178 ASSERT(FontCache::singleton().generation() == m_generation);
179
180 m_realizedFallbackRanges.append(FontRanges());
181 auto& fontRanges = m_realizedFallbackRanges.last();
182
183 if (!index) {
184 fontRanges = realizeNextFallback(description, m_lastRealizedFallbackIndex, m_fontSelector.get());
185 if (fontRanges.isNull() && m_fontSelector)
186 fontRanges = m_fontSelector->fontRangesForFamily(description, standardFamily);
187 if (fontRanges.isNull())
188 fontRanges = FontRanges(FontCache::singleton().lastResortFallbackFont(description));
189 return fontRanges;
190 }
191
192 if (m_lastRealizedFallbackIndex < description.effectiveFamilyCount())
193 fontRanges = realizeNextFallback(description, m_lastRealizedFallbackIndex, m_fontSelector.get());
194
195 if (fontRanges.isNull() && m_fontSelector) {
196 ASSERT(m_lastRealizedFallbackIndex >= description.effectiveFamilyCount());
197
198 unsigned fontSelectorFallbackIndex = m_lastRealizedFallbackIndex - description.effectiveFamilyCount();
199 if (fontSelectorFallbackIndex == m_fontSelector->fallbackFontCount())
200 return fontRanges;
201 ++m_lastRealizedFallbackIndex;
202 fontRanges = FontRanges(m_fontSelector->fallbackFontAt(description, fontSelectorFallbackIndex));
203 }
204
205 return fontRanges;
206}
207
208static inline bool isInRange(UChar32 character, UChar32 lowerBound, UChar32 upperBound)
209{
210 return character >= lowerBound && character <= upperBound;
211}
212
213static bool shouldIgnoreRotation(UChar32 character)
214{
215 if (character == 0x000A7 || character == 0x000A9 || character == 0x000AE)
216 return true;
217
218 if (character == 0x000B6 || character == 0x000BC || character == 0x000BD || character == 0x000BE)
219 return true;
220
221 if (isInRange(character, 0x002E5, 0x002EB))
222 return true;
223
224 if (isInRange(character, 0x01100, 0x011FF) || isInRange(character, 0x01401, 0x0167F) || isInRange(character, 0x01800, 0x018FF))
225 return true;
226
227 if (character == 0x02016 || character == 0x02020 || character == 0x02021 || character == 0x2030 || character == 0x02031)
228 return true;
229
230 if (isInRange(character, 0x0203B, 0x0203D) || character == 0x02042 || character == 0x02044 || character == 0x02047
231 || character == 0x02048 || character == 0x02049 || character == 0x2051)
232 return true;
233
234 if (isInRange(character, 0x02065, 0x02069) || isInRange(character, 0x020DD, 0x020E0)
235 || isInRange(character, 0x020E2, 0x020E4) || isInRange(character, 0x02100, 0x02117)
236 || isInRange(character, 0x02119, 0x02131) || isInRange(character, 0x02133, 0x0213F))
237 return true;
238
239 if (isInRange(character, 0x02145, 0x0214A) || character == 0x0214C || character == 0x0214D
240 || isInRange(character, 0x0214F, 0x0218F))
241 return true;
242
243 if (isInRange(character, 0x02300, 0x02307) || isInRange(character, 0x0230C, 0x0231F)
244 || isInRange(character, 0x02322, 0x0232B) || isInRange(character, 0x0237D, 0x0239A)
245 || isInRange(character, 0x023B4, 0x023B6) || isInRange(character, 0x023BA, 0x023CF)
246 || isInRange(character, 0x023D1, 0x023DB) || isInRange(character, 0x023E2, 0x024FF))
247 return true;
248
249 if (isInRange(character, 0x025A0, 0x02619) || isInRange(character, 0x02620, 0x02767)
250 || isInRange(character, 0x02776, 0x02793) || isInRange(character, 0x02B12, 0x02B2F)
251 || isInRange(character, 0x02B4D, 0x02BFF) || isInRange(character, 0x02E80, 0x03007))
252 return true;
253
254 if (character == 0x03012 || character == 0x03013 || isInRange(character, 0x03020, 0x0302F)
255 || isInRange(character, 0x03031, 0x0309F) || isInRange(character, 0x030A1, 0x030FB)
256 || isInRange(character, 0x030FD, 0x0A4CF))
257 return true;
258
259 if (isInRange(character, 0x0A840, 0x0A87F) || isInRange(character, 0x0A960, 0x0A97F)
260 || isInRange(character, 0x0AC00, 0x0D7FF) || isInRange(character, 0x0E000, 0x0FAFF))
261 return true;
262
263 if (isInRange(character, 0x0FE10, 0x0FE1F) || isInRange(character, 0x0FE30, 0x0FE48)
264 || isInRange(character, 0x0FE50, 0x0FE57) || isInRange(character, 0x0FE5F, 0x0FE62)
265 || isInRange(character, 0x0FE67, 0x0FE6F))
266 return true;
267
268 if (isInRange(character, 0x0FF01, 0x0FF07) || isInRange(character, 0x0FF0A, 0x0FF0C)
269 || isInRange(character, 0x0FF0E, 0x0FF19) || character == 0x0FF1B || isInRange(character, 0x0FF1F, 0x0FF3A))
270 return true;
271
272 if (character == 0x0FF3C || character == 0x0FF3E)
273 return true;
274
275 if (isInRange(character, 0x0FF40, 0x0FF5A) || isInRange(character, 0x0FFE0, 0x0FFE2)
276 || isInRange(character, 0x0FFE4, 0x0FFE7) || isInRange(character, 0x0FFF0, 0x0FFF8)
277 || character == 0x0FFFD)
278 return true;
279
280 if (isInRange(character, 0x13000, 0x1342F) || isInRange(character, 0x1B000, 0x1B0FF)
281 || isInRange(character, 0x1D000, 0x1D1FF) || isInRange(character, 0x1D300, 0x1D37F)
282 || isInRange(character, 0x1F000, 0x1F64F) || isInRange(character, 0x1F680, 0x1F77F))
283 return true;
284
285 if (isInRange(character, 0x20000, 0x2FFFD) || isInRange(character, 0x30000, 0x3FFFD))
286 return true;
287
288 return false;
289}
290
291static GlyphData glyphDataForNonCJKCharacterWithGlyphOrientation(UChar32 character, NonCJKGlyphOrientation orientation, const GlyphData& data)
292{
293 bool syntheticOblique = data.font->platformData().syntheticOblique();
294 if (orientation == NonCJKGlyphOrientation::Upright || shouldIgnoreRotation(character)) {
295 GlyphData uprightData = data.font->uprightOrientationFont().glyphDataForCharacter(character);
296 // If the glyphs are the same, then we know we can just use the horizontal glyph rotated vertically
297 // to be upright. For synthetic oblique, however, we will always return the uprightData to ensure
298 // that non-CJK and CJK runs are broken up. This guarantees that vertical
299 // fonts without isTextOrientationFallback() set contain CJK characters only and thus we can get
300 // the oblique slant correct.
301 if (data.glyph == uprightData.glyph && !syntheticOblique)
302 return data;
303 // The glyphs are distinct, meaning that the font has a vertical-right glyph baked into it. We can't use that
304 // glyph, so we fall back to the upright data and use the horizontal glyph.
305 if (uprightData.font)
306 return uprightData;
307 } else if (orientation == NonCJKGlyphOrientation::Mixed) {
308 GlyphData verticalRightData = data.font->verticalRightOrientationFont().glyphDataForCharacter(character);
309
310 // If there is a baked-in rotated glyph, we will use it unless syntheticOblique is set. If
311 // synthetic oblique is set, we fall back to the horizontal glyph. This guarantees that vertical
312 // fonts without isTextOrientationFallback() set contain CJK characters only and thus we can get
313 // the oblique slant correct.
314 if (data.glyph != verticalRightData.glyph && !syntheticOblique)
315 return data;
316
317 // The glyphs are identical, meaning that we should just use the horizontal glyph.
318 if (verticalRightData.font)
319 return verticalRightData;
320 }
321 return data;
322}
323
324static const Font* findBestFallbackFont(FontCascadeFonts& fontCascadeFonts, const FontCascadeDescription& description, UChar32 character)
325{
326 for (unsigned fallbackIndex = 0; ; ++fallbackIndex) {
327 auto& fontRanges = fontCascadeFonts.realizeFallbackRangesAt(description, fallbackIndex);
328 if (fontRanges.isNull())
329 break;
330 auto* currentFont = fontRanges.glyphDataForCharacter(character, ExternalResourceDownloadPolicy::Forbid).font;
331 if (!currentFont)
332 currentFont = &fontRanges.fontForFirstRange();
333
334 if (!currentFont->isInterstitial())
335 return currentFont;
336 }
337
338 return nullptr;
339}
340
341GlyphData FontCascadeFonts::glyphDataForSystemFallback(UChar32 character, const FontCascadeDescription& description, FontVariant variant, bool systemFallbackShouldBeInvisible)
342{
343 const Font* font = findBestFallbackFont(*this, description, character);
344
345 if (!font)
346 font = &realizeFallbackRangesAt(description, 0).fontForFirstRange();
347
348 auto systemFallbackFont = font->systemFallbackFontForCharacter(character, description, m_isForPlatformFont ? IsForPlatformFont::Yes : IsForPlatformFont::No);
349 if (!systemFallbackFont)
350 return GlyphData();
351
352 if (systemFallbackShouldBeInvisible)
353 systemFallbackFont = const_cast<Font*>(&systemFallbackFont->invisibleFont());
354
355 if (systemFallbackFont->platformData().orientation() == FontOrientation::Vertical && !systemFallbackFont->hasVerticalGlyphs() && FontCascade::isCJKIdeographOrSymbol(character))
356 variant = BrokenIdeographVariant;
357
358 GlyphData fallbackGlyphData;
359 if (variant == NormalVariant)
360 fallbackGlyphData = systemFallbackFont->glyphDataForCharacter(character);
361 else
362 fallbackGlyphData = systemFallbackFont->variantFont(description, variant)->glyphDataForCharacter(character);
363
364 if (fallbackGlyphData.font && fallbackGlyphData.font->platformData().orientation() == FontOrientation::Vertical && !fallbackGlyphData.font->isTextOrientationFallback()) {
365 if (variant == NormalVariant && !FontCascade::isCJKIdeographOrSymbol(character))
366 fallbackGlyphData = glyphDataForNonCJKCharacterWithGlyphOrientation(character, description.nonCJKGlyphOrientation(), fallbackGlyphData);
367 }
368
369 // Keep the system fallback fonts we use alive.
370 if (fallbackGlyphData.glyph)
371 m_systemFallbackFontSet.add(WTFMove(systemFallbackFont));
372
373 return fallbackGlyphData;
374}
375
376enum class FallbackVisibility {
377 Immaterial,
378 Visible,
379 Invisible
380};
381
382static void opportunisticallyStartFontDataURLLoading(const FontCascadeDescription& description, FontSelector* fontSelector)
383{
384 // It is a somewhat common practice for a font foundry to break up a single font into two fonts, each having a random half of
385 // the alphabet, and then encoding the two fonts as data: urls (with different font-family names).
386 // Therefore, if these two fonts don't get loaded at (nearly) the same time, there will be a flash of unintelligible text where
387 // only a random half of the letters are visible.
388 // This code attempts to pre-warm these data urls to make them load at closer to the same time. However, font loading is
389 // asynchronous, and this code doesn't actually fix the race - it just makes it more likely for the two fonts to tie in the race.
390 if (!fontSelector)
391 return;
392 for (unsigned i = 0; i < description.familyCount(); ++i)
393 fontSelector->opportunisticallyStartFontDataURLLoading(description, description.familyAt(i));
394}
395
396GlyphData FontCascadeFonts::glyphDataForVariant(UChar32 character, const FontCascadeDescription& description, FontVariant variant, unsigned fallbackIndex)
397{
398 FallbackVisibility fallbackVisibility = FallbackVisibility::Immaterial;
399 ExternalResourceDownloadPolicy policy = ExternalResourceDownloadPolicy::Allow;
400 GlyphData loadingResult;
401 opportunisticallyStartFontDataURLLoading(description, m_fontSelector.get());
402 for (; ; ++fallbackIndex) {
403 auto& fontRanges = realizeFallbackRangesAt(description, fallbackIndex);
404 if (fontRanges.isNull())
405 break;
406
407 GlyphData data = fontRanges.glyphDataForCharacter(character, policy);
408 if (!data.font)
409 continue;
410
411 if (data.font->isInterstitial()) {
412 policy = ExternalResourceDownloadPolicy::Forbid;
413 if (fallbackVisibility == FallbackVisibility::Immaterial)
414 fallbackVisibility = data.font->visibility() == Font::Visibility::Visible ? FallbackVisibility::Visible : FallbackVisibility::Invisible;
415 if (!loadingResult.font && data.glyph)
416 loadingResult = data;
417 continue;
418 }
419
420 if (fallbackVisibility == FallbackVisibility::Invisible && data.font->visibility() == Font::Visibility::Visible)
421 data.font = &data.font->invisibleFont();
422
423 if (variant == NormalVariant) {
424 if (data.font->platformData().orientation() == FontOrientation::Vertical && !data.font->isTextOrientationFallback()) {
425 if (!FontCascade::isCJKIdeographOrSymbol(character))
426 return glyphDataForNonCJKCharacterWithGlyphOrientation(character, description.nonCJKGlyphOrientation(), data);
427
428 if (!data.font->hasVerticalGlyphs()) {
429 // Use the broken ideograph font data. The broken ideograph font will use the horizontal width of glyphs
430 // to make sure you get a square (even for broken glyphs like symbols used for punctuation).
431 return glyphDataForVariant(character, description, BrokenIdeographVariant, fallbackIndex);
432 }
433 }
434 } else {
435 // The variantFont function should not normally return 0.
436 // But if it does, we will just render the capital letter big.
437 if (const Font* variantFont = data.font->variantFont(description, variant))
438 return variantFont->glyphDataForCharacter(character);
439 }
440
441 return data;
442 }
443
444 if (loadingResult.font)
445 return loadingResult;
446 return glyphDataForSystemFallback(character, description, variant, fallbackVisibility == FallbackVisibility::Invisible);
447}
448
449static RefPtr<GlyphPage> glyphPageFromFontRanges(unsigned pageNumber, const FontRanges& fontRanges)
450{
451 const Font* font = nullptr;
452 UChar32 pageRangeFrom = pageNumber * GlyphPage::size;
453 UChar32 pageRangeTo = pageRangeFrom + GlyphPage::size - 1;
454 auto policy = ExternalResourceDownloadPolicy::Allow;
455 FallbackVisibility desiredVisibility = FallbackVisibility::Immaterial;
456 for (unsigned i = 0; i < fontRanges.size(); ++i) {
457 auto& range = fontRanges.rangeAt(i);
458 if (range.from() <= pageRangeFrom && pageRangeTo <= range.to()) {
459 font = range.font(policy);
460 if (!font)
461 continue;
462 if (font->isInterstitial()) {
463 if (desiredVisibility == FallbackVisibility::Immaterial) {
464 auto fontVisibility = font->visibility();
465 if (fontVisibility == Font::Visibility::Visible)
466 desiredVisibility = FallbackVisibility::Visible;
467 else {
468 ASSERT(fontVisibility == Font::Visibility::Invisible);
469 desiredVisibility = FallbackVisibility::Invisible;
470 }
471 }
472 font = nullptr;
473 policy = ExternalResourceDownloadPolicy::Forbid;
474 continue;
475 }
476 }
477 break;
478 }
479 if (!font || font->platformData().orientation() == FontOrientation::Vertical)
480 return nullptr;
481
482 if (desiredVisibility == FallbackVisibility::Invisible && font->visibility() == Font::Visibility::Visible)
483 return const_cast<GlyphPage*>(font->invisibleFont().glyphPage(pageNumber));
484 return const_cast<GlyphPage*>(font->glyphPage(pageNumber));
485}
486
487GlyphData FontCascadeFonts::glyphDataForCharacter(UChar32 c, const FontCascadeDescription& description, FontVariant variant)
488{
489 ASSERT(isMainThread());
490 ASSERT(variant != AutoVariant);
491
492 if (variant != NormalVariant)
493 return glyphDataForVariant(c, description, variant);
494
495 const unsigned pageNumber = GlyphPage::pageNumberForCodePoint(c);
496
497 auto& cacheEntry = pageNumber ? m_cachedPages.add(pageNumber, GlyphPageCacheEntry()).iterator->value : m_cachedPageZero;
498
499 // Initialize cache with a full page of glyph mappings from a single font.
500 if (cacheEntry.isNull())
501 cacheEntry.setSingleFontPage(glyphPageFromFontRanges(pageNumber, realizeFallbackRangesAt(description, 0)));
502
503 GlyphData glyphData = cacheEntry.glyphDataForCharacter(c);
504 if (!glyphData.glyph) {
505 // No glyph, resolve per-character.
506 ASSERT(variant == NormalVariant);
507 glyphData = glyphDataForVariant(c, description, variant);
508 // Cache the results.
509 cacheEntry.setGlyphDataForCharacter(c, glyphData);
510 }
511
512 return glyphData;
513}
514
515void FontCascadeFonts::pruneSystemFallbacks()
516{
517 if (m_systemFallbackFontSet.isEmpty())
518 return;
519 // Mutable glyph pages may reference fallback fonts.
520 if (m_cachedPageZero.isMixedFont())
521 m_cachedPageZero = { };
522 m_cachedPages.removeIf([](auto& keyAndValue) {
523 return keyAndValue.value.isMixedFont();
524 });
525 m_systemFallbackFontSet.clear();
526}
527
528}
529