HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
otype_kern.hpp
1// Copyright Take Vos 2023.
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 "otype_utilities.hpp"
8#include "../utility/utility.hpp"
9#include "../macros.hpp"
10#include <span>
11#include <cstddef>
12
13hi_export_module(hikogui.font.otype_kern);
14
15hi_export namespace hi { inline namespace v1 {
16
17[[nodiscard]] inline std::optional<float>
18otype_kern_sub0_find(size_t &offset, std::span<std::byte const> bytes, glyph_id first_glyph_id, glyph_id second_glyph_id, float em_scale)
19{
20 struct header_type {
21 big_uint16_buf_t num_pairs;
22 big_uint16_buf_t search_range;
23 big_uint16_buf_t entry_selector;
24 big_uint16_buf_t range_shift;
25 };
26
27 struct entry_type {
28 big_uint16_buf_t left;
29 big_uint16_buf_t right;
30 otype_fword_buf_t value;
31 };
32
33 auto const& header = implicit_cast<header_type>(offset, bytes);
34 auto const entries = implicit_cast<entry_type>(offset, bytes, *header.num_pairs);
35
36 auto const key = (wide_cast<uint32_t>(*first_glyph_id) << 16) | wide_cast<uint32_t>(*second_glyph_id);
37 if (auto const entry_ptr = fast_binary_search_eq<std::endian::big>(entries, key)) {
38 return entry_ptr->value * em_scale;
39 } else {
40 return std::nullopt;
41 }
42}
43
49[[nodiscard]] inline vector2
50otype_kern_v0_find(std::span<std::byte const> bytes, glyph_id first_glyph_id, glyph_id second_glyph_id, float em_scale)
51{
52 struct header_type {
53 big_uint16_buf_t version;
54 big_uint16_buf_t num_tables;
55 };
56
57 struct entry_type {
58 big_uint16_buf_t version;
59 big_uint16_buf_t length;
60 big_uint16_buf_t coverage;
61 };
62
63 auto offset = 0_uz;
64 auto const& header = implicit_cast<header_type>(offset, bytes);
65 hi_check(*header.version == 0, "'kern' table expect version to be version 0.");
66 auto const num_tables = *header.num_tables;
67
68 // The kerning is additive when multiple sub-tables are involved.
69 auto r = vector2{};
70 for (auto i = 0_uz; i != num_tables; ++i) {
71 auto const& entry = implicit_cast<entry_type>(offset, bytes);
72 hi_check(*entry.version == 0, "'kern' expect sub-table version to be 0.");
73
74 // The entry length field is broken, due to being only 16-bits.
75 // There are Windows system fonts where the 'kern' table is 0x13b60 in length,
76 // with only a single sub-table with an entry-length of 0x3b5c. As you see the
77 // top bits of the entry-length are simply truncated.
78 //
79 // This means we have to abort unknown coverage and unknown sub-tables as we
80 // are unable to skip over those.
81
82 auto const entry_coverage = *entry.coverage;
83
84 auto const cross_stream = to_bool(entry_coverage & 0x0004);
85 hi_check(not cross_stream, "'kern' this font contains cross-stream kerning which is unsuported.");
86
87 auto const format = entry_coverage >> 8;
88 hi_check(format == 0, "'kern' this font contains a unsuported subtable.");
89
90 auto const kerning = otype_kern_sub0_find(offset, bytes, first_glyph_id, second_glyph_id, em_scale);
91 if (kerning) {
92 auto const horizontal = to_bool(entry_coverage & 0x0001);
93 auto const minimum = to_bool(entry_coverage & 0x0002);
94 auto const overwrite = to_bool(entry_coverage & 0x0008);
95
96 if (overwrite) {
97 r = horizontal ? vector2{*kerning, 0.0f} : vector2{0.0f, *kerning};
98
99 } else if (minimum) {
100 if (horizontal) {
101 r.x() = std::min(r.x(), *kerning);
102 } else {
103 r.y() = std::min(r.y(), *kerning);
104 }
105
106 } else {
107 r += horizontal ? vector2{*kerning, 0.0f} : vector2{0.0f, *kerning};
108 }
109 }
110 }
111 return r;
112}
113
114[[nodiscard]] inline vector2
115otype_kern_v1_find(std::span<std::byte const> bytes, glyph_id first_glyph_id, glyph_id second_glyph_id, float em_scale)
116{
117 struct header_type {
118 big_uint32_buf_t version;
119 big_uint32_buf_t num_tables;
120 };
121
122 struct entry_type {
123 big_uint32_buf_t length;
124 big_uint16_buf_t coverage;
125 big_uint16_buf_t tuple_index;
126 };
127
128 auto offset = 0_uz;
129 auto const& header = implicit_cast<header_type>(offset, bytes);
130 hi_check(*header.version == 0x00010000, "'kern' table expect version to be version 0x00010000.");
131 auto const num_tables = *header.num_tables;
132
133 // The kerning is additive when multiple sub-tables are involved.
134 auto r = vector2{};
135 for (auto i = 0_uz; i != num_tables; ++i) {
136 auto sub_table_offset = offset;
137 auto const& entry = implicit_cast<entry_type>(sub_table_offset, bytes);
138
139 auto const entry_length = *entry.length;
140 hi_check(entry_length >= sizeof(entry_type), "'kern' subtable length is invalid.");
141
142 // Calculate the offset to the table now, because we are going to filter out sub-tables
143 // based on their coverage.
144 offset += entry_length;
145
146 // The Microsoft OpenType documentation uses bit indicies left to right, i.e. msb=0 and lsb=15.
147 auto const entry_coverage = *entry.coverage;
148
149 auto const cross_stream = to_bool(entry_coverage & 0x4000);
150 if (cross_stream) {
151 // The cross-stream specification is broken as the sub-table data
152 // may contain the value 0x8000 to turn off cross-streaming.
153 // However the specification also says to use binary-search in this
154 // data so we will never see 0x8000 in reality.
155 continue;
156 }
157
158 auto const variation = to_bool(entry_coverage & 0x2000);
159 if (variation or *entry.tuple_index != 0) {
160 // We do not support variation fonts.
161 continue;
162 }
163
164 auto const format = entry_coverage & 0xff;
165
166 auto const kerning = [&]() -> std::optional<float> {
167 if (format == 0) {
168 return otype_kern_sub0_find(sub_table_offset, bytes, first_glyph_id, second_glyph_id, em_scale);
169 } else {
170 return std::nullopt;
171 }
172 }();
173
174 if (kerning) {
175 auto const vertical = to_bool(entry_coverage & 0x8000);
176
177 auto const kerning_2D = vertical ? vector2{0.0f, *kerning} : vector2{*kerning, 0.0f};
178 r += kerning_2D;
179 }
180 }
181 return r;
182}
183
184[[nodiscard]] inline vector2
185otype_kern_find(std::span<std::byte const> bytes, glyph_id first_glyph_id, glyph_id second_glyph_id, float em_scale)
186{
187 if (bytes.empty()) {
188 // If the 'kern' table doesn't exist there are no kern adjustment.
189 return {};
190 }
191
192 auto const version = *implicit_cast<big_uint16_buf_t>(bytes);
193 if (version == 0) {
194 return otype_kern_v0_find(bytes, first_glyph_id, second_glyph_id, em_scale);
195 } else {
196 return otype_kern_v1_find(bytes, first_glyph_id, second_glyph_id, em_scale);
197 }
198}
199
200}} // namespace hi::v1
The HikoGUI namespace.
Definition array_generic.hpp:20
vector2 otype_kern_v0_find(std::span< std::byte const > bytes, glyph_id first_glyph_id, glyph_id second_glyph_id, float em_scale)
'kern' version 0 find.
Definition otype_kern.hpp:50
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
A high-level geometric vector Part of the high-level vector, point, mat and color types.
Definition vector2.hpp:27
constexpr float & x() noexcept
Access the x element from the vector.
Definition vector2.hpp:65
T left(T... args)
T min(T... args)