| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <utility> |
| |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "components/services/font/public/cpp/font_loader.h" |
| #include "components/services/font/public/cpp/manifest.h" |
| #include "components/services/font/public/interfaces/constants.mojom.h" |
| #include "components/services/font/public/interfaces/font_service.mojom.h" |
| #include "ppapi/buildflags/buildflags.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| #include "services/service_manager/public/cpp/manifest_builder.h" |
| #include "services/service_manager/public/cpp/test/test_service.h" |
| #include "services/service_manager/public/cpp/test/test_service_manager.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkFontStyle.h" |
| |
| #if BUILDFLAG(ENABLE_PLUGINS) |
| #include <ft2build.h> |
| #include <freetype/freetype.h> |
| #include "ppapi/c/private/pp_private_font_charset.h" // nogncheck |
| #endif |
| |
| namespace font_service { |
| namespace { |
| |
| bool IsInTestFontDirectory(const char* path) { |
| const char kTestFontsDir[] = "test_fonts"; |
| return std::string(path).find(kTestFontsDir) != std::string::npos; |
| } |
| |
| #if BUILDFLAG(ENABLE_PLUGINS) |
| std::string GetPostscriptNameFromFile(base::File& font_file) { |
| int64_t file_size = font_file.GetLength(); |
| if (!file_size) |
| return ""; |
| |
| std::vector<char> file_contents; |
| file_contents.reserve(file_size); |
| CHECK_EQ(file_size, font_file.Read(0, file_contents.data(), file_size)); |
| std::string font_family_name; |
| FT_Library library; |
| FT_Init_FreeType(&library); |
| FT_Face font_face; |
| FT_Open_Args open_args = {FT_OPEN_MEMORY, |
| reinterpret_cast<FT_Byte*>(file_contents.data()), |
| file_size}; |
| CHECK_EQ(FT_Err_Ok, FT_Open_Face(library, &open_args, 0, &font_face)); |
| font_family_name = FT_Get_Postscript_Name(font_face); |
| FT_Done_Face(font_face); |
| FT_Done_FreeType(library); |
| return font_family_name; |
| } |
| #endif |
| |
| const char kTestServiceName[] = "font_service_unittests"; |
| |
| class FontLoaderTest : public testing::Test { |
| public: |
| FontLoaderTest() |
| : test_service_manager_( |
| {GetManifest(), |
| service_manager::ManifestBuilder() |
| .WithServiceName(kTestServiceName) |
| .RequireCapability(mojom::kServiceName, "font_service") |
| .Build()}), |
| test_service_( |
| test_service_manager_.RegisterTestInstance(kTestServiceName)), |
| font_loader_(test_service_.connector()) {} |
| ~FontLoaderTest() override = default; |
| |
| protected: |
| FontLoader* font_loader() { return &font_loader_; } |
| |
| private: |
| base::test::ScopedTaskEnvironment task_environment_; |
| service_manager::TestServiceManager test_service_manager_; |
| service_manager::TestService test_service_; |
| FontLoader font_loader_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FontLoaderTest); |
| }; |
| |
| } // namespace |
| |
| TEST_F(FontLoaderTest, BasicMatchingTest) { |
| SkFontStyle styles[] = { |
| SkFontStyle(SkFontStyle::kNormal_Weight, SkFontStyle::kNormal_Width, |
| SkFontStyle::kUpright_Slant), |
| SkFontStyle(SkFontStyle::kBold_Weight, SkFontStyle::kNormal_Width, |
| SkFontStyle::kUpright_Slant), |
| SkFontStyle(SkFontStyle::kBold_Weight, SkFontStyle::kNormal_Width, |
| SkFontStyle::kItalic_Slant)}; |
| // See kFontsConfTemplate[] in fontconfig_util_linux.cc for details of which |
| // fonts can be picked. Arial, Times New Roman and Courier New are aliased to |
| // Arimos, Tinos and Cousine ChromeOS open source alternatives when FontConfig |
| // is set up for testing. |
| std::vector<std::vector<std::string>> family_names_expected_names = { |
| {"Arial", "Arimo"}, |
| {"Times New Roman", "Tinos"}, |
| {"Courier New", "Cousine"}}; |
| for (std::vector<std::string> request_family_name : |
| family_names_expected_names) { |
| for (auto& request_style : styles) { |
| SkFontConfigInterface::FontIdentity font_identity; |
| SkFontStyle result_style; |
| SkString result_family_name; |
| font_loader()->matchFamilyName(request_family_name[0].c_str(), |
| request_style, &font_identity, |
| &result_family_name, &result_style); |
| EXPECT_EQ(request_family_name[1], |
| std::string(result_family_name.c_str())); |
| EXPECT_TRUE(IsInTestFontDirectory(font_identity.fString.c_str())); |
| EXPECT_EQ(result_style, request_style); |
| } |
| } |
| } |
| |
| TEST_F(FontLoaderTest, NotFoundTest) { |
| std::string request_family_name = {"IMPROBABLE_FONT_NAME"}; |
| SkFontConfigInterface::FontIdentity font_identity; |
| SkFontStyle result_style; |
| SkString result_family_name; |
| font_loader()->matchFamilyName(request_family_name.c_str(), SkFontStyle(), |
| &font_identity, &result_family_name, |
| &result_style); |
| EXPECT_EQ("", std::string(result_family_name.c_str())); |
| EXPECT_EQ(0u, font_identity.fID); |
| EXPECT_EQ("", std::string(font_identity.fString.c_str())); |
| } |
| |
| TEST_F(FontLoaderTest, EmptyFontName) { |
| std::string request_family_name = {""}; |
| std::string kDefaultFontName = "DejaVu Sans"; |
| SkFontConfigInterface::FontIdentity font_identity; |
| SkFontStyle result_style; |
| SkString result_family_name; |
| font_loader()->matchFamilyName(request_family_name.c_str(), SkFontStyle(), |
| &font_identity, &result_family_name, |
| &result_style); |
| EXPECT_EQ(kDefaultFontName, std::string(result_family_name.c_str())); |
| EXPECT_TRUE(IsInTestFontDirectory(font_identity.fString.c_str())); |
| } |
| |
| TEST_F(FontLoaderTest, CharacterFallback) { |
| std::pair<uint32_t, std::string> fallback_characters_families[] = { |
| // A selection of character hitting fonts from the third_party/test_fonts |
| // portfolio. |
| {0x662F /* CJK UNIFIED IDEOGRAPH-662F */, "Noto Sans CJK JP"}, |
| {0x1780 /* KHMER LETTER KA */, "Noto Sans Khmer"}, |
| {0xA05 /* GURMUKHI LETTER A */, "Lohit Gurmukhi"}, |
| {0xB85 /* TAMIL LETTER A */, "Lohit Tamil"}, |
| {0x904 /* DEVANAGARI LETTER SHORT A */, "Lohit Devanagari"}, |
| {0x985 /* BENGALI LETTER A */, "Mukti Narrow"}, |
| // Tests for not finding fallback: |
| {0x13170 /* EGYPTIAN HIEROGLYPH G042 */, ""}, |
| {0x1817 /* MONGOLIAN DIGIT SEVEN */, ""}}; |
| for (auto& character_family : fallback_characters_families) { |
| mojom::FontIdentityPtr font_identity; |
| std::string result_family_name; |
| bool is_bold; |
| bool is_italic; |
| font_loader()->FallbackFontForCharacter( |
| std::move(character_family.first), "en_US", &font_identity, |
| &result_family_name, &is_bold, &is_italic); |
| EXPECT_EQ(result_family_name, character_family.second); |
| EXPECT_FALSE(is_bold); |
| EXPECT_FALSE(is_italic); |
| if (character_family.second.size()) { |
| EXPECT_TRUE( |
| IsInTestFontDirectory(font_identity->str_representation.c_str())); |
| } else { |
| EXPECT_EQ(font_identity->str_representation.size(), 0u); |
| EXPECT_EQ(result_family_name, ""); |
| } |
| } |
| } |
| |
| TEST_F(FontLoaderTest, RenderStyleForStrike) { |
| // Use FontConfig configured test font aliases from kFontsConfTemplate in |
| // fontconfig_util_linux.cc. |
| std::pair<std::string, mojom::FontRenderStylePtr> families_styles[] = { |
| {"NonAntiAliasedSans", |
| mojom::FontRenderStyle::New( |
| mojom::RenderStyleSwitch::ON, mojom::RenderStyleSwitch::OFF, |
| mojom::RenderStyleSwitch::ON, 3, mojom::RenderStyleSwitch::OFF, |
| mojom::RenderStyleSwitch::OFF, mojom::RenderStyleSwitch::OFF)}, |
| {"SlightHintedGeorgia", |
| mojom::FontRenderStyle::New( |
| mojom::RenderStyleSwitch::ON, mojom::RenderStyleSwitch::OFF, |
| mojom::RenderStyleSwitch::ON, 1, mojom::RenderStyleSwitch::ON, |
| mojom::RenderStyleSwitch::OFF, mojom::RenderStyleSwitch::OFF)}, |
| {"NonHintedSans", |
| mojom::FontRenderStyle::New( |
| mojom::RenderStyleSwitch::ON, mojom::RenderStyleSwitch::OFF, |
| mojom::RenderStyleSwitch::OFF, 0, mojom::RenderStyleSwitch::ON, |
| mojom::RenderStyleSwitch::OFF, mojom::RenderStyleSwitch::OFF)}, |
| {"AutohintedSerif", |
| mojom::FontRenderStyle::New( |
| mojom::RenderStyleSwitch::ON, mojom::RenderStyleSwitch::ON, |
| mojom::RenderStyleSwitch::ON, 2, mojom::RenderStyleSwitch::ON, |
| mojom::RenderStyleSwitch::OFF, mojom::RenderStyleSwitch::OFF)}, |
| {"HintedSerif", |
| mojom::FontRenderStyle::New( |
| mojom::RenderStyleSwitch::ON, mojom::RenderStyleSwitch::OFF, |
| mojom::RenderStyleSwitch::ON, 2, mojom::RenderStyleSwitch::ON, |
| mojom::RenderStyleSwitch::OFF, mojom::RenderStyleSwitch::OFF)}, |
| {"FullAndAutoHintedSerif", |
| mojom::FontRenderStyle::New( |
| mojom::RenderStyleSwitch::ON, mojom::RenderStyleSwitch::ON, |
| mojom::RenderStyleSwitch::ON, 3, mojom::RenderStyleSwitch::ON, |
| mojom::RenderStyleSwitch::OFF, mojom::RenderStyleSwitch::OFF)}, |
| {"SubpixelEnabledArial", |
| mojom::FontRenderStyle::New( |
| mojom::RenderStyleSwitch::ON, mojom::RenderStyleSwitch::OFF, |
| mojom::RenderStyleSwitch::ON, 3, mojom::RenderStyleSwitch::ON, |
| mojom::RenderStyleSwitch::ON, mojom::RenderStyleSwitch::OFF)}, |
| {"SubpixelDisabledArial", |
| mojom::FontRenderStyle::New( |
| mojom::RenderStyleSwitch::ON, mojom::RenderStyleSwitch::OFF, |
| mojom::RenderStyleSwitch::ON, 3, mojom::RenderStyleSwitch::ON, |
| mojom::RenderStyleSwitch::OFF, mojom::RenderStyleSwitch::OFF)}}; |
| |
| for (auto& family_style : families_styles) { |
| mojom::FontRenderStylePtr result_style; |
| font_loader()->FontRenderStyleForStrike(std::move(family_style.first), 16, |
| false, false, 1.0, &result_style); |
| EXPECT_TRUE(result_style.Equals(family_style.second)); |
| } |
| } |
| |
| TEST_F(FontLoaderTest, PPAPIFallback) { |
| #if BUILDFLAG(ENABLE_PLUGINS) |
| std::tuple<std::string, uint32_t, std::string> family_charset_expectations[] = |
| { |
| {"", PP_PRIVATEFONTCHARSET_SHIFTJIS, "DejaVuSans"}, |
| {"", PP_PRIVATEFONTCHARSET_THAI, "Garuda"}, |
| {"", PP_PRIVATEFONTCHARSET_GB2312, "DejaVuSans"}, |
| {"", PP_PRIVATEFONTCHARSET_GREEK, "DejaVuSans"}, |
| {"Arial", PP_PRIVATEFONTCHARSET_DEFAULT, "Arimo-Regular"}, |
| {"Times New Roman", PP_PRIVATEFONTCHARSET_DEFAULT, "Tinos-Regular"}, |
| {"Courier New", PP_PRIVATEFONTCHARSET_DEFAULT, "Cousine-Regular"}, |
| }; |
| for (auto& family_charset_expectation : family_charset_expectations) { |
| base::File result_file; |
| font_loader()->MatchFontWithFallback( |
| std::move(std::get<0>(family_charset_expectation)), false, false, |
| std::move(std::get<1>(family_charset_expectation)), 0, &result_file); |
| EXPECT_TRUE(result_file.IsValid()); |
| EXPECT_EQ(GetPostscriptNameFromFile(result_file), |
| std::get<2>(family_charset_expectation)); |
| } |
| #endif |
| } |
| |
| TEST_F(FontLoaderTest, LocalMatching) { |
| // The following fonts are ensured to be available by the test harnesses |
| // global FontConfig setup which makes the fonts in third_party/test_fonts |
| // available. (See SetUpFontConfig() in TestSuite::Initialize(). |
| std::string postscript_names_test_fonts[] = {"Ahem", |
| "Arimo-Bold", |
| "Arimo-BoldItalic", |
| "Arimo-Italic", |
| "Arimo-Regular", |
| "Cousine-Bold", |
| "Cousine-BoldItalic", |
| "Cousine-Italic", |
| "Cousine-Regular", |
| "DejaVuSans", |
| "DejaVuSans-Bold", |
| "GardinerModBug", |
| "GardinerModCat", |
| "Garuda", |
| "Gelasio-Bold", |
| "Gelasio-BoldItalic", |
| "Gelasio-Italic", |
| "Gelasio-Regular", |
| "Lohit-Devanagari", |
| "Lohit-Gurmukhi", |
| "Lohit-Tamil", |
| "NotoSansKhmer-Regular", |
| "Tinos-Bold", |
| "Tinos-BoldItalic", |
| "Tinos-Italic", |
| "Tinos-Regular", |
| "muktinarrow"}; |
| std::string full_font_names_test_fonts[] = {"Ahem", |
| "Arimo Bold Italic", |
| "Arimo Bold", |
| "Arimo Italic", |
| "Arimo Regular", |
| "Cousine Bold Italic", |
| "Cousine Bold", |
| "Cousine Italic", |
| "Cousine Regular", |
| "DejaVu Sans Bold", |
| "DejaVu Sans", |
| "GardinerMod", |
| "Garuda", |
| "Gelasio Bold Italic", |
| "Gelasio Bold", |
| "Gelasio Italic", |
| "Gelasio Regular", |
| "Lohit Devanagari", |
| "Lohit Gurmukhi", |
| "Lohit Tamil", |
| "Mukti", |
| "Mukti Narrow", |
| "Noto Sans Khmer Regular", |
| "Tinos Bold Italic", |
| "Tinos Bold", |
| "Tinos Italic", |
| "Tinos Regular"}; |
| |
| auto match_unique_names = [this](auto& font_list) { |
| for (auto unique_font_name : font_list) { |
| mojom::FontIdentityPtr font_identity; |
| EXPECT_TRUE(font_loader()->MatchFontByPostscriptNameOrFullFontName( |
| unique_font_name, &font_identity)); |
| EXPECT_FALSE(font_identity.is_null()); |
| EXPECT_TRUE( |
| IsInTestFontDirectory(font_identity->str_representation.c_str())); |
| } |
| }; |
| match_unique_names(full_font_names_test_fonts); |
| match_unique_names(postscript_names_test_fonts); |
| } |
| |
| TEST_F(FontLoaderTest, LocalMatchingExpectNoMatchForFamilyNames) { |
| std::string family_names_expect_no_match[] = {"Arimo", "Cousine", "Gelasio", |
| "Lohit", "Noto Sans", "Tinos"}; |
| for (auto& family_name : family_names_expect_no_match) { |
| mojom::FontIdentityPtr font_identity; |
| EXPECT_FALSE(font_loader()->MatchFontByPostscriptNameOrFullFontName( |
| family_name, &font_identity)); |
| EXPECT_TRUE(font_identity.is_null()); |
| } |
| } |
| |
| TEST_F(FontLoaderTest, RejectNonUtf8) { |
| const char* invalid_utf8_font_names[] = { |
| // Trailing U+FDD0 U+FDD1 U+FDD2 U+FDD3 |
| "FontNameWithNonCharacters\xEF\xB7\x90\x20\xEF\xB7\x91\x20\xEF\xB7" |
| "\x92\x20\xEF\xB7\x93", |
| "InvalidBytes\xfe\xff"}; |
| for (std::string invalid_font_name : invalid_utf8_font_names) { |
| mojom::FontIdentityPtr font_identity; |
| EXPECT_FALSE(font_loader()->MatchFontByPostscriptNameOrFullFontName( |
| invalid_font_name, &font_identity)); |
| EXPECT_TRUE(font_identity.is_null()); |
| } |
| } |
| |
| } // namespace font_service |