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_id.hpp"
9#include "font_glyph_ids.hpp"
10#include "font_family_id.hpp"
11#include "true_type_font.hpp"
12#include "elusive_icon.hpp"
13#include "hikogui_icon.hpp"
14#include "../unicode/unicode.hpp"
15#include "../geometry/geometry.hpp"
16#include "../utility/utility.hpp"
17#include "../path/path.hpp"
18#include <gsl/gsl>
19#include <limits>
20#include <array>
21#include <new>
22#include <atomic>
23#include <filesystem>
24
25hi_export_module(hikogui.font : font_book);
26
27hi_export namespace hi::inline v1 {
28
35class font_book {
36public:
37 static font_book& global() noexcept;
38
39 ~font_book() = default;
40 font_book(font_book const&) = delete;
41 font_book(font_book&&) = delete;
42 font_book& operator=(font_book const&) = delete;
43 font_book& operator=(font_book&&) = delete;
44 font_book() = default;
45
57 font_id register_font_file(std::filesystem::path const& path, bool post_process = true)
58 {
59 if (_fonts.size() >= font_id::empty_value) {
60 throw std::overflow_error("Too many fonts registered");
61 }
62
63 auto const font_id = hi::font_id{gsl::narrow_cast<font_id::value_type>(_fonts.size())};
64 auto const &font = *_fonts.emplace_back(std::make_unique<true_type_font>(path));
65 _fallback_chain.push_back(font_id);
66
67 hi_log_info("Parsed font id={} {}: {}", *font_id, path.string(), to_string(font));
68
69 auto const font_family_id = register_family(font.family_name);
70 _font_variants[*font_family_id][font.font_variant()] = font_id;
71
72 if (post_process) {
73 this->post_process();
74 }
75
76 return font_id;
77 }
78
83 void register_font_directory(std::filesystem::path const& path, bool post_process = true)
84 {
85 auto const font_directory_glob = path / "**" / "*.ttf";
86 for (auto const& font_path : glob(font_directory_glob)) {
87 auto const t = trace<"font_scan">{};
88
89 try {
90 register_font_file(font_path, false);
91
92 } catch (std::exception const& e) {
93 hi_log_error("Failed parsing font at {}: \"{}\"", font_path.string(), e.what());
94 }
95 }
96
97 if (post_process) {
98 this->post_process();
99 }
100 }
101
106 void post_process() noexcept
107 {
108 // Sort the list of fonts based on the amount of unicode code points it supports.
109 std::sort(begin(_fallback_chain), end(_fallback_chain), [](auto const& lhs, auto const& rhs) {
110 return lhs->char_map.count() > rhs->char_map.count();
111 });
112
113 auto const regular_fallback_chain = make_fallback_chain(font_weight::regular, font_style::normal);
114 auto const bold_fallback_chain = make_fallback_chain(font_weight::bold, font_style::normal);
115 auto const italic_fallback_chain = make_fallback_chain(font_weight::regular, font_style::italic);
116
117 hi_log_info(
118 "Post processing fonts number={}, regular-fallback={}, bold-fallback={}, italic-fallback={}",
119 size(_fonts),
120 size(regular_fallback_chain),
121 size(bold_fallback_chain),
122 size(italic_fallback_chain));
123
124 // For each font, find fallback list.
125 for (auto const& font : _fallback_chain) {
126 auto fallback_chain = std::vector<hi::font_id>{};
127
128 // Put the fonts from the same family, italic and weight first.
129 for (auto const& fallback : _fallback_chain) {
130 // clang-format off
131 if (
132 (fallback != font) and
133 (fallback->family_name == font->family_name) and
134 (fallback->style == font->style) and
135 almost_equal(fallback->weight, font->weight)
136 ) {
137 fallback_chain.push_back(fallback);
138 }
139 // clang-format on
140 }
141
142 if (almost_equal(font->weight, font_weight::bold)) {
143 std::copy(begin(bold_fallback_chain), end(bold_fallback_chain), std::back_inserter(fallback_chain));
144 } else if (font->style == font_style::italic) {
145 std::copy(begin(italic_fallback_chain), end(italic_fallback_chain), std::back_inserter(fallback_chain));
146 } else {
147 std::copy(begin(regular_fallback_chain), end(regular_fallback_chain), std::back_inserter(fallback_chain));
148 }
149
150 font->fallback_chain = std::move(fallback_chain);
151 }
152 }
153
157 [[nodiscard]] font_family_id find_family(std::string const& family_name) const noexcept
158 {
159 auto it = _family_names.find(to_lower(family_name));
160 if (it == _family_names.end()) {
161 return std::nullopt;
162 } else {
163 return it->second;
164 }
165 }
166
170 [[nodiscard]] font_family_id register_family(std::string_view family_name)
171 {
172 auto name = to_lower(family_name);
173
174 auto it = _family_names.find(name);
175 if (it == _family_names.end()) {
176 if (_font_variants.size() >= font_family_id::empty_value) {
177 throw std::overflow_error("Too many font-family-ids registered");
178 }
179
180 auto const family_id = font_family_id{gsl::narrow_cast<font_family_id::value_type>(_font_variants.size())};
181 _font_variants.emplace_back();
182 _family_names[name] = family_id;
183 return family_id;
184 } else {
185 return it->second;
186 }
187 }
188
196 [[nodiscard]] font_id find_font(font_family_id family_id, font_variant variant) const noexcept
197 {
198 hi_assert(family_id);
199 hi_assert_bounds(*family_id, _font_variants);
200
201 auto const& variants = _font_variants[*family_id];
202 for (auto i : alternatives(variant)) {
203 if (auto id = variants[i]) {
204 return id;
205 }
206 }
207
208 // If a family exists, there must be at least one font variant available.
209 hi_no_default();
210 }
211
212 [[nodiscard]] font &get_font(font_id id) const
213 {
214 return *_fonts.at(*id);
215 }
216
225 [[nodiscard]] font_glyph_ids find_glyph(font_id font, hi::grapheme grapheme) const noexcept
226 {
227 // First try the selected font.
228 if (auto const glyph_ids = font->find_glyph(grapheme); not glyph_ids.empty()) {
229 return {font, std::move(glyph_ids)};
230 }
231
232 // Scan fonts which are fallback to this.
233 for (auto const fallback : font->fallback_chain) {
234 hi_axiom(not fallback.empty());
235 if (auto const glyph_ids = fallback->find_glyph(grapheme); not glyph_ids.empty()) {
236 return {*fallback, std::move(glyph_ids)};
237 }
238 }
239
240 // If all everything has failed, use the tofu block of the original font.
241 return {font, {glyph_id{0}}};
242 }
243
244private:
248
251 std::vector<std::array<font_id, font_variant::size()>> _font_variants;
252
254 std::vector<hi::font_id> _fallback_chain;
255
256 [[nodiscard]] std::vector<hi::font_id> make_fallback_chain(font_weight weight, font_style style) noexcept
257 {
258 auto r = _fallback_chain;
259
260 std::stable_partition(begin(r), end(r), [weight, style](auto const& item) {
261 return (item->style == style) and almost_equal(item->weight, weight);
262 });
263
264 auto char_mask = std::bitset<0x11'0000>{};
265 for (auto& font : r) {
266 if (font->char_map.update_mask(char_mask) == 0) {
267 // This font did not add any code points.
268 font = std::nullopt;
269 }
270 }
271
272 std::erase(r, std::nullopt);
273 return r;
274 }
275};
276
277namespace detail {
278inline std::unique_ptr<font_book> font_book_global = nullptr;
279}
280
281inline font_book& font_book::global() noexcept
282{
283 if (not detail::font_book_global) {
284 detail::font_book_global = std::make_unique<font_book>();
285 }
286 return *detail::font_book_global;
287}
288
299inline font_id register_font_file(std::filesystem::path const& path)
300{
301 return font_book::global().register_font_file(path);
302}
303
304inline void register_font_directory(std::filesystem::path const& path)
305{
306 return font_book::global().register_font_directory(path);
307}
308
309template<typename Range>
310inline void register_font_directories(Range&& range) noexcept
311{
312 for (auto const& path : range) {
313 font_book::global().register_font_directory(path, false);
314 }
315 font_book::global().post_process();
316}
317
321[[nodiscard]] inline font_family_id find_font_family(std::string const& family_name) noexcept
322{
323 return font_book::global().find_family(family_name);
324}
325
333[[nodiscard]] inline font_id find_font(font_family_id family_id, font_variant variant = font_variant{}) noexcept
334{
335 return font_book::global().find_font(family_id, variant);
336}
337
345[[nodiscard]] inline font_id find_font(std::string const& family_name, font_variant variant = font_variant{}) noexcept
346{
347 if (auto family_id = find_font_family(family_name)) {
348 return find_font(family_id, variant);
349 } else {
350 return font_id{};
351 }
352}
353
362[[nodiscard]] inline font_glyph_ids find_glyph(font_id font, grapheme grapheme) noexcept
363{
364 return font_book::global().find_glyph(font, grapheme);
365}
366
367[[nodiscard]] inline font_glyph_ids find_glyph(elusive_icon rhs) noexcept
368{
369 auto const id = find_font("elusiveicons", font_variant{font_weight::medium, font_style::normal});
370 hi_assert(not id.empty(), "Could not find Elusive icon font");
371 return find_glyph(id, std::to_underlying(rhs));
372}
373
374[[nodiscard]] inline font_glyph_ids find_glyph(hikogui_icon rhs) noexcept
375{
376 auto const id = find_font("Hikogui Icons", font_variant{font_weight::regular, font_style::normal});
377 hi_assert(not id.empty(), "Could not find HikoGUI icon font");
378 return find_glyph(id, std::to_underlying(rhs));
379}
380
381[[nodiscard]] inline font &get_font(font_id id)
382{
383 return font_book::global().get_font(id);
384}
385
386[[nodiscard]] inline font *font_id::operator->() const
387{
388 return std::addressof(get_font(*this));
389}
390
391} // namespace hi::inline v1
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
font_weight
Definition font_weight.hpp:21
@ bold
700: Bold
Definition font_weight.hpp:28
@ regular
400: Normal / Regular
Definition font_weight.hpp:25
font_id find_font(font_family_id family_id, font_variant variant=font_variant{}) noexcept
Find a font closest to the variant.
Definition font_book.hpp:333
font_id register_font_file(std::filesystem::path const &path)
Register a font.
Definition font_book.hpp:299
font_glyph_ids find_glyph(font_id font, grapheme grapheme) noexcept
Find a glyph using the given code-point.
Definition font_book.hpp:362
generator< font_weight > alternatives(font_weight start) noexcept
Generate alternatives for the font_weight.
Definition font_weight.hpp:119
font_family_id find_font_family(std::string const &family_name) noexcept
Find font family id.
Definition font_book.hpp:321
font_id register_font_file(std::filesystem::path const &path, bool post_process=true)
Register a font.
Definition font_book.hpp:57
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:106
void register_font_directory(std::filesystem::path const &path, bool post_process=true)
Register all fonts found in a directory.
Definition font_book.hpp:83
font_glyph_ids find_glyph(font_id font, hi::grapheme grapheme) const noexcept
Find a combination of glyphs matching the given grapheme.
Definition font_book.hpp:225
font_family_id register_family(std::string_view family_name)
Register font family id.
Definition font_book.hpp:170
font_id find_font(font_family_id family_id, font_variant variant) const noexcept
Find a font closest to the variant.
Definition font_book.hpp:196
font_family_id find_family(std::string const &family_name) const noexcept
Find font family id.
Definition font_book.hpp:157
Definition font_font.hpp:32
std::string family_name
The family name as parsed from the font file.
Definition font_font.hpp:38
glyph_id find_glyph(char32_t c) const noexcept
Get the glyph for a code-point.
Definition font_font.hpp:88
std::vector< hi::font_id > fallback_chain
List of fonts to use as a fallback for this font.
Definition font_font.hpp:70
An identifier for a font-family that was registered with HikoGUI.
Definition font_id.hpp:23
font * operator->() const
Dereference to a font-object.
Definition font_book.hpp:386
A font variant is one of 16 different fonts that can be part of a family.
Definition font_variant.hpp:27
Definition trace.hpp:43
A grapheme-cluster, what a user thinks a character is.
Definition grapheme.hpp:168
T addressof(T... args)
T back_inserter(T... args)
T copy(T... args)
T move(T... args)
T sort(T... args)
T stable_partition(T... args)
T what(T... args)