FontPlugin.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /*
  2. * Copyright (c) 2022-2023, Andreas Kling <andreas@ladybird.org>
  3. * Copyright (c) 2023, Linus Groh <linusg@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "FontPlugin.h"
  8. #include <AK/ByteString.h>
  9. #include <AK/String.h>
  10. #include <LibCore/Resource.h>
  11. #include <LibCore/StandardPaths.h>
  12. #include <LibGfx/Font/FontDatabase.h>
  13. #ifdef USE_FONTCONFIG
  14. # include <fontconfig/fontconfig.h>
  15. #endif
  16. namespace Ladybird {
  17. FontPlugin::FontPlugin(bool is_layout_test_mode)
  18. : m_is_layout_test_mode(is_layout_test_mode)
  19. {
  20. #ifdef USE_FONTCONFIG
  21. {
  22. auto fontconfig_initialized = FcInit();
  23. VERIFY(fontconfig_initialized);
  24. }
  25. #endif
  26. // Load anything we can find in the system's font directories
  27. for (auto const& path : Core::StandardPaths::font_directories().release_value_but_fixme_should_propagate_errors())
  28. Gfx::FontDatabase::the().load_all_fonts_from_uri(MUST(String::formatted("file://{}", path)));
  29. update_generic_fonts();
  30. auto default_font_name = generic_font_name(Web::Platform::GenericFont::UiSansSerif);
  31. m_default_font = Gfx::FontDatabase::the().get(default_font_name, 12.0, 400, Gfx::FontWidth::Normal, 0);
  32. VERIFY(m_default_font);
  33. auto default_fixed_width_font_name = generic_font_name(Web::Platform::GenericFont::UiMonospace);
  34. m_default_fixed_width_font = Gfx::FontDatabase::the().get(default_fixed_width_font_name, 12.0, 400, Gfx::FontWidth::Normal, 0);
  35. VERIFY(m_default_fixed_width_font);
  36. }
  37. FontPlugin::~FontPlugin() = default;
  38. Gfx::Font& FontPlugin::default_font()
  39. {
  40. return *m_default_font;
  41. }
  42. Gfx::Font& FontPlugin::default_fixed_width_font()
  43. {
  44. return *m_default_fixed_width_font;
  45. }
  46. RefPtr<Gfx::Font> FontPlugin::default_emoji_font(float point_size)
  47. {
  48. FlyString default_emoji_font_name;
  49. if (m_is_layout_test_mode) {
  50. default_emoji_font_name = "Noto Emoji"_fly_string;
  51. } else {
  52. #ifdef AK_OS_MACOS
  53. default_emoji_font_name = "Apple Color Emoji"_fly_string;
  54. #else
  55. default_emoji_font_name = "Noto Color Emoji"_fly_string;
  56. #endif
  57. }
  58. return Gfx::FontDatabase::the().get(default_emoji_font_name, point_size, 400, Gfx::FontWidth::Normal, 0);
  59. }
  60. #ifdef USE_FONTCONFIG
  61. static Optional<String> query_fontconfig_for_generic_family(Web::Platform::GenericFont generic_font)
  62. {
  63. char const* pattern_string = nullptr;
  64. switch (generic_font) {
  65. case Web::Platform::GenericFont::Cursive:
  66. pattern_string = "cursive";
  67. break;
  68. case Web::Platform::GenericFont::Fantasy:
  69. pattern_string = "fantasy";
  70. break;
  71. case Web::Platform::GenericFont::Monospace:
  72. pattern_string = "monospace";
  73. break;
  74. case Web::Platform::GenericFont::SansSerif:
  75. pattern_string = "sans-serif";
  76. break;
  77. case Web::Platform::GenericFont::Serif:
  78. pattern_string = "serif";
  79. break;
  80. case Web::Platform::GenericFont::UiMonospace:
  81. pattern_string = "monospace";
  82. break;
  83. case Web::Platform::GenericFont::UiRounded:
  84. pattern_string = "sans-serif";
  85. break;
  86. case Web::Platform::GenericFont::UiSansSerif:
  87. pattern_string = "sans-serif";
  88. break;
  89. case Web::Platform::GenericFont::UiSerif:
  90. pattern_string = "serif";
  91. break;
  92. default:
  93. VERIFY_NOT_REACHED();
  94. }
  95. auto* config = FcConfigGetCurrent();
  96. VERIFY(config);
  97. FcPattern* pattern = FcNameParse(reinterpret_cast<FcChar8 const*>(pattern_string));
  98. VERIFY(pattern);
  99. auto success = FcConfigSubstitute(config, pattern, FcMatchPattern);
  100. VERIFY(success);
  101. FcDefaultSubstitute(pattern);
  102. // Never select bitmap fonts.
  103. success = FcPatternAddBool(pattern, FC_SCALABLE, FcTrue);
  104. VERIFY(success);
  105. // FIXME: Enable this once we can handle OpenType variable fonts.
  106. success = FcPatternAddBool(pattern, FC_VARIABLE, FcFalse);
  107. VERIFY(success);
  108. Optional<String> name;
  109. FcResult result {};
  110. if (auto* matched = FcFontMatch(config, pattern, &result)) {
  111. FcChar8* family = nullptr;
  112. if (FcPatternGetString(matched, FC_FAMILY, 0, &family) == FcResultMatch) {
  113. auto const* family_cstring = reinterpret_cast<char const*>(family);
  114. if (auto string = String::from_utf8(StringView { family_cstring, strlen(family_cstring) }); !string.is_error()) {
  115. name = string.release_value();
  116. }
  117. }
  118. FcPatternDestroy(matched);
  119. }
  120. FcPatternDestroy(pattern);
  121. return name;
  122. }
  123. #endif
  124. void FontPlugin::update_generic_fonts()
  125. {
  126. // How we choose which system font to use for each CSS font:
  127. // 1. Try a list of known-suitable fonts with their names hard-coded below.
  128. // This is rather weird, but it's how things work right now.
  129. // We should eventually have a way to query the system for the default font.
  130. // Furthermore, we should allow overriding via some kind of configuration mechanism.
  131. m_generic_font_names.resize(static_cast<size_t>(Web::Platform::GenericFont::__Count));
  132. auto update_mapping = [&](Web::Platform::GenericFont generic_font, ReadonlySpan<FlyString> fallbacks) {
  133. if (m_is_layout_test_mode) {
  134. m_generic_font_names[static_cast<size_t>(generic_font)] = "SerenitySans"_fly_string;
  135. return;
  136. }
  137. RefPtr<Gfx::Font const> gfx_font;
  138. #ifdef USE_FONTCONFIG
  139. auto name = query_fontconfig_for_generic_family(generic_font);
  140. if (name.has_value()) {
  141. gfx_font = Gfx::FontDatabase::the().get(name.value(), 16, 400, Gfx::FontWidth::Normal, 0);
  142. }
  143. #endif
  144. if (!gfx_font) {
  145. for (auto const& fallback : fallbacks) {
  146. gfx_font = Gfx::FontDatabase::the().get(fallback, 16, 400, Gfx::FontWidth::Normal, 0);
  147. if (gfx_font)
  148. break;
  149. }
  150. }
  151. m_generic_font_names[static_cast<size_t>(generic_font)] = gfx_font ? gfx_font->family() : String {};
  152. };
  153. // Fallback fonts to look for if Gfx::Font can't load expected font
  154. // The lists are basically arbitrary, taken from https://www.w3.org/Style/Examples/007/fonts.en.html
  155. // (We also add Android-specific font names to the list from W3 where required.)
  156. Vector<FlyString> cursive_fallbacks { "Comic Sans MS"_fly_string, "Comic Sans"_fly_string, "Apple Chancery"_fly_string, "Bradley Hand"_fly_string, "Brush Script MT"_fly_string, "Snell Roundhand"_fly_string, "URW Chancery L"_fly_string, "Dancing Script"_fly_string };
  157. Vector<FlyString> fantasy_fallbacks { "Impact"_fly_string, "Luminari"_fly_string, "Chalkduster"_fly_string, "Jazz LET"_fly_string, "Blippo"_fly_string, "Stencil Std"_fly_string, "Marker Felt"_fly_string, "Trattatello"_fly_string, "Coming Soon"_fly_string };
  158. Vector<FlyString> monospace_fallbacks { "Andale Mono"_fly_string, "Courier New"_fly_string, "Courier"_fly_string, "FreeMono"_fly_string, "OCR A Std"_fly_string, "DejaVu Sans Mono"_fly_string, "Droid Sans Mono"_fly_string, "Liberation Mono"_fly_string };
  159. Vector<FlyString> sans_serif_fallbacks { "Arial"_fly_string, "Helvetica"_fly_string, "Verdana"_fly_string, "Trebuchet MS"_fly_string, "Gill Sans"_fly_string, "Noto Sans"_fly_string, "Avantgarde"_fly_string, "Optima"_fly_string, "Arial Narrow"_fly_string, "Liberation Sans"_fly_string, "Roboto"_fly_string };
  160. Vector<FlyString> serif_fallbacks { "Times"_fly_string, "Times New Roman"_fly_string, "Didot"_fly_string, "Georgia"_fly_string, "Palatino"_fly_string, "Bookman"_fly_string, "New Century Schoolbook"_fly_string, "American Typewriter"_fly_string, "Liberation Serif"_fly_string, "Roman"_fly_string, "Noto Serif"_fly_string };
  161. update_mapping(Web::Platform::GenericFont::Cursive, cursive_fallbacks);
  162. update_mapping(Web::Platform::GenericFont::Fantasy, fantasy_fallbacks);
  163. update_mapping(Web::Platform::GenericFont::Monospace, monospace_fallbacks);
  164. update_mapping(Web::Platform::GenericFont::SansSerif, sans_serif_fallbacks);
  165. update_mapping(Web::Platform::GenericFont::Serif, serif_fallbacks);
  166. update_mapping(Web::Platform::GenericFont::UiMonospace, monospace_fallbacks);
  167. update_mapping(Web::Platform::GenericFont::UiRounded, sans_serif_fallbacks);
  168. update_mapping(Web::Platform::GenericFont::UiSansSerif, sans_serif_fallbacks);
  169. update_mapping(Web::Platform::GenericFont::UiSerif, serif_fallbacks);
  170. }
  171. FlyString FontPlugin::generic_font_name(Web::Platform::GenericFont generic_font)
  172. {
  173. return m_generic_font_names[static_cast<size_t>(generic_font)];
  174. }
  175. }