Avi Drissman | 3e1a26c | 2022-09-15 20:26:03 | [diff] [blame] | 1 | // Copyright 2019 The Chromium Authors |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Elly Fong-Jones | a8ea66f1 | 2024-07-24 18:37:10 | [diff] [blame] | 5 | #ifdef UNSAFE_BUFFERS_BUILD |
| 6 | // TODO(crbug.com/354829279): Remove this and convert code to safer constructs. |
| 7 | #pragma allow_unsafe_buffers |
| 8 | #endif |
| 9 | |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 10 | #include "ui/gfx/font_fallback_skia_impl.h" |
| 11 | |
| 12 | #include <set> |
| 13 | #include <string> |
Helmut Januschka | 36619c1 | 2024-04-24 14:33:19 | [diff] [blame] | 14 | #include <string_view> |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 15 | |
Kevin Lubick | eb3a85d | 2023-11-17 14:08:52 | [diff] [blame] | 16 | #include "skia/ext/font_utils.h" |
Etienne Bergeron | 1c8d1c7 | 2019-10-25 21:43:18 | [diff] [blame] | 17 | #include "third_party/icu/source/common/unicode/normalizer2.h" |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 18 | #include "third_party/icu/source/common/unicode/uchar.h" |
| 19 | #include "third_party/icu/source/common/unicode/utf16.h" |
| 20 | #include "third_party/skia/include/core/SkFontMgr.h" |
| 21 | #include "third_party/skia/include/core/SkTypeface.h" |
| 22 | |
| 23 | namespace gfx { |
| 24 | |
Etienne Bergeron | 1c8d1c7 | 2019-10-25 21:43:18 | [diff] [blame] | 25 | namespace { |
| 26 | |
| 27 | // Returns true when the codepoint has an unicode decomposition and store |
| 28 | // the decomposed string into |output|. |
| 29 | bool UnicodeDecomposeCodepoint(UChar32 codepoint, icu::UnicodeString* output) { |
| 30 | static const icu::Normalizer2* normalizer = nullptr; |
| 31 | |
| 32 | UErrorCode error = U_ZERO_ERROR; |
| 33 | if (!normalizer) { |
| 34 | normalizer = icu::Normalizer2::getNFDInstance(error); |
| 35 | if (U_FAILURE(error)) |
| 36 | return false; |
| 37 | DCHECK(normalizer); |
| 38 | } |
| 39 | |
| 40 | return normalizer->getDecomposition(codepoint, *output); |
| 41 | } |
| 42 | |
| 43 | // Extracts every codepoint and its decomposed codepoints from unicode |
| 44 | // decomposition. Inserts in |codepoints| the set of codepoints in |text|. |
Helmut Januschka | 36619c1 | 2024-04-24 14:33:19 | [diff] [blame] | 45 | void RetrieveCodepointsAndDecomposedCodepoints(std::u16string_view text, |
Etienne Bergeron | 1c8d1c7 | 2019-10-25 21:43:18 | [diff] [blame] | 46 | std::set<UChar32>* codepoints) { |
| 47 | size_t offset = 0; |
| 48 | while (offset < text.length()) { |
| 49 | UChar32 codepoint; |
| 50 | U16_NEXT(text.data(), offset, text.length(), codepoint); |
| 51 | |
| 52 | if (codepoints->insert(codepoint).second) { |
| 53 | // For each codepoint, add the decomposed codepoints. |
| 54 | icu::UnicodeString decomposed_text; |
| 55 | if (UnicodeDecomposeCodepoint(codepoint, &decomposed_text)) { |
| 56 | for (int i = 0; i < decomposed_text.length(); ++i) { |
| 57 | codepoints->insert(decomposed_text[i]); |
| 58 | } |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | // Returns the amount of codepoint in |text| without a glyph representation in |
| 65 | // |typeface|. A codepoint is present if there is a corresponding glyph in |
| 66 | // typeface, or if there are glyphs for each of its decomposed codepoints. |
Helmut Januschka | 36619c1 | 2024-04-24 14:33:19 | [diff] [blame] | 67 | size_t ComputeMissingGlyphsForGivenTypeface(std::u16string_view text, |
Etienne Bergeron | 1c8d1c7 | 2019-10-25 21:43:18 | [diff] [blame] | 68 | sk_sp<SkTypeface> typeface) { |
| 69 | // Validate that every character has a known glyph in the font. |
| 70 | size_t missing_glyphs = 0; |
| 71 | size_t i = 0; |
| 72 | while (i < text.length()) { |
| 73 | UChar32 codepoint; |
| 74 | U16_NEXT(text.data(), i, text.length(), codepoint); |
| 75 | |
| 76 | // The glyph is present in the font. |
| 77 | if (typeface->unicharToGlyph(codepoint) != 0) |
| 78 | continue; |
| 79 | |
Etienne Bergeron | 98ba22e | 2020-03-04 20:14:08 | [diff] [blame] | 80 | // Do not count missing codepoints when they are ignorable as they will be |
| 81 | // ignored by the shaping engine. |
| 82 | if (u_hasBinaryProperty(codepoint, UCHAR_DEFAULT_IGNORABLE_CODE_POINT)) |
| 83 | continue; |
| 84 | |
Etienne Bergeron | 1c8d1c7 | 2019-10-25 21:43:18 | [diff] [blame] | 85 | // No glyph is present in the font for the codepoint. Try the decomposed |
| 86 | // codepoints instead. |
| 87 | icu::UnicodeString decomposed_text; |
| 88 | if (UnicodeDecomposeCodepoint(codepoint, &decomposed_text) && |
| 89 | !decomposed_text.isEmpty()) { |
| 90 | // Check that every decomposed codepoint is in the font. |
| 91 | bool every_codepoint_found = true; |
| 92 | for (int offset = 0; offset < decomposed_text.length(); ++offset) { |
| 93 | if (typeface->unicharToGlyph(decomposed_text[offset]) == 0) { |
| 94 | every_codepoint_found = false; |
| 95 | break; |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | // The decomposed codepoints can be mapped to glyphs by the font. |
| 100 | if (every_codepoint_found) |
| 101 | continue; |
| 102 | } |
| 103 | |
| 104 | // The current glyphs can't be find. |
| 105 | ++missing_glyphs; |
| 106 | } |
| 107 | |
| 108 | return missing_glyphs; |
| 109 | } |
| 110 | |
| 111 | } // namespace |
| 112 | |
Dominik Röttsches | 5a32dbc | 2019-09-27 17:15:28 | [diff] [blame] | 113 | sk_sp<SkTypeface> GetSkiaFallbackTypeface(const Font& template_font, |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 114 | const std::string& locale, |
Helmut Januschka | 36619c1 | 2024-04-24 14:33:19 | [diff] [blame] | 115 | std::u16string_view text) { |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 116 | if (text.empty()) |
Dominik Röttsches | 5a32dbc | 2019-09-27 17:15:28 | [diff] [blame] | 117 | return nullptr; |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 118 | |
Kevin Lubick | eb3a85d | 2023-11-17 14:08:52 | [diff] [blame] | 119 | sk_sp<SkFontMgr> font_mgr(skia::DefaultFontMgr()); |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 120 | |
| 121 | const char* bcp47_locales[] = {locale.c_str()}; |
| 122 | int num_locales = locale.empty() ? 0 : 1; |
| 123 | const char** locales = locale.empty() ? nullptr : bcp47_locales; |
| 124 | |
| 125 | const int font_weight = (template_font.GetWeight() == Font::Weight::INVALID) |
| 126 | ? static_cast<int>(Font::Weight::NORMAL) |
| 127 | : static_cast<int>(template_font.GetWeight()); |
| 128 | const bool italic = (template_font.GetStyle() & Font::ITALIC) != 0; |
| 129 | SkFontStyle skia_style( |
| 130 | font_weight, SkFontStyle::kNormal_Width, |
| 131 | italic ? SkFontStyle::kItalic_Slant : SkFontStyle::kUpright_Slant); |
| 132 | |
Kevin Lubick | c3e9e5c | 2023-11-01 20:41:39 | [diff] [blame] | 133 | std::set<SkTypefaceID> tested_typeface; |
Dominik Röttsches | 5a32dbc | 2019-09-27 17:15:28 | [diff] [blame] | 134 | sk_sp<SkTypeface> fallback_typeface; |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 135 | size_t fewest_missing_glyphs = text.length() + 1; |
| 136 | |
Etienne Bergeron | 1c8d1c7 | 2019-10-25 21:43:18 | [diff] [blame] | 137 | // Retrieve the set of codepoints (or unicode decomposed codepoints) from |
| 138 | // the input text. |
| 139 | std::set<UChar32> codepoints; |
| 140 | RetrieveCodepointsAndDecomposedCodepoints(text, &codepoints); |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 141 | |
Etienne Bergeron | 1c8d1c7 | 2019-10-25 21:43:18 | [diff] [blame] | 142 | // Determine which fallback font is given the fewer missing glyphs. |
| 143 | for (UChar32 codepoint : codepoints) { |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 144 | sk_sp<SkTypeface> typeface(font_mgr->matchFamilyStyleCharacter( |
| 145 | template_font.GetFontName().c_str(), skia_style, locales, num_locales, |
Etienne Bergeron | 1c8d1c7 | 2019-10-25 21:43:18 | [diff] [blame] | 146 | codepoint)); |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 147 | // If the typeface is not found or was already tested, skip it. |
| 148 | if (!typeface || !tested_typeface.insert(typeface->uniqueID()).second) |
| 149 | continue; |
| 150 | |
Etienne Bergeron | 1c8d1c7 | 2019-10-25 21:43:18 | [diff] [blame] | 151 | // Validate that every codepoint has a known glyph in the font. |
| 152 | size_t missing_glyphs = |
| 153 | ComputeMissingGlyphsForGivenTypeface(text, typeface); |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 154 | if (missing_glyphs < fewest_missing_glyphs) { |
| 155 | fewest_missing_glyphs = missing_glyphs; |
Dominik Röttsches | 5a32dbc | 2019-09-27 17:15:28 | [diff] [blame] | 156 | fallback_typeface = typeface; |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 157 | } |
| 158 | |
| 159 | // The font is a valid fallback font for the given text. |
| 160 | if (missing_glyphs == 0) |
| 161 | break; |
| 162 | } |
| 163 | |
Dominik Röttsches | 5a32dbc | 2019-09-27 17:15:28 | [diff] [blame] | 164 | return fallback_typeface; |
Dominik Röttsches | 6c69c7a | 2019-06-19 13:03:53 | [diff] [blame] | 165 | } |
| 166 | |
| 167 | } // namespace gfx |