blob: 450a8dc0852ecce96d9a7dbcfd2cf5f260ec7e45 [file] [log] [blame]
Avi Drissman3e1a26c2022-09-15 20:26:031// Copyright 2019 The Chromium Authors
Dominik Röttsches6c69c7a2019-06-19 13:03:532// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Elly Fong-Jonesa8ea66f12024-07-24 18:37:105#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öttsches6c69c7a2019-06-19 13:03:5310#include "ui/gfx/font_fallback_skia_impl.h"
11
12#include <set>
13#include <string>
Helmut Januschka36619c12024-04-24 14:33:1914#include <string_view>
Dominik Röttsches6c69c7a2019-06-19 13:03:5315
Kevin Lubickeb3a85d2023-11-17 14:08:5216#include "skia/ext/font_utils.h"
Etienne Bergeron1c8d1c72019-10-25 21:43:1817#include "third_party/icu/source/common/unicode/normalizer2.h"
Dominik Röttsches6c69c7a2019-06-19 13:03:5318#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
23namespace gfx {
24
Etienne Bergeron1c8d1c72019-10-25 21:43:1825namespace {
26
27// Returns true when the codepoint has an unicode decomposition and store
28// the decomposed string into |output|.
29bool 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 Januschka36619c12024-04-24 14:33:1945void RetrieveCodepointsAndDecomposedCodepoints(std::u16string_view text,
Etienne Bergeron1c8d1c72019-10-25 21:43:1846 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 Januschka36619c12024-04-24 14:33:1967size_t ComputeMissingGlyphsForGivenTypeface(std::u16string_view text,
Etienne Bergeron1c8d1c72019-10-25 21:43:1868 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 Bergeron98ba22e2020-03-04 20:14:0880 // 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 Bergeron1c8d1c72019-10-25 21:43:1885 // 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öttsches5a32dbc2019-09-27 17:15:28113sk_sp<SkTypeface> GetSkiaFallbackTypeface(const Font& template_font,
Dominik Röttsches6c69c7a2019-06-19 13:03:53114 const std::string& locale,
Helmut Januschka36619c12024-04-24 14:33:19115 std::u16string_view text) {
Dominik Röttsches6c69c7a2019-06-19 13:03:53116 if (text.empty())
Dominik Röttsches5a32dbc2019-09-27 17:15:28117 return nullptr;
Dominik Röttsches6c69c7a2019-06-19 13:03:53118
Kevin Lubickeb3a85d2023-11-17 14:08:52119 sk_sp<SkFontMgr> font_mgr(skia::DefaultFontMgr());
Dominik Röttsches6c69c7a2019-06-19 13:03:53120
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 Lubickc3e9e5c2023-11-01 20:41:39133 std::set<SkTypefaceID> tested_typeface;
Dominik Röttsches5a32dbc2019-09-27 17:15:28134 sk_sp<SkTypeface> fallback_typeface;
Dominik Röttsches6c69c7a2019-06-19 13:03:53135 size_t fewest_missing_glyphs = text.length() + 1;
136
Etienne Bergeron1c8d1c72019-10-25 21:43:18137 // 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öttsches6c69c7a2019-06-19 13:03:53141
Etienne Bergeron1c8d1c72019-10-25 21:43:18142 // Determine which fallback font is given the fewer missing glyphs.
143 for (UChar32 codepoint : codepoints) {
Dominik Röttsches6c69c7a2019-06-19 13:03:53144 sk_sp<SkTypeface> typeface(font_mgr->matchFamilyStyleCharacter(
145 template_font.GetFontName().c_str(), skia_style, locales, num_locales,
Etienne Bergeron1c8d1c72019-10-25 21:43:18146 codepoint));
Dominik Röttsches6c69c7a2019-06-19 13:03:53147 // 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 Bergeron1c8d1c72019-10-25 21:43:18151 // Validate that every codepoint has a known glyph in the font.
152 size_t missing_glyphs =
153 ComputeMissingGlyphsForGivenTypeface(text, typeface);
Dominik Röttsches6c69c7a2019-06-19 13:03:53154 if (missing_glyphs < fewest_missing_glyphs) {
155 fewest_missing_glyphs = missing_glyphs;
Dominik Röttsches5a32dbc2019-09-27 17:15:28156 fallback_typeface = typeface;
Dominik Röttsches6c69c7a2019-06-19 13:03:53157 }
158
159 // The font is a valid fallback font for the given text.
160 if (missing_glyphs == 0)
161 break;
162 }
163
Dominik Röttsches5a32dbc2019-09-27 17:15:28164 return fallback_typeface;
Dominik Röttsches6c69c7a2019-06-19 13:03:53165}
166
167} // namespace gfx