HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
font_book.hpp
1// Copyright Take Vos 2020-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "font_font.hpp"
8#include "font_family_id.hpp"
9#include "true_type_font.hpp"
10#include "elusive_icon.hpp"
11#include "hikogui_icon.hpp"
12#include "../unicode/unicode.hpp"
13#include "../geometry/geometry.hpp"
14#include "../utility/utility.hpp"
15#include "../path/path.hpp"
16#include <limits>
17#include <array>
18#include <new>
19#include <atomic>
20#include <filesystem>
21
22hi_export_module(hikogui.font.font_book);
23
24hi_export namespace hi::inline v1 {
25
32hi_export class font_book {
33public:
35 hi::font const *font = nullptr;
36 hi::glyph_id id = {};
37
38 constexpr font_glyph_type() noexcept = default;
39 constexpr font_glyph_type(hi::font const& font, glyph_id id) noexcept : font(std::addressof(font)), id(id) {}
40
41 [[nodiscard]] constexpr friend bool operator==(font_glyph_type const&, font_glyph_type const&) noexcept = default;
42
43 [[nodiscard]] font_metrics_em const& get_font_metrics() const noexcept
44 {
45 hi_axiom_not_null(font);
46 return font->metrics;
47 }
48
49 [[nodiscard]] glyph_metrics get_metrics() const noexcept
50 {
51 hi_axiom_not_null(font);
52 return font->get_metrics(id);
53 }
54
55 [[nodiscard]] aarectangle get_bounding_rectangle() const noexcept
56 {
57 return get_metrics().bounding_rectangle;
58 }
59 };
60
62 hi::font const *font = nullptr;
63 lean_vector<glyph_id> ids = {};
64
65 constexpr font_glyphs_type() noexcept = default;
66 font_glyphs_type(hi::font const& font, lean_vector<glyph_id> ids) noexcept :
68 {
69 }
70 font_glyphs_type(hi::font const& font, glyph_id id) noexcept : font(std::addressof(font)), ids{id} {}
71
72 [[nodiscard]] font_metrics_em const& get_font_metrics() const noexcept
73 {
74 hi_axiom_not_null(font);
75 return font->metrics;
76 }
77
78 [[nodiscard]] glyph_metrics get_starter_metrics() const noexcept
79 {
80 hi_axiom(not ids.empty());
81 hi_axiom_not_null(font);
82 return font->get_metrics(ids.front());
83 }
84 };
85
86 static font_book& global() noexcept;
87
88 ~font_book() = default;
89 font_book(font_book const&) = delete;
90 font_book(font_book&&) = delete;
91 font_book& operator=(font_book const&) = delete;
92 font_book& operator=(font_book&&) = delete;
93 font_book() = default;
94
106 font& register_font_file(std::filesystem::path const& path, bool post_process = true)
107 {
108 auto font = std::make_unique<true_type_font>(path);
109 auto font_ptr = font.get();
110
111 hi_log_info("Parsed font {}: {}", path.string(), to_string(*font));
112
113 auto const font_family_id = register_family(font->family_name);
114 _font_variants[*font_family_id][font->font_variant()] = font_ptr;
115
116 _fonts.emplace_back(std::move(font));
117 _font_ptrs.push_back(font_ptr);
118
119 if (post_process) {
120 this->post_process();
121 }
122
123 return *font_ptr;
124 }
125
130 void register_font_directory(std::filesystem::path const& path, bool post_process = true)
131 {
132 auto const font_directory_glob = path / "**" / "*.ttf";
133 for (auto const& font_path : glob(font_directory_glob)) {
134 auto const t = trace<"font_scan">{};
135
136 try {
137 register_font_file(font_path, false);
138
139 } catch (std::exception const& e) {
140 hi_log_error("Failed parsing font at {}: \"{}\"", font_path.string(), e.what());
141 }
142 }
143
144 if (post_process) {
145 this->post_process();
146 }
147 }
148
153 void post_process() noexcept
154 {
155 // Sort the list of fonts based on the amount of unicode code points it supports.
156 std::sort(begin(_font_ptrs), end(_font_ptrs), [](auto const& lhs, auto const& rhs) {
157 return lhs->char_map.count() > rhs->char_map.count();
158 });
159
160 auto const regular_fallback_chain = make_fallback_chain(font_weight::regular, font_style::normal);
161 auto const bold_fallback_chain = make_fallback_chain(font_weight::bold, font_style::normal);
162 auto const italic_fallback_chain = make_fallback_chain(font_weight::regular, font_style::italic);
163
164 hi_log_info(
165 "Post processing fonts number={}, regular-fallback={}, bold-fallback={}, italic-fallback={}",
166 size(_fonts),
167 size(regular_fallback_chain),
168 size(bold_fallback_chain),
169 size(italic_fallback_chain));
170
171 // For each font, find fallback list.
172 for (auto const& font : _font_ptrs) {
173 auto fallback_chain = std::vector<hi::font *>{};
174
175 // Put the fonts from the same family, italic and weight first.
176 for (auto const& fallback : _font_ptrs) {
177 // clang-format off
178 if (
179 (fallback != font) and
180 (fallback->family_name == font->family_name) and
181 (fallback->style == font->style) and
182 almost_equal(fallback->weight, font->weight)
183 ) {
184 fallback_chain.push_back(fallback);
185 }
186 // clang-format on
187 }
188
189 if (almost_equal(font->weight, font_weight::bold)) {
190 std::copy(begin(bold_fallback_chain), end(bold_fallback_chain), std::back_inserter(fallback_chain));
191 } else if (font->style == font_style::italic) {
192 std::copy(begin(italic_fallback_chain), end(italic_fallback_chain), std::back_inserter(fallback_chain));
193 } else {
194 std::copy(begin(regular_fallback_chain), end(regular_fallback_chain), std::back_inserter(fallback_chain));
195 }
196
197 font->fallback_chain = std::move(fallback_chain);
198 }
199 }
200
204 [[nodiscard]] font_family_id find_family(std::string const& family_name) const noexcept
205 {
206 auto it = _family_names.find(to_lower(family_name));
207 if (it == _family_names.end()) {
208 return std::nullopt;
209 } else {
210 return it->second;
211 }
212 }
213
217 [[nodiscard]] font_family_id register_family(std::string_view family_name) noexcept
218 {
219 auto name = to_lower(family_name);
220
221 auto it = _family_names.find(name);
222 if (it == _family_names.end()) {
223 auto const family_id = font_family_id(_font_variants.size());
224 _font_variants.emplace_back();
225 _family_names[name] = family_id;
226 return family_id;
227 } else {
228 return it->second;
229 }
230 }
231
239 [[nodiscard]] font const& find_font(font_family_id family_id, font_variant variant) const noexcept
240 {
241 hi_assert(family_id);
242 hi_assert_bounds(*family_id, _font_variants);
243
244 auto const& variants = _font_variants[*family_id];
245 for (auto alternative_variant : alternatives(variant)) {
246 if (auto font = variants[alternative_variant]) {
247 return *font;
248 }
249 }
250
251 // If a family exists, there must be at least one font variant available.
252 hi_no_default();
253 }
254
263 [[nodiscard]] font const *find_font(std::string const& family_name, font_variant variant) const noexcept
264 {
265 if (auto const family_id = find_family(family_name)) {
266 return &find_font(family_id, variant);
267 } else {
268 return nullptr;
269 }
270 }
271
280 [[nodiscard]] font_glyphs_type find_glyph(font const& font, hi::grapheme grapheme) const noexcept
281 {
282 // First try the selected font.
283 if (auto const glyph_ids = font.find_glyph(grapheme); not glyph_ids.empty()) {
284 return {font, std::move(glyph_ids)};
285 }
286
287 // Scan fonts which are fallback to this.
288 for (auto const fallback : font.fallback_chain) {
289 hi_axiom_not_null(fallback);
290 if (auto const glyph_ids = fallback->find_glyph(grapheme); not glyph_ids.empty()) {
291 return {*fallback, std::move(glyph_ids)};
292 }
293 }
294
295 // If all everything has failed, use the tofu block of the original font.
296 return {font, {glyph_id{0}}};
297 }
298
307 [[nodiscard]] font_glyph_type find_glyph(font const& font, char32_t code_point) const noexcept
308 {
309 // First try the selected font.
310 if (auto const glyph_id = font.find_glyph(code_point)) {
311 return {font, glyph_id};
312 }
313
314 // Scan fonts which are fallback to this.
315 for (auto const fallback : font.fallback_chain) {
316 hi_axiom_not_null(fallback);
317 if (auto const glyph_id = fallback->find_glyph(code_point)) {
318 return {*fallback, glyph_id};
319 }
320 }
321
322 // If all everything has failed, use the tofu block of the original font.
323 return {font, glyph_id{0}};
324 }
325
326private:
330
333 std::vector<std::array<font const *, font_variant::size()>> _font_variants;
334
336 std::vector<hi::font *> _font_ptrs;
337
338 [[nodiscard]] std::vector<hi::font *> make_fallback_chain(font_weight weight, font_style style) noexcept
339 {
340 auto r = _font_ptrs;
341
342 std::stable_partition(begin(r), end(r), [weight, style](auto const& item) {
343 return (item->style == style) and almost_equal(item->weight, weight);
344 });
345
346 auto char_mask = std::bitset<0x11'0000>{};
347 for (auto& font : r) {
348 if (font->char_map.update_mask(char_mask) == 0) {
349 // This font did not add any code points.
350 font = nullptr;
351 }
352 }
353
354 std::erase(r, nullptr);
355 return r;
356 }
357};
358
359namespace detail {
360inline std::unique_ptr<font_book> font_book_global = nullptr;
361}
362
363inline font_book& font_book::global() noexcept
364{
365 if (not detail::font_book_global) {
366 detail::font_book_global = std::make_unique<font_book>();
367 }
368 return *detail::font_book_global;
369}
370
381hi_export inline font& register_font_file(std::filesystem::path const& path)
382{
383 return font_book::global().register_font_file(path);
384}
385
386hi_export inline void register_font_directory(std::filesystem::path const& path)
387{
388 return font_book::global().register_font_directory(path);
389}
390
391hi_export template<typename Range>
392inline void register_font_directories(Range&& range) noexcept
393{
394 for (auto const& path : range) {
395 font_book::global().register_font_directory(path, false);
396 }
397 font_book::global().post_process();
398}
399
403hi_export [[nodiscard]] inline font_family_id find_font_family(std::string const& family_name) noexcept
404{
405 return font_book::global().find_family(family_name);
406}
407
415hi_export [[nodiscard]] inline font const& find_font(font_family_id family_id, font_variant variant = font_variant{}) noexcept
416{
417 return font_book::global().find_font(family_id, variant);
418}
419
427hi_export [[nodiscard]] inline font const *find_font(std::string const& family_name, font_variant variant = font_variant{}) noexcept
428{
429 return font_book::global().find_font(family_name, variant);
430}
431
440hi_export [[nodiscard]] inline auto find_glyph(font const& font, grapheme grapheme) noexcept
441{
442 return font_book::global().find_glyph(font, grapheme);
443}
444
453hi_export [[nodiscard]] inline auto find_glyph(font const& font, char32_t code_point) noexcept
454{
455 return font_book::global().find_glyph(font, code_point);
456}
457
458hi_export [[nodiscard]] inline auto find_glyph(elusive_icon rhs) noexcept
459{
460 auto const *font = find_font("elusiveicons", font_variant{font_weight::medium, font_style::normal});
461 hi_assert_not_null(font, "Could not find Elusive icon font");
462 return find_glyph(*font, std::to_underlying(rhs));
463}
464
465hi_export [[nodiscard]] inline auto find_glyph(hikogui_icon rhs) noexcept
466{
467 auto const *font = find_font("Hikogui Icons", font_variant{font_weight::regular, font_style::normal});
468 hi_assert_not_null(font, "Could not find HikoGUI icon font");
469 return find_glyph(*font, std::to_underlying(rhs));
470}
471
472} // namespace hi::inline v1
STL namespace.
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
font_weight
Definition font_weight.hpp:21
hi_export font & register_font_file(std::filesystem::path const &path)
Register a font.
Definition font_book.hpp:381
hi_export font const & find_font(font_family_id family_id, font_variant variant=font_variant{}) noexcept
Find a font closest to the variant.
Definition font_book.hpp:415
hi_export font_family_id find_font_family(std::string const &family_name) noexcept
Find font family id.
Definition font_book.hpp:403
hi_export auto find_glyph(font const &font, grapheme grapheme) noexcept
Find a glyph using the given code-point.
Definition font_book.hpp:440
font_book keeps track of multiple fonts.
Definition font_book.hpp:32
void post_process() noexcept
Post process font_book Should be called after a set of register_font() calls This calculates font fal...
Definition font_book.hpp:153
void register_font_directory(std::filesystem::path const &path, bool post_process=true)
Register all fonts found in a directory.
Definition font_book.hpp:130
font const & find_font(font_family_id family_id, font_variant variant) const noexcept
Find a font closest to the variant.
Definition font_book.hpp:239
font_family_id register_family(std::string_view family_name) noexcept
Register font family id.
Definition font_book.hpp:217
font const * find_font(std::string const &family_name, font_variant variant) const noexcept
Find a font closest to the variant.
Definition font_book.hpp:263
font_glyph_type find_glyph(font const &font, char32_t code_point) const noexcept
Find a glyph using the given code-point.
Definition font_book.hpp:307
font_glyphs_type find_glyph(font const &font, hi::grapheme grapheme) const noexcept
Find a glyph using the given code-point.
Definition font_book.hpp:280
font_family_id find_family(std::string const &family_name) const noexcept
Find font family id.
Definition font_book.hpp:204
Definition font_book.hpp:34
Definition font_book.hpp:61
Definition font_font.hpp:31
virtual glyph_metrics get_metrics(hi::glyph_id glyph_id) const =0
Load a glyph into a path.
font_metrics_em metrics
The metrics of a font.
Definition font_font.hpp:65
std::string family_name
The family name as parsed from the font file.
Definition font_font.hpp:37
std::vector< hi::font * > fallback_chain
List of fonts to use as a fallback for this font.
Definition font_font.hpp:69
glyph_id find_glyph(char32_t c) const noexcept
Get the glyph for a code-point.
Definition font_font.hpp:87
A font variant is one of 16 different fonts that can be part of a family.
Definition font_variant.hpp:27
Definition glyph_metrics.hpp:20
aarectangle bounding_rectangle
Definition glyph_metrics.hpp:23
Definition trace.hpp:43
A grapheme-cluster, what a user thinks a character is.
Definition grapheme.hpp:167
Definition tagged_id.hpp:26
T addressof(T... args)
T back_inserter(T... args)
T copy(T... args)
T move(T... args)
T push_back(T... args)
T sort(T... args)
T stable_partition(T... args)
T what(T... args)