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", &times_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