34 true_type_font(std::filesystem::path
const& path) : _path(path), _view(file_view{path})
36 ++global_counter<
"ttf:map">;
38 _bytes = as_span<std::byte const>(_view);
39 parse_font_directory(_bytes);
44 ++global_counter<
"ttf:unmap">;
47 throw parse_error(std::format(
"{}: Could not parse font directory.\n{}", path.string(), e.
what()));
58 [[nodiscard]]
bool loaded() const noexcept
override
60 return to_bool(_view);
67 hi_check(*
glyph_id < num_glyphs,
"glyph_id is not valid in this font.");
69 auto const glyph_bytes = otype_loca_get(_loca_table_bytes, _glyf_table_bytes,
glyph_id, _loca_is_offset32);
71 if (otype_glyf_is_compound(glyph_bytes)) {
72 auto r = graphic_path{};
74 for (
auto const& component : otype_glyf_get_compound(glyph_bytes, _em_scale)) {
75 auto component_path = component.scale * get_path(component.glyph_id);
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;
83 component_path = translate2{component.offset} * component_path;
91 return otype_glyf_get_path(glyph_bytes, _em_scale);
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;
108 hi_check(*
glyph_id < num_glyphs,
"glyph_id is not valid in this font.");
110 auto const glyph_bytes = otype_loca_get(_loca_table_bytes, _glyf_table_bytes,
glyph_id, _loca_is_offset32);
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);
122 auto const[advance_width, left_side_bearing] = otype_hmtx_get(_hmtx_table_bytes,
glyph_id, _num_horizontal_metrics, _em_scale);
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());
132 auto r = shape_run_basic(run);
137 auto positioned =
false;
139 if (not positioned and not _kern_table_bytes.empty()) {
144 hi_log_error(
"Turning off invalid 'kern' table in font '{} {}': {}", family_name, sub_family_name, e.
what());
145 _kern_table_bytes = {};
155 std::filesystem::path _path;
161 mutable file_view _view;
163 float OS2_x_height = 0;
164 float OS2_cap_height = 0;
168 uint16_t _num_horizontal_metrics;
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;
179 void cache_tables(std::span<std::byte const> bytes)
const
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);
186 _kern_table_bytes = otype_sfnt_search<
"kern">(bytes);
187 _GSUB_table_bytes = otype_sfnt_search<
"GSUB">(bytes);
190 void load_view() const noexcept
196 _view = file_view{_path};
197 _bytes = as_span<std::byte const>(_view);
198 ++global_counter<
"ttf:map">;
199 cache_tables(_bytes);
207 void parse_font_directory(std::span<std::byte const> bytes)
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;
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);
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;
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;
234 if (
auto cmap_bytes = otype_sfnt_search<"cmap">(bytes); not cmap_bytes.empty()) {
235 char_map = otype_cmap_parse(cmap_bytes);
237 throw parse_error(
"Could not find 'cmap'");
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);
243 condensed = os2.condensed;
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;
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;
262 if (name_lower.find(
"oblique") != std::string::npos) {
263 style = font_style::oblique;
266 if (name_lower.find(
"condensed") != std::string::npos) {
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 ) {
276 if (name_lower.find(
"sans") != std::string::npos) {
278 }
else if (name_lower.find(
"serif") != std::string::npos) {
282 if (name_lower.find(
"regular") != std::string::npos or
283 name_lower.find(
"medium") != std::string::npos) {
284 weight = font_weight::regular;
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;
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;
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;
313 if (not _kern_table_bytes.empty()) {
316 if (not _GSUB_table_bytes.empty()) {
320 if (OS2_x_height > 0.0f) {
321 metrics.x_height = em_squares(OS2_x_height);
325 metrics.x_height = em_squares(get_metrics(glyph_id).bounding_rectangle.height());
329 if (OS2_cap_height > 0.0f) {
330 metrics.cap_height = em_squares(OS2_cap_height);
334 metrics.cap_height = em_squares(get_metrics(glyph_id).bounding_rectangle.height());
340 metrics.digit_advance = em_squares(get_metrics(glyph_id).advance);
346 [[nodiscard]] font::shape_run_result_type shape_run_basic(gstring run)
const
348 auto r = font::shape_run_result_type{};
349 r.reserve(run.size());
351 for (
auto const grapheme : run) {
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);
360 r.advances.push_back(base_glyph_metrics.advance);
361 r.glyph_count.push_back(glyphs.size());
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);
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];
373 auto const glyph_metrics = get_metrics(glyph_id);
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);
379 glyph_position.x() += glyph_metrics.advance;
385 void shape_run_kern(font::shape_run_result_type& shape_result)
const
387 auto const num_graphemes = shape_result.advances.size();
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) {
395 auto const base_glyph_id = shape_result.glyphs[glyph_index];
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);
400 hi_axiom(grapheme_index != 0);
401 shape_result.advances[grapheme_index - 1] += kerning.x();
404 glyph_index += shape_result.glyph_count[grapheme_index];