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