HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
true_type_font.hpp
1// Copyright Take Vos 2019-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 "otype_utilities.hpp"
9#include "otype_sfnt.hpp"
10#include "otype_cmap.hpp"
11#include "otype_glyf.hpp"
12#include "otype_head.hpp"
13#include "otype_hhea.hpp"
14#include "otype_hmtx.hpp"
15#include "otype_kern.hpp"
16#include "otype_loca.hpp"
17#include "otype_maxp.hpp"
18#include "otype_name.hpp"
19#include "otype_os2.hpp"
20#include "font_char_map.hpp"
21#include "../file/file_view.hpp"
22#include "../graphic_path/graphic_path.hpp"
23#include "../telemetry/telemetry.hpp"
24#include "../utility/utility.hpp"
25#include <memory>
26#include <filesystem>
27
28hi_export_module(hikogui.font.true_type_font);
29
30hi_export namespace hi::inline v1 {
31
32hi_export class true_type_font final : public font {
33public:
34 true_type_font(std::filesystem::path const& path) : _path(path), _view(file_view{path})
35 {
36 ++global_counter<"ttf:map">;
37 try {
38 _bytes = as_span<std::byte const>(_view);
39 parse_font_directory(_bytes);
40
41 // Clear the view to reclaim resources.
42 _view = {};
43 _bytes = {};
44 ++global_counter<"ttf:unmap">;
45
46 } catch (std::exception const& e) {
47 throw parse_error(std::format("{}: Could not parse font directory.\n{}", path.string(), e.what()));
48 }
49 }
50
51 true_type_font() = delete;
52 true_type_font(true_type_font const& other) = delete;
53 true_type_font& operator=(true_type_font const& other) = delete;
54 true_type_font(true_type_font&& other) = delete;
55 true_type_font& operator=(true_type_font&& other) = delete;
56 ~true_type_font() = default;
57
58 [[nodiscard]] bool loaded() const noexcept override
59 {
60 return to_bool(_view);
61 }
62
63 [[nodiscard]] graphic_path get_path(hi::glyph_id glyph_id) const override
64 {
65 load_view();
66
67 hi_check(*glyph_id < num_glyphs, "glyph_id is not valid in this font.");
68
69 auto const glyph_bytes = otype_loca_get(_loca_table_bytes, _glyf_table_bytes, glyph_id, _loca_is_offset32);
70
71 if (otype_glyf_is_compound(glyph_bytes)) {
72 auto r = graphic_path{};
73
74 for (auto const& component : otype_glyf_get_compound(glyph_bytes, _em_scale)) {
75 auto component_path = component.scale * get_path(component.glyph_id);
76
77 if (component.use_points) {
78 auto const compound_point = hi_check_at(r.points, component.compound_point_index).p;
79 auto const component_point = hi_check_at(component_path.points, component.component_point_index).p;
80 auto const offset = translate2{compound_point - component_point};
81 component_path = offset * component_path;
82 } else {
83 component_path = translate2{component.offset} * component_path;
84 }
85
86 r += component_path;
87 }
88 return r;
89
90 } else {
91 return otype_glyf_get_path(glyph_bytes, _em_scale);
92 }
93 }
94
95 [[nodiscard]] float get_advance(hi::glyph_id glyph_id) const override
96 {
97 load_view();
98
99 hi_check(*glyph_id < num_glyphs, "glyph_id is not valid in this font.");
100 auto const[advance_width, left_side_bearing] = otype_hmtx_get(_hmtx_table_bytes, glyph_id, _num_horizontal_metrics, _em_scale);
101 return advance_width;
102 }
103
104 [[nodiscard]] glyph_metrics get_metrics(hi::glyph_id glyph_id) const override
105 {
106 load_view();
107
108 hi_check(*glyph_id < num_glyphs, "glyph_id is not valid in this font.");
109
110 auto const glyph_bytes = otype_loca_get(_loca_table_bytes, _glyf_table_bytes, glyph_id, _loca_is_offset32);
111
112 if (otype_glyf_is_compound(glyph_bytes)) {
113 for (auto const& component : otype_glyf_get_compound(glyph_bytes, _em_scale)) {
114 if (component.use_for_metrics) {
115 return get_metrics(component.glyph_id);
116 }
117 }
118 }
119
120 auto r = glyph_metrics{};
121 r.bounding_rectangle = otype_glyf_get_bounding_box(glyph_bytes, _em_scale);
122 auto const[advance_width, left_side_bearing] = otype_hmtx_get(_hmtx_table_bytes, glyph_id, _num_horizontal_metrics, _em_scale);
123
124 r.advance = advance_width;
125 r.left_side_bearing = left_side_bearing;
126 r.right_side_bearing = advance_width - (left_side_bearing + r.bounding_rectangle.width());
127 return r;
128 }
129
130 [[nodiscard]] shape_run_result_type shape_run(iso_639 language, iso_15924 script, gstring run) const override
131 {
132 auto r = shape_run_basic(run);
133
134 // Glyphs should be morphed only once.
135 // auto morphed = false;
136 // Glyphs should be positioned only once.
137 auto positioned = false;
138
139 if (not positioned and not _kern_table_bytes.empty()) {
140 try {
141 shape_run_kern(r);
142 positioned = true;
143 } catch (std::exception const& e) {
144 hi_log_error("Turning off invalid 'kern' table in font '{} {}': {}", family_name, sub_family_name, e.what());
145 _kern_table_bytes = {};
146 }
147 }
148
149 return r;
150 }
151
152private:
155 std::filesystem::path _path;
156
161 mutable file_view _view;
162
163 float OS2_x_height = 0;
164 float OS2_cap_height = 0;
165
166 float _em_scale;
167
168 uint16_t _num_horizontal_metrics;
169
170 int num_glyphs;
171 mutable std::span<std::byte const> _bytes;
172 mutable std::span<std::byte const> _loca_table_bytes;
173 mutable std::span<std::byte const> _glyf_table_bytes;
174 mutable std::span<std::byte const> _hmtx_table_bytes;
175 mutable std::span<std::byte const> _kern_table_bytes;
176 mutable std::span<std::byte const> _GSUB_table_bytes;
177 bool _loca_is_offset32;
178
179 void cache_tables(std::span<std::byte const> bytes) const
180 {
181 _loca_table_bytes = otype_sfnt_search<"loca">(bytes);
182 _glyf_table_bytes = otype_sfnt_search<"glyf">(bytes);
183 _hmtx_table_bytes = otype_sfnt_search<"hmtx">(bytes);
184
185 // Optional tables.
186 _kern_table_bytes = otype_sfnt_search<"kern">(bytes);
187 _GSUB_table_bytes = otype_sfnt_search<"GSUB">(bytes);
188 }
189
190 void load_view() const noexcept
191 {
192 if (_view) {
193 [[likely]] return;
194 }
195
196 _view = file_view{_path};
197 _bytes = as_span<std::byte const>(_view);
198 ++global_counter<"ttf:map">;
199 cache_tables(_bytes);
200 }
201
207 void parse_font_directory(std::span<std::byte const> bytes)
208 {
209 if (auto head_bytes = otype_sfnt_search<"head">(bytes); not head_bytes.empty()) {
210 auto head = otype_head_parse(head_bytes);
211 _loca_is_offset32 = head.loca_is_offset32;
212 _em_scale = head.em_scale;
213 }
214
215 if (auto name_bytes = otype_sfnt_search<"name">(bytes); not name_bytes.empty()) {
216 auto names = otype_name_get_family(name_bytes);
217 family_name = std::move(names.family_name);
218 sub_family_name = std::move(names.sub_family_name);
219 }
220
221 if (auto maxp_bytes = otype_sfnt_search<"maxp">(bytes); not maxp_bytes.empty()) {
222 auto maxp = otype_maxp_parse(maxp_bytes);
223 num_glyphs = maxp.num_glyphs;
224 }
225
226 if (auto hhea_bytes = otype_sfnt_search<"hhea">(bytes); not hhea_bytes.empty()) {
227 auto hhea = otype_hhea_parse(hhea_bytes, _em_scale);
228 metrics.ascender = em_squares(hhea.ascender);
229 metrics.descender = em_squares(-hhea.descender);
230 metrics.line_gap = em_squares(hhea.line_gap);
231 _num_horizontal_metrics = hhea.number_of_h_metrics;
232 }
233
234 if (auto cmap_bytes = otype_sfnt_search<"cmap">(bytes); not cmap_bytes.empty()) {
235 char_map = otype_cmap_parse(cmap_bytes);
236 } else {
237 throw parse_error("Could not find 'cmap'");
238 }
239
240 if (auto os2_bytes = otype_sfnt_search<"OS/2">(bytes); not os2_bytes.empty()) {
241 auto os2 = otype_parse_os2(os2_bytes, _em_scale);
242 weight = os2.weight;
243 condensed = os2.condensed;
244 serif = os2.serif;
245 monospace = os2.monospace;
246 style = os2.italic ? font_style::italic : font_style::normal;
247 OS2_x_height = os2.x_height;
248 OS2_cap_height = os2.cap_height;
249 }
250
251 cache_tables(bytes);
252
253 // Parsing the weight, italic and other features from the sub-family-name
254 // is much more reliable than the explicit data in the OS/2 table.
255 // Only use the OS/2 data as a last resort.
256 // clang-format off
257 auto name_lower = to_lower(family_name + " " + sub_family_name);
258 if (name_lower.find("italic") != std::string::npos) {
259 style = font_style::italic;
260 }
261
262 if (name_lower.find("oblique") != std::string::npos) {
263 style = font_style::oblique;
264 }
265
266 if (name_lower.find("condensed") != std::string::npos) {
267 condensed = true;
268 }
269
270 if (name_lower.find("mono") != std::string::npos or
271 name_lower.find("console") != std::string::npos or
272 name_lower.find("code") != std::string::npos ) {
273 monospace = true;
274 }
275
276 if (name_lower.find("sans") != std::string::npos) {
277 serif = false;
278 } else if (name_lower.find("serif") != std::string::npos) {
279 serif = true;
280 }
281
282 if (name_lower.find("regular") != std::string::npos or
283 name_lower.find("medium") != std::string::npos) {
284 weight = font_weight::regular;
285 } else if (
286 name_lower.find("extra light") != std::string::npos or
287 name_lower.find("extra-light") != std::string::npos or
288 name_lower.find("extralight") != std::string::npos) {
289 weight = font_weight::extra_light;
290 } else if (
291 name_lower.find("extra black") != std::string::npos or
292 name_lower.find("extra-black") != std::string::npos or
293 name_lower.find("extrablack") != std::string::npos) {
294 weight = font_weight::extra_black;
295 } else if (
296 name_lower.find("extra bold") != std::string::npos or
297 name_lower.find("extra-bold") != std::string::npos or
298 name_lower.find("extrabold") != std::string::npos) {
299 weight = font_weight::extra_bold;
300 } else if (name_lower.find("thin") != std::string::npos) {
301 weight = font_weight::thin;
302 } else if (name_lower.find("light") != std::string::npos) {
303 weight = font_weight::light;
304 } else if (name_lower.find("bold") != std::string::npos) {
305 weight = font_weight::bold;
306 } else if (name_lower.find("black") != std::string::npos) {
307 weight = font_weight::black;
308 }
309 // clang-format on
310
311 // Figure out the features.
312 features.clear();
313 if (not _kern_table_bytes.empty()) {
314 features += "kern,";
315 }
316 if (not _GSUB_table_bytes.empty()) {
317 features += "GSUB,";
318 }
319
320 if (OS2_x_height > 0.0f) {
321 metrics.x_height = em_squares(OS2_x_height);
322 } else {
323 auto const glyph_id = find_glyph('x');
324 if (glyph_id) {
325 metrics.x_height = em_squares(get_metrics(glyph_id).bounding_rectangle.height());
326 }
327 }
328
329 if (OS2_cap_height > 0.0f) {
330 metrics.cap_height = em_squares(OS2_cap_height);
331 } else {
332 auto const glyph_id = find_glyph('H');
333 if (glyph_id) {
334 metrics.cap_height = em_squares(get_metrics(glyph_id).bounding_rectangle.height());
335 }
336 }
337
338 auto const glyph_id = find_glyph('8');
339 if (glyph_id) {
340 metrics.digit_advance = em_squares(get_metrics(glyph_id).advance);
341 }
342 }
343
346 [[nodiscard]] font::shape_run_result_type shape_run_basic(gstring run) const
347 {
348 auto r = font::shape_run_result_type{};
349 r.reserve(run.size());
350
351 for (auto const grapheme : run) {
352 auto const glyphs = find_glyph(grapheme);
353
354 // At this point ligature substitution has not been done. So we should
355 // have at least one glyph per grapheme.
356 hi_axiom(not glyphs.empty());
357 auto const base_glyph_id = glyphs.front();
358 auto const base_glyph_metrics = get_metrics(base_glyph_id);
359
360 r.advances.push_back(base_glyph_metrics.advance);
361 r.glyph_count.push_back(glyphs.size());
362
363 // Store information of the base-glyph
364 r.glyphs.push_back(base_glyph_id);
365 r.glyph_positions.push_back(point2{});
366 r.glyph_rectangles.push_back(base_glyph_metrics.bounding_rectangle);
367
368 // Position the mark-glyphs.
369 auto glyph_position = point2{base_glyph_metrics.advance, 0.0f};
370 for (auto i = 1_uz; i != glyphs.size(); ++i) {
371 auto const glyph_id = glyphs[i];
372
373 auto const glyph_metrics = get_metrics(glyph_id);
374
375 r.glyphs.push_back(glyph_id);
376 r.glyph_positions.push_back(glyph_position);
377 r.glyph_rectangles.push_back(glyph_metrics.bounding_rectangle);
378
379 glyph_position.x() += glyph_metrics.advance;
380 }
381 }
382 return r;
383 }
384
385 void shape_run_kern(font::shape_run_result_type& shape_result) const
386 {
387 auto const num_graphemes = shape_result.advances.size();
388
389 auto prev_base_glyph_id = hi::glyph_id{};
390 auto glyph_index = 0_uz;
391 for (auto grapheme_index = 0_uz; grapheme_index != num_graphemes; ++grapheme_index) {
392 // Kerning is done between base-glyphs of consecutive graphemes.
393 // Marks should be handled by the Unicode mark positioning algorithm.
394 // Or by the more stateful GPOS table.
395 auto const base_glyph_id = shape_result.glyphs[glyph_index];
396
397 if (prev_base_glyph_id) {
398 auto const kerning = otype_kern_find(_kern_table_bytes, prev_base_glyph_id, base_glyph_id, _em_scale);
399
400 hi_axiom(grapheme_index != 0);
401 shape_result.advances[grapheme_index - 1] += kerning.x();
402 }
403
404 glyph_index += shape_result.glyph_count[grapheme_index];
405 }
406 }
407};
408
409} // namespace hi::inline v1
Defines the file_view class.
Defined font_char_map type.
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
hi_export auto find_glyph(font const &font, grapheme grapheme) noexcept
Find a glyph using the given code-point.
Definition font_book.hpp:440
Definition font_font.hpp:31
Definition font_font.hpp:153
Definition glyph_metrics.hpp:20
aarectangle bounding_rectangle
Definition glyph_metrics.hpp:23
Definition true_type_font.hpp:32
graphic_path get_path(hi::glyph_id glyph_id) const override
Load a glyph into a path.
Definition true_type_font.hpp:63
shape_run_result_type shape_run(iso_639 language, iso_15924 script, gstring run) const override
Shape a run of graphemes.
Definition true_type_font.hpp:130
bool loaded() const noexcept override
Return if the font is loaded.
Definition true_type_font.hpp:58
glyph_metrics get_metrics(hi::glyph_id glyph_id) const override
Load a glyph into a path.
Definition true_type_font.hpp:104
float get_advance(hi::glyph_id glyph_id) const override
Get the advance for a glyph.
Definition true_type_font.hpp:95
ISO-639 language code.
Definition iso_639.hpp:29
Definition tagged_id.hpp:26
T move(T... args)
T what(T... args)