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.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/module.hpp"
14#include "../utility/utility.hpp"
15#include "../coroutine/module.hpp"
16#include "../path/path.hpp"
17#include <limits>
18#include <array>
19#include <new>
20#include <atomic>
21#include <filesystem>
22
23hi_export_module(hikogui.font.font_book);
24
25namespace hi::inline v1 {
26
33hi_export class font_book {
34public:
36 hi::font const *font = nullptr;
37 hi::glyph_id id = {};
38
39 constexpr font_glyph_type() noexcept = default;
40 constexpr font_glyph_type(hi::font const& font, glyph_id id) noexcept : font(std::addressof(font)), id(id) {}
41
42 [[nodiscard]] constexpr friend bool operator==(font_glyph_type const&, font_glyph_type const&) noexcept = default;
43
44 [[nodiscard]] font_metrics const& get_font_metrics() const noexcept
45 {
46 hi_axiom_not_null(font);
47 return font->metrics;
48 }
49
50 [[nodiscard]] glyph_metrics get_metrics() const noexcept
51 {
52 hi_axiom_not_null(font);
53 return font->get_metrics(id);
54 }
55
56 [[nodiscard]] aarectangle get_bounding_rectangle() const noexcept
57 {
58 return get_metrics().bounding_rectangle;
59 }
60 };
61
63 hi::font const *font = nullptr;
64 lean_vector<glyph_id> ids = {};
65
66 constexpr font_glyphs_type() noexcept = default;
67 font_glyphs_type(hi::font const& font, lean_vector<glyph_id> ids) noexcept :
69 {
70 }
71 font_glyphs_type(hi::font const& font, glyph_id id) noexcept : font(std::addressof(font)), ids{id} {}
72
73 [[nodiscard]] font_metrics const& get_font_metrics() const noexcept
74 {
75 hi_axiom_not_null(font);
76 return font->metrics;
77 }
78
79 [[nodiscard]] glyph_metrics get_starter_metrics() const noexcept
80 {
81 hi_axiom(not ids.empty());
82 hi_axiom_not_null(font);
83 return font->get_metrics(ids.front());
84 }
85 };
86
87 static font_book& global() noexcept
88 {
89 if (not _global) {
90 _global = std::make_unique<font_book>();
91 }
92 return *_global;
93 }
94
95 ~font_book() = default;
96 font_book(font_book const&) = delete;
97 font_book(font_book&&) = delete;
98 font_book& operator=(font_book const&) = delete;
99 font_book& operator=(font_book&&) = delete;
100 font_book() = default;
101
113 font& register_font_file(std::filesystem::path const& path, bool post_process = true)
114 {
115 auto font = std::make_unique<true_type_font>(path);
116 auto font_ptr = font.get();
117
118 hi_log_info("Parsed font {}: {}", path.string(), to_string(*font));
119
120 hilet font_family_id = register_family(font->family_name);
121 _font_variants[*font_family_id][font->font_variant()] = font_ptr;
122
123 _fonts.emplace_back(std::move(font));
124 _font_ptrs.push_back(font_ptr);
125
126 if (post_process) {
127 this->post_process();
128 }
129
130 return *font_ptr;
131 }
132
137 void register_font_directory(std::filesystem::path const& path, bool post_process = true)
138 {
139 hilet font_directory_glob = path / "**" / "*.ttf";
140 for (hilet& font_path : glob(font_directory_glob)) {
141 hilet t = trace<"font_scan">{};
142
143 try {
144 register_font_file(font_path, false);
145
146 } catch (std::exception const& e) {
147 hi_log_error("Failed parsing font at {}: \"{}\"", font_path.string(), e.what());
148 }
149 }
150
151 if (post_process) {
152 this->post_process();
153 }
154 }
155
160 void post_process() noexcept
161 {
162 // Sort the list of fonts based on the amount of unicode code points it supports.
163 std::sort(begin(_font_ptrs), end(_font_ptrs), [](hilet& lhs, hilet& rhs) {
164 return lhs->char_map.count() > rhs->char_map.count();
165 });
166
167 hilet regular_fallback_chain = make_fallback_chain(font_weight::regular, font_style::normal);
168 hilet bold_fallback_chain = make_fallback_chain(font_weight::bold, font_style::normal);
169 hilet italic_fallback_chain = make_fallback_chain(font_weight::regular, font_style::italic);
170
171 hi_log_info(
172 "Post processing fonts number={}, regular-fallback={}, bold-fallback={}, italic-fallback={}",
173 size(_fonts),
174 size(regular_fallback_chain),
175 size(bold_fallback_chain),
176 size(italic_fallback_chain));
177
178 // For each font, find fallback list.
179 for (hilet& font : _font_ptrs) {
180 auto fallback_chain = std::vector<hi::font *>{};
181
182 // Put the fonts from the same family, italic and weight first.
183 for (hilet& fallback : _font_ptrs) {
184 // clang-format off
185 if (
186 (fallback != font) and
187 (fallback->family_name == font->family_name) and
188 (fallback->style == font->style) and
189 almost_equal(fallback->weight, font->weight)
190 ) {
191 fallback_chain.push_back(fallback);
192 }
193 // clang-format on
194 }
195
196 if (almost_equal(font->weight, font_weight::bold)) {
197 std::copy(begin(bold_fallback_chain), end(bold_fallback_chain), std::back_inserter(fallback_chain));
198 } else if (font->style == font_style::italic) {
199 std::copy(begin(italic_fallback_chain), end(italic_fallback_chain), std::back_inserter(fallback_chain));
200 } else {
201 std::copy(begin(regular_fallback_chain), end(regular_fallback_chain), std::back_inserter(fallback_chain));
202 }
203
204 font->fallback_chain = std::move(fallback_chain);
205 }
206 }
207
211 [[nodiscard]] font_family_id find_family(std::string const& family_name) const noexcept
212 {
213 auto it = _family_names.find(to_lower(family_name));
214 if (it == _family_names.end()) {
215 return std::nullopt;
216 } else {
217 return it->second;
218 }
219 }
220
224 [[nodiscard]] font_family_id register_family(std::string_view family_name) noexcept
225 {
226 auto name = to_lower(family_name);
227
228 auto it = _family_names.find(name);
229 if (it == _family_names.end()) {
230 hilet family_id = font_family_id(_font_variants.size());
231 _font_variants.emplace_back();
232 _family_names[name] = family_id;
233 return family_id;
234 } else {
235 return it->second;
236 }
237 }
238
246 [[nodiscard]] font const& find_font(font_family_id family_id, font_variant variant) const noexcept
247 {
248 hi_assert(family_id);
249 hi_assert_bounds(*family_id, _font_variants);
250
251 hilet& variants = _font_variants[*family_id];
252 for (auto alternative_variant : alternatives(variant)) {
253 if (auto font = variants[alternative_variant]) {
254 return *font;
255 }
256 }
257
258 // If a family exists, there must be at least one font variant available.
259 hi_no_default();
260 }
261
270 [[nodiscard]] font const *find_font(std::string const& family_name, font_variant variant) const noexcept
271 {
272 if (hilet family_id = find_family(family_name)) {
273 return &find_font(family_id, variant);
274 } else {
275 return nullptr;
276 }
277 }
278
287 [[nodiscard]] font_glyphs_type find_glyph(font const& font, hi::grapheme grapheme) const noexcept
288 {
289 // First try the selected font.
290 if (hilet glyph_ids = font.find_glyph(grapheme); not glyph_ids.empty()) {
291 return {font, std::move(glyph_ids)};
292 }
293
294 // Scan fonts which are fallback to this.
295 for (hilet fallback : font.fallback_chain) {
296 hi_axiom_not_null(fallback);
297 if (hilet glyph_ids = fallback->find_glyph(grapheme); not glyph_ids.empty()) {
298 return {*fallback, std::move(glyph_ids)};
299 }
300 }
301
302 // If all everything has failed, use the tofu block of the original font.
303 return {font, {glyph_id{0}}};
304 }
305
314 [[nodiscard]] font_glyph_type find_glyph(font const& font, char32_t code_point) const noexcept
315 {
316 // First try the selected font.
317 if (hilet glyph_id = font.find_glyph(code_point)) {
318 return {font, glyph_id};
319 }
320
321 // Scan fonts which are fallback to this.
322 for (hilet fallback : font.fallback_chain) {
323 hi_axiom_not_null(fallback);
324 if (hilet glyph_id = fallback->find_glyph(code_point)) {
325 return {*fallback, glyph_id};
326 }
327 }
328
329 // If all everything has failed, use the tofu block of the original font.
330 return {font, glyph_id{0}};
331 }
332
333private:
334 inline static std::unique_ptr<font_book> _global = nullptr;
335
339
342 std::vector<std::array<font const *, font_variant::size()>> _font_variants;
343
345 std::vector<hi::font *> _font_ptrs;
346
347 [[nodiscard]] std::vector<hi::font *> make_fallback_chain(font_weight weight, font_style style) noexcept
348 {
349 auto r = _font_ptrs;
350
351 std::stable_partition(begin(r), end(r), [weight, style](hilet& item) {
352 return (item->style == style) and almost_equal(item->weight, weight);
353 });
354
355 auto char_mask = std::bitset<0x11'0000>{};
356 for (auto& font : r) {
357 if (font->char_map.update_mask(char_mask) == 0) {
358 // This font did not add any code points.
359 font = nullptr;
360 }
361 }
362
363 std::erase(r, nullptr);
364 return r;
365 }
366};
367
378hi_export inline font& register_font_file(std::filesystem::path const& path)
379{
380 return font_book::global().register_font_file(path);
381}
382
383hi_export inline void register_font_directory(std::filesystem::path const& path)
384{
385 return font_book::global().register_font_directory(path);
386}
387
388hi_export template<typename Range>
389inline void register_font_directories(Range&& range) noexcept
390{
391 for (auto const& path : range) {
392 font_book::global().register_font_directory(path, false);
393 }
394 font_book::global().post_process();
395}
396
400hi_export [[nodiscard]] inline font_family_id find_font_family(std::string const& family_name) noexcept
401{
402 return font_book::global().find_family(family_name);
403}
404
412hi_export [[nodiscard]] inline font const& find_font(font_family_id family_id, font_variant variant = font_variant{}) noexcept
413{
414 return font_book::global().find_font(family_id, variant);
415}
416
424hi_export [[nodiscard]] inline font const *find_font(std::string const& family_name, font_variant variant = font_variant{}) noexcept
425{
426 return font_book::global().find_font(family_name, variant);
427}
428
437hi_export [[nodiscard]] inline auto find_glyph(font const& font, grapheme grapheme) noexcept
438{
439 return font_book::global().find_glyph(font, grapheme);
440}
441
450hi_export [[nodiscard]] inline auto find_glyph(font const& font, char32_t code_point) noexcept
451{
452 return font_book::global().find_glyph(font, code_point);
453}
454
455hi_export [[nodiscard]] inline auto find_glyph(elusive_icon rhs) noexcept
456{
457 hilet *font = find_font("elusiveicons", font_variant{font_weight::medium, font_style::normal});
458 hi_assert_not_null(font, "Could not find Elusive icon font");
459 return find_glyph(*font, std::to_underlying(rhs));
460}
461
462hi_export [[nodiscard]] inline auto find_glyph(hikogui_icon rhs) noexcept
463{
464 hilet *font = find_font("Hikogui Icons", font_variant{font_weight::regular, font_style::normal});
465 hi_assert_not_null(font, "Could not find HikoGUI icon font");
466 return find_glyph(*font, std::to_underlying(rhs));
467}
468
469} // namespace hi::inline v1
DOXYGEN BUG.
Definition algorithm.hpp:16
font_weight
Definition font_weight.hpp:18
hi_export font & register_font_file(std::filesystem::path const &path)
Register a font.
Definition font_book.hpp:378
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:412
hi_export font_family_id find_font_family(std::string const &family_name) noexcept
Find font family id.
Definition font_book.hpp:400
hi_export auto find_glyph(font const &font, grapheme grapheme) noexcept
Find a glyph using the given code-point.
Definition font_book.hpp:437
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
font_book keeps track of multiple fonts.
Definition font_book.hpp:33
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:160
void register_font_directory(std::filesystem::path const &path, bool post_process=true)
Register all fonts found in a directory.
Definition font_book.hpp:137
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:246
font_family_id register_family(std::string_view family_name) noexcept
Register font family id.
Definition font_book.hpp:224
font & register_font_file(std::filesystem::path const &path, bool post_process=true)
Register a font.
Definition font_book.hpp:113
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:270
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:314
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:287
font_family_id find_family(std::string const &family_name) const noexcept
Find font family id.
Definition font_book.hpp:211
Definition font_book.hpp:35
Definition font_book.hpp:62
Definition font_font.hpp:31
virtual glyph_metrics get_metrics(hi::glyph_id glyph_id) const =0
Load a glyph into a path.
hi::font_metrics metrics
The metrics of a font.
Definition font_font.hpp:67
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:71
glyph_id find_glyph(char32_t c) const noexcept
Get the glyph for a code-point.
Definition font_font.hpp:89
The metrics of a font.
Definition font_metrics.hpp:19
A font variant is one of 16 different fonts that can be part of a family.
Definition font_variant.hpp:19
Definition glyph_metrics.hpp:19
aarectangle bounding_rectangle
Definition glyph_metrics.hpp:22
Definition trace.hpp:42
A grapheme-cluster, what a user thinks a character is.
Definition grapheme.hpp:160
Definition tagged_id.hpp:20
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)