1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/renderer_host/dwrite_font_proxy_impl_win.h"
6
7 #include <dwrite.h>
8 #include <dwrite_2.h>
9
10 #include <memory>
11
12 #include "base/file_version_info.h"
13 #include "base/files/file.h"
14 #include "base/files/scoped_temp_dir.h"
15 #include "base/memory/ref_counted.h"
16 #include "base/run_loop.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/synchronization/waitable_event.h"
19 #include "base/test/scoped_feature_list.h"
20 #include "base/test/task_environment.h"
21 #include "base/threading/thread_task_runner_handle.h"
22 #include "base/win/windows_version.h"
23 #include "content/public/common/content_features.h"
24 #include "content/public/test/browser_task_environment.h"
25 #include "mojo/public/cpp/bindings/receiver.h"
26 #include "mojo/public/cpp/bindings/remote.h"
27 #include "services/service_manager/public/cpp/bind_source_info.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29 #include "third_party/blink/public/common/dwrite_rasterizer_support/dwrite_rasterizer_support.h"
30 #include "third_party/blink/public/common/font_unique_name_lookup/font_table_matcher.h"
31 #include "third_party/icu/source/common/unicode/umachine.h"
32 #include "ui/gfx/test/font_fallback_test_data.h"
33
34 namespace content {
35
36 namespace {
37
38 struct FontExpectation {
39 const char font_name[64];
40 uint16_t ttc_index;
41 };
42
43 constexpr FontExpectation kExpectedTestFonts[] = {{u8"CambriaMath", 1},
44 {u8"Ming-Lt-HKSCS-ExtB", 2},
45 {u8"NSimSun", 1},
46 {u8"calibri-bolditalic", 0}};
47
48 // DirectWrite on Windows supports IDWriteFontSet API which allows for querying
49 // by PostScript name and full font name directly. In the implementation of
50 // DWriteFontProxy we check whether this API is available by checking for
51 // whether IDWriteFactory3 is available. In order to validate in a unit test
52 // whether this check works, compare it against the dwrite.dll major version -
53 // versions starting from 10 have the required functionality.
54 constexpr int kDWriteMajorVersionSupportingSingleLookups = 10;
55
56 // Base test class that sets up the Mojo connection to DWriteFontProxy so that
57 // tests can call its Mojo methods.
58 class DWriteFontProxyImplUnitTest : public testing::Test {
59 public:
DWriteFontProxyImplUnitTest()60 DWriteFontProxyImplUnitTest()
61 : receiver_(&impl_, dwrite_font_proxy_.BindNewPipeAndPassReceiver()) {}
62
dwrite_font_proxy()63 blink::mojom::DWriteFontProxy& dwrite_font_proxy() {
64 return *dwrite_font_proxy_;
65 }
66
SupportsSingleLookups()67 bool SupportsSingleLookups() {
68 blink::mojom::UniqueFontLookupMode lookup_mode;
69 dwrite_font_proxy().GetUniqueFontLookupMode(&lookup_mode);
70 return lookup_mode == blink::mojom::UniqueFontLookupMode::kSingleLookups;
71 }
72
73 base::test::TaskEnvironment task_environment_;
74 mojo::Remote<blink::mojom::DWriteFontProxy> dwrite_font_proxy_;
75 DWriteFontProxyImpl impl_;
76 mojo::Receiver<blink::mojom::DWriteFontProxy> receiver_;
77 };
78
79 // Derived class for tests that exercise font unique local matching mojo methods
80 // of DWriteFontProxy. Needs a ScopedFeatureList to activate the feature as it
81 // is currently behind a flag.
82 class DWriteFontProxyLocalMatchingTest : public DWriteFontProxyImplUnitTest {
83 public:
DWriteFontProxyLocalMatchingTest()84 DWriteFontProxyLocalMatchingTest() {
85 feature_list_.InitAndEnableFeature(features::kFontSrcLocalMatching);
86 }
87
88 private:
89 base::test::ScopedFeatureList feature_list_;
90 };
91
92 // Derived class for tests that exercise the parts of the DWriteFontProxy Mojo
93 // interface that deal with accessing the font lookup table created by
94 // DWriteFontLookupTableBuilder. Initializes the DWriteFontLookupTableBuilder
95 // and has a ScopedTempDir for testing persisting the lookup table to disk.
96 class DWriteFontProxyTableMatchingTest
97 : public DWriteFontProxyLocalMatchingTest {
98 public:
SetUp()99 void SetUp() override {
100 DWriteFontLookupTableBuilder* table_builder_instance =
101 DWriteFontLookupTableBuilder::GetInstance();
102 bool temp_dir_success = scoped_temp_dir_.CreateUniqueTempDir();
103 ASSERT_TRUE(temp_dir_success);
104 table_builder_instance->OverrideDWriteVersionChecksForTesting();
105 table_builder_instance->SetCacheDirectoryForTesting(
106 scoped_temp_dir_.GetPath());
107 table_builder_instance->ResetLookupTableForTesting();
108 table_builder_instance->SchedulePrepareFontUniqueNameTableIfNeeded();
109 }
110
TearDown()111 void TearDown() override {
112 DWriteFontLookupTableBuilder* table_builder_instance =
113 DWriteFontLookupTableBuilder::GetInstance();
114 table_builder_instance->ResetStateForTesting();
115 }
116
117 private:
118 base::ScopedTempDir scoped_temp_dir_;
119 };
120
TEST_F(DWriteFontProxyImplUnitTest,GetFamilyCount)121 TEST_F(DWriteFontProxyImplUnitTest, GetFamilyCount) {
122 UINT32 family_count = 0;
123 dwrite_font_proxy().GetFamilyCount(&family_count);
124 EXPECT_NE(0u, family_count); // Assume there's some fonts on the test system.
125 }
126
TEST_F(DWriteFontProxyImplUnitTest,FindFamily)127 TEST_F(DWriteFontProxyImplUnitTest, FindFamily) {
128 UINT32 arial_index = 0;
129 dwrite_font_proxy().FindFamily(L"Arial", &arial_index);
130 EXPECT_NE(UINT_MAX, arial_index);
131
132 UINT32 times_index = 0;
133 dwrite_font_proxy().FindFamily(L"Times New Roman", ×_index);
134 EXPECT_NE(UINT_MAX, times_index);
135 EXPECT_NE(arial_index, times_index);
136
137 UINT32 unknown_index = 0;
138 dwrite_font_proxy().FindFamily(L"Not a font family", &unknown_index);
139 EXPECT_EQ(UINT_MAX, unknown_index);
140 }
141
TEST_F(DWriteFontProxyImplUnitTest,GetFamilyNames)142 TEST_F(DWriteFontProxyImplUnitTest, GetFamilyNames) {
143 UINT32 arial_index = 0;
144 dwrite_font_proxy().FindFamily(L"Arial", &arial_index);
145
146 std::vector<blink::mojom::DWriteStringPairPtr> names;
147 dwrite_font_proxy().GetFamilyNames(arial_index, &names);
148
149 EXPECT_LT(0u, names.size());
150 for (const auto& pair : names) {
151 EXPECT_FALSE(pair->first.empty());
152 EXPECT_FALSE(pair->second.empty());
153 }
154 }
155
TEST_F(DWriteFontProxyImplUnitTest,GetFamilyNamesIndexOutOfBounds)156 TEST_F(DWriteFontProxyImplUnitTest, GetFamilyNamesIndexOutOfBounds) {
157 std::vector<blink::mojom::DWriteStringPairPtr> names;
158 UINT32 invalid_index = 1000000;
159 dwrite_font_proxy().GetFamilyNames(invalid_index, &names);
160
161 EXPECT_TRUE(names.empty());
162 }
163
TEST_F(DWriteFontProxyImplUnitTest,GetFontFiles)164 TEST_F(DWriteFontProxyImplUnitTest, GetFontFiles) {
165 UINT32 arial_index = 0;
166 dwrite_font_proxy().FindFamily(L"Arial", &arial_index);
167
168 std::vector<base::FilePath> files;
169 std::vector<base::File> handles;
170 dwrite_font_proxy().GetFontFiles(arial_index, &files, &handles);
171
172 EXPECT_LT(0u, files.size());
173 for (const auto& file : files) {
174 EXPECT_FALSE(file.value().empty());
175 }
176 }
177
TEST_F(DWriteFontProxyImplUnitTest,GetFontFilesIndexOutOfBounds)178 TEST_F(DWriteFontProxyImplUnitTest, GetFontFilesIndexOutOfBounds) {
179 std::vector<base::FilePath> files;
180 std::vector<base::File> handles;
181 UINT32 invalid_index = 1000000;
182 dwrite_font_proxy().GetFontFiles(invalid_index, &files, &handles);
183
184 EXPECT_EQ(0u, files.size());
185 }
186
TEST_F(DWriteFontProxyImplUnitTest,MapCharacter)187 TEST_F(DWriteFontProxyImplUnitTest, MapCharacter) {
188 if (!blink::DWriteRasterizerSupport::IsDWriteFactory2Available())
189 return;
190
191 blink::mojom::MapCharactersResultPtr result;
192 dwrite_font_proxy().MapCharacters(
193 L"abc",
194 blink::mojom::DWriteFontStyle::New(DWRITE_FONT_WEIGHT_NORMAL,
195 DWRITE_FONT_STYLE_NORMAL,
196 DWRITE_FONT_STRETCH_NORMAL),
197 L"", DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, L"", &result);
198
199 EXPECT_NE(UINT32_MAX, result->family_index);
200 EXPECT_STRNE(L"", result->family_name.c_str());
201 EXPECT_EQ(3u, result->mapped_length);
202 EXPECT_NE(0.0, result->scale);
203 EXPECT_NE(0, result->font_style->font_weight);
204 EXPECT_EQ(DWRITE_FONT_STYLE_NORMAL, result->font_style->font_slant);
205 EXPECT_NE(0, result->font_style->font_stretch);
206 }
207
TEST_F(DWriteFontProxyImplUnitTest,MapCharacterInvalidCharacter)208 TEST_F(DWriteFontProxyImplUnitTest, MapCharacterInvalidCharacter) {
209 if (!blink::DWriteRasterizerSupport::IsDWriteFactory2Available())
210 return;
211
212 blink::mojom::MapCharactersResultPtr result;
213 dwrite_font_proxy().MapCharacters(
214 L"\ufffe\uffffabc",
215 blink::mojom::DWriteFontStyle::New(DWRITE_FONT_WEIGHT_NORMAL,
216 DWRITE_FONT_STYLE_NORMAL,
217 DWRITE_FONT_STRETCH_NORMAL),
218 L"en-us", DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, L"", &result);
219
220 EXPECT_EQ(UINT32_MAX, result->family_index);
221 EXPECT_STREQ(L"", result->family_name.c_str());
222 EXPECT_EQ(2u, result->mapped_length);
223 }
224
TEST_F(DWriteFontProxyImplUnitTest,MapCharacterInvalidAfterValid)225 TEST_F(DWriteFontProxyImplUnitTest, MapCharacterInvalidAfterValid) {
226 if (!blink::DWriteRasterizerSupport::IsDWriteFactory2Available())
227 return;
228
229 blink::mojom::MapCharactersResultPtr result;
230 dwrite_font_proxy().MapCharacters(
231 L"abc\ufffe\uffff",
232 blink::mojom::DWriteFontStyle::New(DWRITE_FONT_WEIGHT_NORMAL,
233 DWRITE_FONT_STYLE_NORMAL,
234 DWRITE_FONT_STRETCH_NORMAL),
235 L"en-us", DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, L"", &result);
236
237 EXPECT_NE(UINT32_MAX, result->family_index);
238 EXPECT_STRNE(L"", result->family_name.c_str());
239 EXPECT_EQ(3u, result->mapped_length);
240 EXPECT_NE(0.0, result->scale);
241 EXPECT_NE(0, result->font_style->font_weight);
242 EXPECT_EQ(DWRITE_FONT_STYLE_NORMAL, result->font_style->font_slant);
243 EXPECT_NE(0, result->font_style->font_stretch);
244 }
245
TEST_F(DWriteFontProxyImplUnitTest,TestCustomFontFiles)246 TEST_F(DWriteFontProxyImplUnitTest, TestCustomFontFiles) {
247 // Override windows fonts path to force the custom font file codepath.
248 impl_.SetWindowsFontsPathForTesting(L"X:\\NotWindowsFonts");
249
250 UINT32 arial_index = 0;
251 dwrite_font_proxy().FindFamily(L"Arial", &arial_index);
252
253 std::vector<base::FilePath> files;
254 std::vector<base::File> handles;
255 dwrite_font_proxy().GetFontFiles(arial_index, &files, &handles);
256
257 EXPECT_TRUE(files.empty());
258 EXPECT_FALSE(handles.empty());
259 for (auto& file : handles) {
260 EXPECT_TRUE(file.IsValid());
261 EXPECT_LT(0, file.GetLength()); // Check the file exists
262 }
263 }
264
TEST_F(DWriteFontProxyImplUnitTest,FallbackFamily)265 TEST_F(DWriteFontProxyImplUnitTest, FallbackFamily) {
266 const bool on_win10 = base::win::GetVersion() >= base::win::Version::WIN10;
267
268 for (auto& fallback_request : gfx::kGetFontFallbackTests) {
269 if (fallback_request.is_win10 && !on_win10)
270 continue;
271
272 blink::mojom::FallbackFamilyAndStylePtr fallback_family_and_style;
273 UChar32 codepoint;
274 U16_GET(fallback_request.text.c_str(), 0, 0, fallback_request.text.size(),
275 codepoint);
276 dwrite_font_proxy().FallbackFamilyAndStyleForCodepoint(
277 "Times New Roman", fallback_request.language_tag, codepoint,
278 &fallback_family_and_style);
279
280 auto find_result_it =
281 std::find(fallback_request.fallback_fonts.begin(),
282 fallback_request.fallback_fonts.end(),
283 fallback_family_and_style->fallback_family_name);
284
285 EXPECT_TRUE(find_result_it != fallback_request.fallback_fonts.end())
286 << "Did not find expected fallback font for language: "
287 << fallback_request.language_tag << ", codepoint U+" << std::hex
288 << codepoint << " DWrite returned font name: \""
289 << fallback_family_and_style->fallback_family_name << "\""
290 << ", expected: "
291 << base::JoinString(fallback_request.fallback_fonts, ", ");
292 EXPECT_EQ(fallback_family_and_style->weight, 400u);
293 EXPECT_EQ(fallback_family_and_style->width,
294 5u); // SkFontStyle::Width::kNormal_Width
295 EXPECT_EQ(fallback_family_and_style->slant,
296 0u); // SkFontStyle::Slant::kUpright_Slant
297 }
298 }
299
300 namespace {
TestWhenLookupTableReady(bool * did_test_fonts,base::ReadOnlySharedMemoryRegion font_table_memory)301 void TestWhenLookupTableReady(
302 bool* did_test_fonts,
303 base::ReadOnlySharedMemoryRegion font_table_memory) {
304 blink::FontTableMatcher font_table_matcher(font_table_memory.Map());
305 for (auto& test_font_name_index : kExpectedTestFonts) {
306 base::Optional<blink::FontTableMatcher::MatchResult> match_result =
307 font_table_matcher.MatchName(test_font_name_index.font_name);
308 ASSERT_TRUE(match_result)
309 << "No font matched for font name: " << test_font_name_index.font_name;
310 base::File unique_font_file(
311 base::FilePath::FromUTF8Unsafe(match_result->font_path),
312 base::File::FLAG_OPEN | base::File::FLAG_READ);
313 ASSERT_TRUE(unique_font_file.IsValid());
314 ASSERT_GT(unique_font_file.GetLength(), 0);
315 ASSERT_EQ(test_font_name_index.ttc_index, match_result->ttc_index);
316 *did_test_fonts = true;
317 }
318 }
319 } // namespace
320
TEST_F(DWriteFontProxyTableMatchingTest,TestFindUniqueFont)321 TEST_F(DWriteFontProxyTableMatchingTest, TestFindUniqueFont) {
322 bool lookup_table_results_were_tested = false;
323 dwrite_font_proxy().GetUniqueNameLookupTable(base::BindOnce(
324 &TestWhenLookupTableReady, &lookup_table_results_were_tested));
325 task_environment_.RunUntilIdle();
326 ASSERT_TRUE(lookup_table_results_were_tested);
327 }
328
TEST_F(DWriteFontProxyLocalMatchingTest,TestSingleLookup)329 TEST_F(DWriteFontProxyLocalMatchingTest, TestSingleLookup) {
330 // Do not run this test on unsupported Windows versions.
331 if (!SupportsSingleLookups())
332 return;
333 for (auto& test_font_name_index : kExpectedTestFonts) {
334 base::FilePath result_path;
335 uint32_t ttc_index;
336 dwrite_font_proxy().MatchUniqueFont(
337 base::UTF8ToUTF16(test_font_name_index.font_name), &result_path,
338 &ttc_index);
339 ASSERT_GT(result_path.value().size(), 0u);
340 base::File unique_font_file(result_path,
341 base::File::FLAG_OPEN | base::File::FLAG_READ);
342 ASSERT_TRUE(unique_font_file.IsValid());
343 ASSERT_GT(unique_font_file.GetLength(), 0);
344 ASSERT_EQ(test_font_name_index.ttc_index, ttc_index);
345 }
346 }
347
TEST_F(DWriteFontProxyLocalMatchingTest,TestSingleLookupUnavailable)348 TEST_F(DWriteFontProxyLocalMatchingTest, TestSingleLookupUnavailable) {
349 // Do not run this test on unsupported Windows versions.
350 if (!SupportsSingleLookups())
351 return;
352 base::FilePath result_path;
353 uint32_t ttc_index;
354 std::string unavailable_font_name =
355 "Unavailable_Font_Name_56E7EA7E-2C69-4E23-99DC-750BC19B250E";
356 dwrite_font_proxy().MatchUniqueFont(base::UTF8ToUTF16(unavailable_font_name),
357 &result_path, &ttc_index);
358 ASSERT_EQ(result_path.value().size(), 0u);
359 ASSERT_EQ(ttc_index, 0u);
360 }
361
TEST_F(DWriteFontProxyLocalMatchingTest,TestLookupMode)362 TEST_F(DWriteFontProxyLocalMatchingTest, TestLookupMode) {
363 std::unique_ptr<FileVersionInfo> dwrite_version_info =
364 FileVersionInfo::CreateFileVersionInfo(
365 base::FilePath(FILE_PATH_LITERAL("DWrite.dll")));
366
367 std::string dwrite_version =
368 base::WideToUTF8(dwrite_version_info->product_version());
369
370 int dwrite_major_version_number =
371 std::stoi(dwrite_version.substr(0, dwrite_version.find(".")));
372
373 blink::mojom::UniqueFontLookupMode expected_lookup_mode;
374 if (dwrite_major_version_number >=
375 kDWriteMajorVersionSupportingSingleLookups) {
376 expected_lookup_mode = blink::mojom::UniqueFontLookupMode::kSingleLookups;
377 } else {
378 expected_lookup_mode = blink::mojom::UniqueFontLookupMode::kRetrieveTable;
379 }
380
381 blink::mojom::UniqueFontLookupMode lookup_mode;
382 dwrite_font_proxy().GetUniqueFontLookupMode(&lookup_mode);
383 ASSERT_EQ(lookup_mode, expected_lookup_mode);
384 }
385
386 } // namespace
387
388 } // namespace content
389